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.
Files changed (41) hide show
  1. package/README.md +209 -1
  2. package/android/build.gradle +2 -0
  3. package/android/src/main/java/expo/modules/breathing/BreathingConfiguration.kt +25 -0
  4. package/android/src/main/java/expo/modules/breathing/BreathingExerciseView.kt +610 -0
  5. package/android/src/main/java/expo/modules/breathing/BreathingSharedState.kt +108 -0
  6. package/android/src/main/java/expo/modules/breathing/BreathingTextCue.kt +51 -0
  7. package/android/src/main/java/expo/modules/breathing/ExpoBreathingExerciseModule.kt +177 -0
  8. package/android/src/main/java/expo/modules/breathing/ExpoBreathingExerciseView.kt +144 -0
  9. package/android/src/main/java/expo/modules/breathing/MorphingBlobView.kt +128 -0
  10. package/android/src/main/java/expo/modules/breathing/ProgressRingView.kt +50 -0
  11. package/build/ExpoBreathingExercise.types.d.ts +48 -0
  12. package/build/ExpoBreathingExercise.types.d.ts.map +1 -0
  13. package/build/ExpoBreathingExercise.types.js +2 -0
  14. package/build/ExpoBreathingExercise.types.js.map +1 -0
  15. package/build/ExpoBreathingExerciseModule.d.ts +16 -0
  16. package/build/ExpoBreathingExerciseModule.d.ts.map +1 -0
  17. package/build/ExpoBreathingExerciseModule.js +52 -0
  18. package/build/ExpoBreathingExerciseModule.js.map +1 -0
  19. package/build/ExpoBreathingExerciseView.d.ts +4 -0
  20. package/build/ExpoBreathingExerciseView.d.ts.map +1 -0
  21. package/build/ExpoBreathingExerciseView.js +7 -0
  22. package/build/ExpoBreathingExerciseView.js.map +1 -0
  23. package/build/index.d.ts +3 -0
  24. package/build/index.d.ts.map +1 -1
  25. package/build/index.js +3 -0
  26. package/build/index.js.map +1 -1
  27. package/expo-module.config.json +2 -2
  28. package/ios/Breathing/BreathingConfiguration.swift +57 -0
  29. package/ios/Breathing/BreathingExerciseView.swift +451 -0
  30. package/ios/Breathing/BreathingParticlesView.swift +81 -0
  31. package/ios/Breathing/BreathingSharedState.swift +84 -0
  32. package/ios/Breathing/BreathingTextCue.swift +14 -0
  33. package/ios/Breathing/MorphingBlobView.swift +242 -0
  34. package/ios/Breathing/ProgressRingView.swift +27 -0
  35. package/ios/ExpoBreathingExerciseModule.swift +182 -0
  36. package/ios/ExpoBreathingExerciseView.swift +124 -0
  37. package/package.json +8 -5
  38. package/src/ExpoBreathingExercise.types.ts +50 -0
  39. package/src/ExpoBreathingExerciseModule.ts +67 -0
  40. package/src/ExpoBreathingExerciseView.tsx +11 -0
  41. package/src/index.ts +11 -0
