expo-orb 0.1.0
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/.eslintrc.js +5 -0
- package/LICENSE +21 -0
- package/README.md +129 -0
- package/android/build.gradle +60 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/expo/modules/orb/ExpoOrbModule.kt +79 -0
- package/android/src/main/java/expo/modules/orb/ExpoOrbView.kt +105 -0
- package/android/src/main/java/expo/modules/orb/OrbConfiguration.kt +30 -0
- package/android/src/main/java/expo/modules/orb/OrbSharedState.kt +25 -0
- package/android/src/main/java/expo/modules/orb/OrbView.kt +368 -0
- package/android/src/main/java/expo/modules/orb/ParticlesView.kt +77 -0
- package/android/src/main/java/expo/modules/orb/RotatingGlowView.kt +90 -0
- package/android/src/main/java/expo/modules/orb/WavyBlobView.kt +90 -0
- package/build/ExpoOrb.types.d.ts +17 -0
- package/build/ExpoOrb.types.d.ts.map +1 -0
- package/build/ExpoOrb.types.js +2 -0
- package/build/ExpoOrb.types.js.map +1 -0
- package/build/ExpoOrbModule.d.ts +17 -0
- package/build/ExpoOrbModule.d.ts.map +1 -0
- package/build/ExpoOrbModule.js +11 -0
- package/build/ExpoOrbModule.js.map +1 -0
- package/build/ExpoOrbModule.web.d.ts +6 -0
- package/build/ExpoOrbModule.web.d.ts.map +1 -0
- package/build/ExpoOrbModule.web.js +5 -0
- package/build/ExpoOrbModule.web.js.map +1 -0
- package/build/ExpoOrbView.d.ts +4 -0
- package/build/ExpoOrbView.d.ts.map +1 -0
- package/build/ExpoOrbView.js +7 -0
- package/build/ExpoOrbView.js.map +1 -0
- package/build/ExpoOrbView.web.d.ts +4 -0
- package/build/ExpoOrbView.web.d.ts.map +1 -0
- package/build/ExpoOrbView.web.js +17 -0
- package/build/ExpoOrbView.web.js.map +1 -0
- package/build/index.d.ts +4 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +4 -0
- package/build/index.js.map +1 -0
- package/expo-module.config.json +9 -0
- package/ios/ExpoOrb.podspec +30 -0
- package/ios/ExpoOrbModule.swift +70 -0
- package/ios/ExpoOrbView.swift +105 -0
- package/ios/Orb/OrbConfiguration.swift +53 -0
- package/ios/Orb/OrbView.swift +367 -0
- package/ios/Orb/ParticlesView.swift +156 -0
- package/ios/Orb/RealisticShadows.swift +42 -0
- package/ios/Orb/RotatingGlowView.swift +62 -0
- package/ios/Orb/WavyBlobView.swift +83 -0
- package/package.json +46 -0
- package/src/ExpoOrb.types.ts +17 -0
- package/src/ExpoOrbModule.ts +22 -0
- package/src/ExpoOrbModule.web.ts +5 -0
- package/src/ExpoOrbView.tsx +11 -0
- package/src/ExpoOrbView.web.tsx +26 -0
- package/src/index.ts +3 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import SwiftUI
|
|
2
|
+
|
|
3
|
+
struct WavyBlobView: View {
|
|
4
|
+
@State private var points: [CGPoint] = (0 ..< 6).map { index in
|
|
5
|
+
let angle = (Double(index) / 6) * 2 * .pi
|
|
6
|
+
return CGPoint(
|
|
7
|
+
x: 0.5 + cos(angle) * 0.9,
|
|
8
|
+
y: 0.5 + sin(angle) * 0.9
|
|
9
|
+
)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
private let color: Color
|
|
13
|
+
private let loopDuration: Double
|
|
14
|
+
|
|
15
|
+
init(color: Color, loopDuration: Double = 1) {
|
|
16
|
+
self.color = color
|
|
17
|
+
self.loopDuration = loopDuration
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
var body: some View {
|
|
21
|
+
TimelineView(.animation) { timeline in
|
|
22
|
+
Canvas { context, size in
|
|
23
|
+
let timeNow = timeline.date.timeIntervalSinceReferenceDate
|
|
24
|
+
let angle = (timeNow.remainder(dividingBy: loopDuration) / loopDuration) * 2 * .pi
|
|
25
|
+
|
|
26
|
+
var path = Path()
|
|
27
|
+
let center = CGPoint(x: size.width / 2, y: size.height / 2)
|
|
28
|
+
let radius = min(size.width, size.height) * 0.45
|
|
29
|
+
|
|
30
|
+
// Move points with larger variations using sine for smooth looping
|
|
31
|
+
let adjustedPoints = points.enumerated().map { index, point in
|
|
32
|
+
let phaseOffset = Double(index) * .pi / 3
|
|
33
|
+
let xOffset = sin(angle + phaseOffset) * 0.15
|
|
34
|
+
let yOffset = cos(angle + phaseOffset) * 0.15
|
|
35
|
+
return CGPoint(
|
|
36
|
+
x: (point.x - 0.5 + xOffset) * radius + center.x,
|
|
37
|
+
y: (point.y - 0.5 + yOffset) * radius + center.y
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Start the path
|
|
42
|
+
path.move(to: adjustedPoints[0])
|
|
43
|
+
|
|
44
|
+
// Create smooth curves between points
|
|
45
|
+
for i in 0 ..< adjustedPoints.count {
|
|
46
|
+
let next = (i + 1) % adjustedPoints.count
|
|
47
|
+
|
|
48
|
+
// Calculate the angle between points
|
|
49
|
+
let currentAngle = atan2(
|
|
50
|
+
adjustedPoints[i].y - center.y,
|
|
51
|
+
adjustedPoints[i].x - center.x
|
|
52
|
+
)
|
|
53
|
+
let nextAngle = atan2(
|
|
54
|
+
adjustedPoints[next].y - center.y,
|
|
55
|
+
adjustedPoints[next].x - center.x
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
// Create perpendicular handles
|
|
59
|
+
let handleLength = radius * 0.33
|
|
60
|
+
|
|
61
|
+
let control1 = CGPoint(
|
|
62
|
+
x: adjustedPoints[i].x + cos(currentAngle + .pi / 2) * handleLength,
|
|
63
|
+
y: adjustedPoints[i].y + sin(currentAngle + .pi / 2) * handleLength
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
let control2 = CGPoint(
|
|
67
|
+
x: adjustedPoints[next].x + cos(nextAngle - .pi / 2) * handleLength,
|
|
68
|
+
y: adjustedPoints[next].y + sin(nextAngle - .pi / 2) * handleLength
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
path.addCurve(
|
|
72
|
+
to: adjustedPoints[next],
|
|
73
|
+
control1: control1,
|
|
74
|
+
control2: control2
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
context.fill(path, with: .color(color))
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
.animation(.spring(), value: points)
|
|
82
|
+
}
|
|
83
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "expo-orb",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Animated orb component for React Native (iOS, Android, Web)",
|
|
5
|
+
"main": "build/index.js",
|
|
6
|
+
"types": "build/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "expo-module build",
|
|
9
|
+
"clean": "expo-module clean",
|
|
10
|
+
"lint": "expo-module lint",
|
|
11
|
+
"test": "expo-module test",
|
|
12
|
+
"prepare": "expo-module prepare",
|
|
13
|
+
"prepublishOnly": "expo-module prepublishOnly",
|
|
14
|
+
"expo-module": "expo-module",
|
|
15
|
+
"open:ios": "xed example/ios",
|
|
16
|
+
"open:android": "open -a \"Android Studio\" example/android"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"react-native",
|
|
20
|
+
"expo",
|
|
21
|
+
"expo-orb",
|
|
22
|
+
"orb",
|
|
23
|
+
"animation",
|
|
24
|
+
"ios",
|
|
25
|
+
"android"
|
|
26
|
+
],
|
|
27
|
+
"repository": "https://github.com/enso-works/expo-orb",
|
|
28
|
+
"bugs": {
|
|
29
|
+
"url": "https://github.com/enso-works/expo-orb/issues"
|
|
30
|
+
},
|
|
31
|
+
"author": "Ensar Bavrk <ensar.bavrk@gmail.com> (https://github.com/enso-works)",
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"homepage": "https://github.com/enso-works/expo-orb#readme",
|
|
34
|
+
"dependencies": {},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/react": "~19.1.0",
|
|
37
|
+
"expo-module-scripts": "^5.0.8",
|
|
38
|
+
"expo": "^54.0.27",
|
|
39
|
+
"react-native": "0.81.5"
|
|
40
|
+
},
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"expo": "*",
|
|
43
|
+
"react": "*",
|
|
44
|
+
"react-native": "*"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ColorValue, StyleProp, ViewStyle } from 'react-native';
|
|
2
|
+
|
|
3
|
+
export type ExpoOrbViewProps = {
|
|
4
|
+
backgroundColors?: ColorValue[];
|
|
5
|
+
glowColor?: ColorValue;
|
|
6
|
+
particleColor?: ColorValue;
|
|
7
|
+
coreGlowIntensity?: number;
|
|
8
|
+
breathingIntensity?: number;
|
|
9
|
+
breathingSpeed?: number;
|
|
10
|
+
showBackground?: boolean;
|
|
11
|
+
showWavyBlobs?: boolean;
|
|
12
|
+
showParticles?: boolean;
|
|
13
|
+
showGlowEffects?: boolean;
|
|
14
|
+
showShadow?: boolean;
|
|
15
|
+
speed?: number;
|
|
16
|
+
style?: StyleProp<ViewStyle>;
|
|
17
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { NativeModule, requireNativeModule } from 'expo';
|
|
2
|
+
|
|
3
|
+
declare class ExpoOrbModule extends NativeModule {
|
|
4
|
+
/**
|
|
5
|
+
* Set the activity level (0-1) for the orb animation.
|
|
6
|
+
* Uses a native function instead of a prop to bypass React's reconciliation
|
|
7
|
+
* and prevent view re-renders that cause animation flickering.
|
|
8
|
+
*/
|
|
9
|
+
setActivity(activity: number): void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const module = requireNativeModule<ExpoOrbModule>('ExpoOrb');
|
|
13
|
+
|
|
14
|
+
export default module;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Set the orb activity level directly via native function.
|
|
18
|
+
* This bypasses React's prop system to prevent animation interference.
|
|
19
|
+
*/
|
|
20
|
+
export function setOrbActivity(activity: number): void {
|
|
21
|
+
module.setActivity(activity);
|
|
22
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { requireNativeView } from 'expo';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
|
|
4
|
+
import { ExpoOrbViewProps } from './ExpoOrb.types';
|
|
5
|
+
|
|
6
|
+
const NativeView: React.ComponentType<ExpoOrbViewProps> =
|
|
7
|
+
requireNativeView('ExpoOrb');
|
|
8
|
+
|
|
9
|
+
export default function ExpoOrbView(props: ExpoOrbViewProps) {
|
|
10
|
+
return <NativeView {...props} />;
|
|
11
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
import { ExpoOrbViewProps } from './ExpoOrb.types';
|
|
4
|
+
|
|
5
|
+
const defaultColors = ['#22c55e', '#3b82f6', '#ec4899'];
|
|
6
|
+
|
|
7
|
+
export default function ExpoOrbView(props: ExpoOrbViewProps) {
|
|
8
|
+
const colors = Array.isArray(props.backgroundColors) &&
|
|
9
|
+
props.backgroundColors.every((color) => typeof color === 'string')
|
|
10
|
+
? (props.backgroundColors as string[])
|
|
11
|
+
: defaultColors;
|
|
12
|
+
|
|
13
|
+
const gradient = `radial-gradient(circle at 30% 30%, ${colors[0]}, ${colors[1] ?? colors[0]}, ${colors[2] ?? colors[1] ?? colors[0]})`;
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<div
|
|
17
|
+
style={{
|
|
18
|
+
width: '100%',
|
|
19
|
+
height: '100%',
|
|
20
|
+
borderRadius: '9999px',
|
|
21
|
+
background: gradient,
|
|
22
|
+
boxShadow: '0 0 30px rgba(255,255,255,0.2)',
|
|
23
|
+
}}
|
|
24
|
+
/>
|
|
25
|
+
);
|
|
26
|
+
}
|
package/src/index.ts
ADDED
package/tsconfig.json
ADDED