@@ -0,0 +1,51 @@
1
+ package expo.modules.breathing
2
+
3
+ import androidx.compose.animation.AnimatedContent
4
+ import androidx.compose.animation.fadeIn
5
+ import androidx.compose.animation.fadeOut
6
+ import androidx.compose.animation.togetherWith
7
+ import androidx.compose.foundation.layout.Box
8
+ import androidx.compose.material3.Text
9
+ import androidx.compose.runtime.Composable
10
+ import androidx.compose.ui.Alignment
11
+ import androidx.compose.ui.Modifier
12
+ import androidx.compose.ui.geometry.Offset
13
+ import androidx.compose.ui.graphics.Color
14
+ import androidx.compose.ui.graphics.Shadow
15
+ import androidx.compose.ui.text.TextStyle
16
+ import androidx.compose.ui.text.font.FontWeight
17
+ import androidx.compose.ui.unit.sp
18
+
19
+ @Composable
20
+ fun BreathingTextCue(
21
+ text: String,
22
+ color: Color,
23
+ modifier: Modifier = Modifier
24
+ ) {
25
+ Box(
26
+ modifier = modifier,
27
+ contentAlignment = Alignment.Center
28
+ ) {
29
+ AnimatedContent(
30
+ targetState = text,
31
+ transitionSpec = {
32
+ fadeIn() togetherWith fadeOut()
33
+ },
34
+ label = "BreathingTextCue"
35
+ ) { currentText ->
36
+ Text(
37
+ text = currentText,
38
+ style = TextStyle(
39
+ color = color,
40
+ fontSize = 24.sp,
41
+ fontWeight = FontWeight.SemiBold,
42
+ shadow = Shadow(
43
+ color = Color.Black.copy(alpha = 0.5f),
44
+ offset = Offset(0f, 2f),
45
+ blurRadius = 8f
46
+ )
47
+ )
48
+ )
49
+ }
50
+ }
51
+ }
@@ -0,0 +1,177 @@
1
+ package expo.modules.breathing
2
+
3
+ import android.graphics.Color as AndroidColor
4
+ import expo.modules.kotlin.modules.Module
5
+ import expo.modules.kotlin.modules.ModuleDefinition
6
+
7
+ class ExpoBreathingExerciseModule : Module() {
8
+ override fun definition() = ModuleDefinition {
9
+ Name("ExpoBreathingExercise")
10
+
11
+ Events("onPhaseChange", "onExerciseComplete")
12
+
13
+ Function("startBreathingExercise") { pattern: Map<String, Any?> ->
14
+ startExercise(pattern)
15
+ }
16
+
17
+ Function("stopBreathingExercise") {
18
+ BreathingSharedState.reset()
19
+ }
20
+
21
+ Function("pauseBreathingExercise") {
22
+ val state = BreathingSharedState
23
+ if (state.state == BreathingExerciseState.RUNNING) {
24
+ state.state = BreathingExerciseState.PAUSED
25
+ state.pauseTime = System.nanoTime()
26
+ }
27
+ }
28
+
29
+ Function("resumeBreathingExercise") {
30
+ val state = BreathingSharedState
31
+ val pauseTime = state.pauseTime
32
+ if (state.state == BreathingExerciseState.PAUSED && pauseTime != null) {
33
+ val pauseDuration = System.nanoTime() - pauseTime
34
+ state.phaseStartTime += pauseDuration
35
+ state.exerciseStartTime += pauseDuration
36
+ state.state = BreathingExerciseState.RUNNING
37
+ state.pauseTime = null
38
+ }
39
+ }
40
+
41
+ View(ExpoBreathingExerciseView::class) {
42
+ Events("onPhaseChange", "onExerciseComplete")
43
+
44
+ Prop("blobColors") { view: ExpoBreathingExerciseView, colors: List<Any> ->
45
+ val parsedColors = colors.map { parseColor(it) }
46
+ view.setBlobColors(parsedColors)
47
+ }
48
+
49
+ Prop("innerBlobColor") { view: ExpoBreathingExerciseView, color: Any ->
50
+ view.setInnerBlobColor(parseColor(color))
51
+ }
52
+
53
+ Prop("progressRingColor") { view: ExpoBreathingExerciseView, color: Any ->
54
+ view.setProgressRingColor(parseColor(color))
55
+ }
56
+
57
+ Prop("textColor") { view: ExpoBreathingExerciseView, color: Any ->
58
+ view.setTextColor(parseColor(color))
59
+ }
60
+
61
+ Prop("showProgressRing") { view: ExpoBreathingExerciseView, value: Boolean ->
62
+ view.setShowProgressRing(value)
63
+ }
64
+
65
+ Prop("showTextCue") { view: ExpoBreathingExerciseView, value: Boolean ->
66
+ view.setShowTextCue(value)
67
+ }
68
+
69
+ Prop("showInnerBlob") { view: ExpoBreathingExerciseView, value: Boolean ->
70
+ view.setShowInnerBlob(value)
71
+ }
72
+
73
+ Prop("showShadow") { view: ExpoBreathingExerciseView, value: Boolean ->
74
+ view.setShowShadow(value)
75
+ }
76
+
77
+ Prop("showParticles") { view: ExpoBreathingExerciseView, value: Boolean ->
78
+ view.setShowParticles(value)
79
+ }
80
+
81
+ Prop("showWavyBlobs") { view: ExpoBreathingExerciseView, value: Boolean ->
82
+ view.setShowWavyBlobs(value)
83
+ }
84
+
85
+ Prop("showGlowEffects") { view: ExpoBreathingExerciseView, value: Boolean ->
86
+ view.setShowGlowEffects(value)
87
+ }
88
+
89
+ Prop("glowColor") { view: ExpoBreathingExerciseView, color: Any ->
90
+ view.setGlowColor(parseColor(color))
91
+ }
92
+
93
+ Prop("particleColor") { view: ExpoBreathingExerciseView, color: Any ->
94
+ view.setParticleColor(parseColor(color))
95
+ }
96
+
97
+ Prop("pointCount") { view: ExpoBreathingExerciseView, value: Int ->
98
+ view.setPointCount(value)
99
+ }
100
+
101
+ Prop("wobbleIntensity") { view: ExpoBreathingExerciseView, value: Double ->
102
+ view.setWobbleIntensity(value)
103
+ }
104
+ }
105
+ }
106
+
107
+ private fun startExercise(pattern: Map<String, Any?>) {
108
+ val state = BreathingSharedState
109
+ state.reset()
110
+
111
+ // Parse phases
112
+ @Suppress("UNCHECKED_CAST")
113
+ val phasesArray = pattern["phases"] as? List<Map<String, Any?>> ?: return
114
+
115
+ val phases = phasesArray.mapNotNull { phaseDict ->
116
+ val phaseString = phaseDict["phase"] as? String ?: return@mapNotNull null
117
+ val duration = (phaseDict["duration"] as? Number)?.toDouble() ?: return@mapNotNull null
118
+ val targetScale = (phaseDict["targetScale"] as? Number)?.toDouble() ?: return@mapNotNull null
119
+ val label = phaseDict["label"] as? String ?: return@mapNotNull null
120
+
121
+ val phase = when (phaseString) {
122
+ "inhale" -> BreathPhase.INHALE
123
+ "holdIn" -> BreathPhase.HOLD_IN
124
+ "exhale" -> BreathPhase.EXHALE
125
+ "holdOut" -> BreathPhase.HOLD_OUT
126
+ else -> return@mapNotNull null
127
+ }
128
+
129
+ BreathPhaseConfig(
130
+ phase = phase,
131
+ duration = duration / 1000.0, // Convert ms to seconds
132
+ targetScale = targetScale,
133
+ label = label
134
+ )
135
+ }
136
+
137
+ if (phases.isEmpty()) return
138
+
139
+ // Parse cycles
140
+ state.totalCycles = (pattern["cycles"] as? Number)?.toInt()
141
+
142
+ // Setup state
143
+ state.phases = phases
144
+ state.currentPhaseIndex = 0
145
+ state.currentCycle = 0
146
+ state.phaseStartTime = System.nanoTime()
147
+ state.exerciseStartTime = System.nanoTime()
148
+
149
+ // Set initial phase values
150
+ val firstPhase = phases[0]
151
+ state.currentPhase = firstPhase.phase
152
+ state.currentLabel = firstPhase.label
153
+ state.startScale = 1.0
154
+ state.targetScale = firstPhase.targetScale
155
+ state.currentScale = 1.0
156
+ state.phaseProgress = 0.0
157
+
158
+ // Set initial wobble intensity
159
+ state.wobbleIntensity = when (firstPhase.phase) {
160
+ BreathPhase.INHALE, BreathPhase.EXHALE -> 1.0
161
+ BreathPhase.HOLD_IN, BreathPhase.HOLD_OUT -> 0.3
162
+ BreathPhase.IDLE -> 0.5
163
+ }
164
+
165
+ state.state = BreathingExerciseState.RUNNING
166
+ }
167
+
168
+ private fun parseColor(value: Any): Int {
169
+ return when (value) {
170
+ is String -> AndroidColor.parseColor(value)
171
+ is Int -> value
172
+ is Double -> value.toInt()
173
+ is Long -> value.toInt()
174
+ else -> AndroidColor.WHITE
175
+ }
176
+ }
177
+ }
@@ -0,0 +1,144 @@
1
+ package expo.modules.breathing
2
+
3
+ import android.content.Context
4
+ import androidx.compose.foundation.layout.Box
5
+ import androidx.compose.foundation.layout.fillMaxSize
6
+ import androidx.compose.foundation.layout.padding
7
+ import androidx.compose.runtime.Composable
8
+ import androidx.compose.runtime.getValue
9
+ import androidx.compose.runtime.mutableStateOf
10
+ import androidx.compose.runtime.setValue
11
+ import androidx.compose.ui.Alignment
12
+ import androidx.compose.ui.Modifier
13
+ import androidx.compose.ui.graphics.Color
14
+ import androidx.compose.ui.platform.ComposeView
15
+ import androidx.compose.ui.unit.dp
16
+ import expo.modules.kotlin.AppContext
17
+ import expo.modules.kotlin.viewevent.EventDispatcher
18
+ import expo.modules.kotlin.views.ExpoView
19
+
20
+ class ExpoBreathingExerciseView(context: Context, appContext: AppContext) : ExpoView(context, appContext) {
21
+
22
+ // Mutable state for configuration
23
+ private var config by mutableStateOf(BreathingConfiguration())
24
+
25
+ private val onPhaseChange by EventDispatcher()
26
+ private val onExerciseComplete by EventDispatcher()
27
+
28
+ private val composeView = ComposeView(context).apply {
29
+ setContent {
30
+ BreathingExerciseViewContent()
31
+ }
32
+ }
33
+
34
+ init {
35
+ // Allow effects to render outside view bounds
36
+ clipChildren = false
37
+ clipToPadding = false
38
+ composeView.clipChildren = false
39
+ composeView.clipToPadding = false
40
+ addView(composeView, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT))
41
+
42
+ // Setup callbacks
43
+ setupCallbacks()
44
+ }
45
+
46
+ private fun setupCallbacks() {
47
+ BreathingSharedState.onPhaseChange = { phase, label, phaseIndex, cycle ->
48
+ onPhaseChange(
49
+ mapOf(
50
+ "phase" to phase.value,
51
+ "label" to label,
52
+ "phaseIndex" to phaseIndex,
53
+ "cycle" to cycle
54
+ )
55
+ )
56
+ }
57
+
58
+ BreathingSharedState.onExerciseComplete = { totalCycles, totalDuration ->
59
+ onExerciseComplete(
60
+ mapOf(
61
+ "totalCycles" to totalCycles,
62
+ "totalDuration" to totalDuration
63
+ )
64
+ )
65
+ }
66
+ }
67
+
68
+ @Composable
69
+ private fun BreathingExerciseViewContent() {
70
+ Box(
71
+ modifier = Modifier
72
+ .fillMaxSize()
73
+ .padding(24.dp),
74
+ contentAlignment = Alignment.Center
75
+ ) {
76
+ BreathingExerciseView(
77
+ config = config,
78
+ modifier = Modifier.fillMaxSize()
79
+ )
80
+ }
81
+ }
82
+
83
+ fun setBlobColors(colors: List<Int>) {
84
+ config = config.copy(
85
+ blobColors = colors.map { Color(it) }
86
+ )
87
+ }
88
+
89
+ fun setInnerBlobColor(color: Int) {
90
+ config = config.copy(innerBlobColor = Color(color))
91
+ }
92
+
93
+ fun setProgressRingColor(color: Int) {
94
+ config = config.copy(progressRingColor = Color(color))
95
+ }
96
+
97
+ fun setTextColor(color: Int) {
98
+ config = config.copy(textColor = Color(color))
99
+ }
100
+
101
+ fun setShowProgressRing(value: Boolean) {
102
+ config = config.copy(showProgressRing = value)
103
+ }
104
+
105
+ fun setShowTextCue(value: Boolean) {
106
+ config = config.copy(showTextCue = value)
107
+ }
108
+
109
+ fun setShowInnerBlob(value: Boolean) {
110
+ config = config.copy(showInnerBlob = value)
111
+ }
112
+
113
+ fun setShowShadow(value: Boolean) {
114
+ config = config.copy(showShadow = value)
115
+ }
116
+
117
+ fun setPointCount(value: Int) {
118
+ config = config.copy(pointCount = maxOf(6, value))
119
+ }
120
+
121
+ fun setWobbleIntensity(value: Double) {
122
+ config = config.copy(wobbleIntensity = value.coerceIn(0.0, 1.0))
123
+ }
124
+
125
+ fun setGlowColor(color: Int) {
126
+ config = config.copy(glowColor = Color(color))
127
+ }
128
+
129
+ fun setParticleColor(color: Int) {
130
+ config = config.copy(particleColor = Color(color))
131
+ }
132
+
133
+ fun setShowParticles(value: Boolean) {
134
+ config = config.copy(showParticles = value)
135
+ }
136
+
137
+ fun setShowWavyBlobs(value: Boolean) {
138
+ config = config.copy(showWavyBlobs = value)
139
+ }
140
+
141
+ fun setShowGlowEffects(value: Boolean) {
142
+ config = config.copy(showGlowEffects = value)
143
+ }
144
+ }
@@ -0,0 +1,128 @@
1
+ package expo.modules.breathing
2
+
3
+ import androidx.compose.foundation.Canvas
4
+ import androidx.compose.runtime.Composable
5
+ import androidx.compose.ui.Modifier
6
+ import androidx.compose.ui.geometry.Offset
7
+ import androidx.compose.ui.graphics.Brush
8
+ import androidx.compose.ui.graphics.Color
9
+ import androidx.compose.ui.graphics.Path
10
+ import androidx.compose.ui.graphics.drawscope.DrawScope
11
+ import kotlin.math.PI
12
+ import kotlin.math.cos
13
+ import kotlin.math.min
14
+ import kotlin.math.sin
15
+ import kotlin.math.tan
16
+
17
+ @Composable
18
+ fun MorphingBlobView(
19
+ baseRadius: Float,
20
+ pointCount: Int,
21
+ offsets: List<Double>,
22
+ colors: List<Color>,
23
+ innerColor: Color,
24
+ showInnerBlob: Boolean,
25
+ modifier: Modifier = Modifier
26
+ ) {
27
+ Canvas(modifier = modifier) {
28
+ val size = min(this.size.width, this.size.height)
29
+ val center = Offset(this.size.width / 2f, this.size.height / 2f)
30
+ val effectiveRadius = baseRadius * size / 2f
31
+
32
+ // Draw main blob
33
+ val mainPath = createBlobPath(center, effectiveRadius, pointCount, offsets)
34
+ drawPath(
35
+ path = mainPath,
36
+ brush = Brush.radialGradient(
37
+ colors = colors,
38
+ center = center,
39
+ radius = effectiveRadius
40
+ )
41
+ )
42
+
43
+ // Draw inner blob for depth
44
+ if (showInnerBlob) {
45
+ val innerPath = createBlobPath(
46
+ center = center,
47
+ baseRadius = effectiveRadius * 0.6f,
48
+ pointCount = pointCount,
49
+ offsets = offsets.map { it * 0.7 }
50
+ )
51
+ drawPath(
52
+ path = innerPath,
53
+ color = innerColor
54
+ )
55
+ }
56
+ }
57
+ }
58
+
59
+ private fun DrawScope.createBlobPath(
60
+ center: Offset,
61
+ baseRadius: Float,
62
+ pointCount: Int,
63
+ offsets: List<Double>
64
+ ): Path {
65
+ val path = Path()
66
+ if (pointCount < 3) return path
67
+
68
+ // Generate points around the circle with offsets
69
+ val points = mutableListOf<Offset>()
70
+ for (i in 0 until pointCount) {
71
+ val angle = (i.toDouble() / pointCount) * 2 * PI - PI / 2
72
+ val offset = if (i < offsets.size) offsets[i] else 0.0
73
+ val radius = baseRadius * (1.0 + offset).toFloat()
74
+
75
+ points.add(
76
+ Offset(
77
+ x = center.x + (cos(angle) * radius).toFloat(),
78
+ y = center.y + (sin(angle) * radius).toFloat()
79
+ )
80
+ )
81
+ }
82
+
83
+ // Tangent coefficient for smooth cubic bezier curves
84
+ val tangentCoeff = ((4.0 / 3.0) * tan(PI / (2.0 * pointCount))).toFloat()
85
+
86
+ // Start at first point
87
+ path.moveTo(points[0].x, points[0].y)
88
+
89
+ // Draw cubic bezier curves between each pair of points
90
+ for (i in 0 until pointCount) {
91
+ val current = points[i]
92
+ val next = points[(i + 1) % pointCount]
93
+
94
+ // Calculate angles for tangent directions
95
+ val currentAngle = (i.toDouble() / pointCount) * 2 * PI - PI / 2
96
+ val nextAngle = (((i + 1) % pointCount).toDouble() / pointCount) * 2 * PI - PI / 2
97
+
98
+ // Current point's radius and tangent
99
+ val currentOffset = if (i < offsets.size) offsets[i] else 0.0
100
+ val currentRadius = baseRadius * (1.0 + currentOffset).toFloat()
101
+ val currentTangentLength = currentRadius * tangentCoeff
102
+
103
+ // Next point's radius and tangent
104
+ val nextOffset = if ((i + 1) % pointCount < offsets.size) offsets[(i + 1) % pointCount] else 0.0
105
+ val nextRadius = baseRadius * (1.0 + nextOffset).toFloat()
106
+ val nextTangentLength = nextRadius * tangentCoeff
107
+
108
+ // Control points are perpendicular to radial direction
109
+ val control1 = Offset(
110
+ x = current.x + (cos(currentAngle + PI / 2) * currentTangentLength).toFloat(),
111
+ y = current.y + (sin(currentAngle + PI / 2) * currentTangentLength).toFloat()
112
+ )
113
+
114
+ val control2 = Offset(
115
+ x = next.x + (cos(nextAngle - PI / 2) * nextTangentLength).toFloat(),
116
+ y = next.y + (sin(nextAngle - PI / 2) * nextTangentLength).toFloat()
117
+ )
118
+
119
+ path.cubicTo(
120
+ control1.x, control1.y,
121
+ control2.x, control2.y,
122
+ next.x, next.y
123
+ )
124
+ }
125
+
126
+ path.close()
127
+ return path
128
+ }
@@ -0,0 +1,50 @@
1
+ package expo.modules.breathing
2
+
3
+ import androidx.compose.foundation.Canvas
4
+ import androidx.compose.runtime.Composable
5
+ import androidx.compose.ui.Modifier
6
+ import androidx.compose.ui.geometry.Offset
7
+ import androidx.compose.ui.geometry.Size
8
+ import androidx.compose.ui.graphics.Color
9
+ import androidx.compose.ui.graphics.StrokeCap
10
+ import androidx.compose.ui.graphics.drawscope.Stroke
11
+ import kotlin.math.min
12
+
13
+ @Composable
14
+ fun ProgressRingView(
15
+ progress: Double,
16
+ color: Color,
17
+ lineWidth: Float,
18
+ modifier: Modifier = Modifier
19
+ ) {
20
+ Canvas(modifier = modifier) {
21
+ val size = min(this.size.width, this.size.height)
22
+ val radius = (size - lineWidth) / 2f
23
+ val center = Offset(this.size.width / 2f, this.size.height / 2f)
24
+ val topLeft = Offset(center.x - radius, center.y - radius)
25
+ val arcSize = Size(radius * 2, radius * 2)
26
+
27
+ // Background ring
28
+ drawArc(
29
+ color = color.copy(alpha = 0.2f),
30
+ startAngle = 0f,
31
+ sweepAngle = 360f,
32
+ useCenter = false,
33
+ topLeft = topLeft,
34
+ size = arcSize,
35
+ style = Stroke(width = lineWidth)
36
+ )
37
+
38
+ // Progress arc
39
+ val clampedProgress = progress.coerceIn(0.0, 1.0).toFloat()
40
+ drawArc(
41
+ color = color,
42
+ startAngle = -90f, // Start from top
43
+ sweepAngle = 360f * clampedProgress,
44
+ useCenter = false,
45
+ topLeft = topLeft,
46
+ size = arcSize,
47
+ style = Stroke(width = lineWidth, cap = StrokeCap.Round)
48
+ )
49
+ }
50
+ }
@@ -0,0 +1,48 @@
1
+ import type { ColorValue, StyleProp, ViewStyle } from 'react-native';
2
+ export type BreathPhase = 'inhale' | 'holdIn' | 'exhale' | 'holdOut';
3
+ export interface BreathPhaseConfig {
4
+ phase: BreathPhase;
5
+ duration: number;
6
+ targetScale: number;
7
+ label: string;
8
+ }
9
+ export interface BreathingPattern {
10
+ phases: BreathPhaseConfig[];
11
+ cycles?: number;
12
+ }
13
+ export type BreathingPreset = 'relaxing' | 'box' | 'energizing' | 'calming';
14
+ export interface PhaseChangeEvent {
15
+ phase: BreathPhase;
16
+ label: string;
17
+ phaseIndex: number;
18
+ cycle: number;
19
+ }
20
+ export interface ExerciseCompleteEvent {
21
+ totalCycles: number;
22
+ totalDuration: number;
23
+ }
24
+ export interface ExpoBreathingExerciseViewProps {
25
+ blobColors?: ColorValue[];
26
+ innerBlobColor?: ColorValue;
27
+ glowColor?: ColorValue;
28
+ particleColor?: ColorValue;
29
+ progressRingColor?: ColorValue;
30
+ textColor?: ColorValue;
31
+ showProgressRing?: boolean;
32
+ showTextCue?: boolean;
33
+ showInnerBlob?: boolean;
34
+ showShadow?: boolean;
35
+ showParticles?: boolean;
36
+ showWavyBlobs?: boolean;
37
+ showGlowEffects?: boolean;
38
+ pointCount?: number;
39
+ wobbleIntensity?: number;
40
+ onPhaseChange?: (event: {
41
+ nativeEvent: PhaseChangeEvent;
42
+ }) => void;
43
+ onExerciseComplete?: (event: {
44
+ nativeEvent: ExerciseCompleteEvent;
45
+ }) => void;
46
+ style?: StyleProp<ViewStyle>;
47
+ }
48
+ //# sourceMappingURL=ExpoBreathingExercise.types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExpoBreathingExercise.types.d.ts","sourceRoot":"","sources":["../src/ExpoBreathingExercise.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAErE,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC;AAErE,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,WAAW,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,iBAAiB,EAAE,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,eAAe,GAAG,UAAU,GAAG,KAAK,GAAG,YAAY,GAAG,SAAS,CAAC;AAE5E,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,WAAW,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,qBAAqB;IACpC,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,8BAA8B;IAC7C,UAAU,CAAC,EAAE,UAAU,EAAE,CAAC;IAC1B,cAAc,CAAC,EAAE,UAAU,CAAC;IAC5B,SAAS,CAAC,EAAE,UAAU,CAAC;IACvB,aAAa,CAAC,EAAE,UAAU,CAAC;IAC3B,iBAAiB,CAAC,EAAE,UAAU,CAAC;IAC/B,SAAS,CAAC,EAAE,UAAU,CAAC;IACvB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,WAAW,EAAE,gBAAgB,CAAA;KAAE,KAAK,IAAI,CAAC;IACnE,kBAAkB,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,WAAW,EAAE,qBAAqB,CAAA;KAAE,KAAK,IAAI,CAAC;IAC7E,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=ExpoBreathingExercise.types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExpoBreathingExercise.types.js","sourceRoot":"","sources":["../src/ExpoBreathingExercise.types.ts"],"names":[],"mappings":"","sourcesContent":["import type { ColorValue, StyleProp, ViewStyle } from 'react-native';\n\nexport type BreathPhase = 'inhale' | 'holdIn' | 'exhale' | 'holdOut';\n\nexport interface BreathPhaseConfig {\n phase: BreathPhase;\n duration: number; // milliseconds\n targetScale: number; // e.g., 1.0 to 1.3\n label: string; // \"Breathe In\"\n}\n\nexport interface BreathingPattern {\n phases: BreathPhaseConfig[];\n cycles?: number; // undefined = infinite\n}\n\nexport type BreathingPreset = 'relaxing' | 'box' | 'energizing' | 'calming';\n\nexport interface PhaseChangeEvent {\n phase: BreathPhase;\n label: string;\n phaseIndex: number;\n cycle: number;\n}\n\nexport interface ExerciseCompleteEvent {\n totalCycles: number;\n totalDuration: number;\n}\n\nexport interface ExpoBreathingExerciseViewProps {\n blobColors?: ColorValue[];\n innerBlobColor?: ColorValue;\n glowColor?: ColorValue;\n particleColor?: ColorValue;\n progressRingColor?: ColorValue;\n textColor?: ColorValue;\n showProgressRing?: boolean;\n showTextCue?: boolean;\n showInnerBlob?: boolean;\n showShadow?: boolean;\n showParticles?: boolean;\n showWavyBlobs?: boolean;\n showGlowEffects?: boolean;\n pointCount?: number; // Morphing points (default: 8)\n wobbleIntensity?: number; // 0-1\n onPhaseChange?: (event: { nativeEvent: PhaseChangeEvent }) => void;\n onExerciseComplete?: (event: { nativeEvent: ExerciseCompleteEvent }) => void;\n style?: StyleProp<ViewStyle>;\n}\n"]}
@@ -0,0 +1,16 @@
1
+ import { NativeModule } from 'expo';
2
+ import type { BreathingPattern, BreathingPreset } from './ExpoBreathingExercise.types';
3
+ declare class ExpoBreathingExerciseModuleType extends NativeModule {
4
+ startBreathingExercise(pattern: BreathingPattern): void;
5
+ stopBreathingExercise(): void;
6
+ pauseBreathingExercise(): void;
7
+ resumeBreathingExercise(): void;
8
+ }
9
+ declare const module: ExpoBreathingExerciseModuleType;
10
+ export default module;
11
+ export declare function startBreathingExercise(pattern: BreathingPattern): void;
12
+ export declare function stopBreathingExercise(): void;
13
+ export declare function pauseBreathingExercise(): void;
14
+ export declare function resumeBreathingExercise(): void;
15
+ export declare function getBreathingPreset(preset: BreathingPreset): BreathingPattern;
16
+ //# sourceMappingURL=ExpoBreathingExerciseModule.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExpoBreathingExerciseModule.d.ts","sourceRoot":"","sources":["../src/ExpoBreathingExerciseModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAuB,MAAM,MAAM,CAAC;AACzD,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAEvF,OAAO,OAAO,+BAAgC,SAAQ,YAAY;IAChE,sBAAsB,CAAC,OAAO,EAAE,gBAAgB,GAAG,IAAI;IACvD,qBAAqB,IAAI,IAAI;IAC7B,sBAAsB,IAAI,IAAI;IAC9B,uBAAuB,IAAI,IAAI;CAChC;AAED,QAAA,MAAM,MAAM,iCAAgF,CAAC;AAE7F,eAAe,MAAM,CAAC;AAEtB,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,gBAAgB,GAAG,IAAI,CAEtE;AAED,wBAAgB,qBAAqB,IAAI,IAAI,CAE5C;AAED,wBAAgB,sBAAsB,IAAI,IAAI,CAE7C;AAED,wBAAgB,uBAAuB,IAAI,IAAI,CAE9C;AAoCD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,eAAe,GAAG,gBAAgB,CAE5E"}
@@ -0,0 +1,52 @@
1
+ import { requireNativeModule } from 'expo';
2
+ const module = requireNativeModule('ExpoBreathingExercise');
3
+ export default module;
4
+ export function startBreathingExercise(pattern) {
5
+ module.startBreathingExercise(pattern);
6
+ }
7
+ export function stopBreathingExercise() {
8
+ module.stopBreathingExercise();
9
+ }
10
+ export function pauseBreathingExercise() {
11
+ module.pauseBreathingExercise();
12
+ }
13
+ export function resumeBreathingExercise() {
14
+ module.resumeBreathingExercise();
15
+ }
16
+ const BREATHING_PRESETS = {
17
+ relaxing: {
18
+ phases: [
19
+ { phase: 'inhale', duration: 4000, targetScale: 1.35, label: 'Breathe In' },
20
+ { phase: 'holdIn', duration: 7000, targetScale: 1.35, label: 'Hold' },
21
+ { phase: 'exhale', duration: 8000, targetScale: 0.75, label: 'Breathe Out' },
22
+ ],
23
+ cycles: 4,
24
+ },
25
+ box: {
26
+ phases: [
27
+ { phase: 'inhale', duration: 4000, targetScale: 1.35, label: 'Breathe In' },
28
+ { phase: 'holdIn', duration: 4000, targetScale: 1.35, label: 'Hold' },
29
+ { phase: 'exhale', duration: 4000, targetScale: 0.75, label: 'Breathe Out' },
30
+ { phase: 'holdOut', duration: 4000, targetScale: 0.75, label: 'Hold' },
31
+ ],
32
+ cycles: 4,
33
+ },
34
+ energizing: {
35
+ phases: [
36
+ { phase: 'inhale', duration: 2000, targetScale: 1.35, label: 'Breathe In' },
37
+ { phase: 'exhale', duration: 2000, targetScale: 0.75, label: 'Breathe Out' },
38
+ ],
39
+ cycles: 10,
40
+ },
41
+ calming: {
42
+ phases: [
43
+ { phase: 'inhale', duration: 4000, targetScale: 1.35, label: 'Breathe In' },
44
+ { phase: 'exhale', duration: 6000, targetScale: 0.75, label: 'Breathe Out' },
45
+ ],
46
+ cycles: 6,
47
+ },
48
+ };
49
+ export function getBreathingPreset(preset) {
50
+ return BREATHING_PRESETS[preset];
51
+ }
52
+ //# sourceMappingURL=ExpoBreathingExerciseModule.js.map