stream-chat-react-native-core 9.10.2-beta.4 → 9.11.0-beta.1

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 (183) hide show
  1. package/lib/commonjs/ai-components/README.md +50 -2
  2. package/lib/commonjs/ai-components/index.js +24 -0
  3. package/lib/commonjs/ai-components/index.js.map +1 -1
  4. package/lib/commonjs/ai-components/message-composer/ComposerView.js +4 -2
  5. package/lib/commonjs/ai-components/message-composer/ComposerView.js.map +1 -1
  6. package/lib/commonjs/ai-components/message-composer/SpeechToTextButton.js +4 -1
  7. package/lib/commonjs/ai-components/message-composer/SpeechToTextButton.js.map +1 -1
  8. package/lib/commonjs/ai-components/native/AIDictation.js +6 -0
  9. package/lib/commonjs/ai-components/native/AIDictation.js.map +1 -1
  10. package/lib/commonjs/ai-components/store/dictation/store.js +4 -2
  11. package/lib/commonjs/ai-components/store/dictation/store.js.map +1 -1
  12. package/lib/commonjs/ai-components/store/dictation/useDictationState.js +4 -2
  13. package/lib/commonjs/ai-components/store/dictation/useDictationState.js.map +1 -1
  14. package/lib/commonjs/ai-components/store/index.js +22 -0
  15. package/lib/commonjs/ai-components/store/index.js.map +1 -1
  16. package/lib/commonjs/ai-components/theme.js +3 -0
  17. package/lib/commonjs/ai-components/theme.js.map +1 -1
  18. package/lib/commonjs/ai-components/transcription/useDictation.js +7 -3
  19. package/lib/commonjs/ai-components/transcription/useDictation.js.map +1 -1
  20. package/lib/commonjs/ai-components/transcription/useDictationAudioLevel.js +32 -0
  21. package/lib/commonjs/ai-components/transcription/useDictationAudioLevel.js.map +1 -0
  22. package/lib/commonjs/ai-components/voice-visualization/VoiceGlow.js +79 -0
  23. package/lib/commonjs/ai-components/voice-visualization/VoiceGlow.js.map +1 -0
  24. package/lib/commonjs/ai-components/voice-visualization/VoiceVisualizationView.js +147 -0
  25. package/lib/commonjs/ai-components/voice-visualization/VoiceVisualizationView.js.map +1 -0
  26. package/lib/commonjs/ai-components/voice-visualization/VoiceWave.js +58 -0
  27. package/lib/commonjs/ai-components/voice-visualization/VoiceWave.js.map +1 -0
  28. package/lib/commonjs/ai-components/voice-visualization/bounce.js +60 -0
  29. package/lib/commonjs/ai-components/voice-visualization/bounce.js.map +1 -0
  30. package/lib/commonjs/ai-components/voice-visualization/driver.js +208 -0
  31. package/lib/commonjs/ai-components/voice-visualization/driver.js.map +1 -0
  32. package/lib/commonjs/ai-components/voice-visualization/glowShader.js +218 -0
  33. package/lib/commonjs/ai-components/voice-visualization/glowShader.js.map +1 -0
  34. package/lib/commonjs/ai-components/voice-visualization/index.js +18 -0
  35. package/lib/commonjs/ai-components/voice-visualization/index.js.map +1 -0
  36. package/lib/commonjs/ai-components/voice-visualization/math.js +70 -0
  37. package/lib/commonjs/ai-components/voice-visualization/math.js.map +1 -0
  38. package/lib/commonjs/ai-components/voice-visualization/palette.js +47 -0
  39. package/lib/commonjs/ai-components/voice-visualization/palette.js.map +1 -0
  40. package/lib/commonjs/ai-components/voice-visualization/simplex.js +77 -0
  41. package/lib/commonjs/ai-components/voice-visualization/simplex.js.map +1 -0
  42. package/lib/commonjs/ai-components/voice-visualization/styles.js +33 -0
  43. package/lib/commonjs/ai-components/voice-visualization/styles.js.map +1 -0
  44. package/lib/commonjs/ai-components/voice-visualization/types.js +4 -0
  45. package/lib/commonjs/ai-components/voice-visualization/types.js.map +1 -0
  46. package/lib/commonjs/ai-components/voice-visualization/useReduceMotion.js +28 -0
  47. package/lib/commonjs/ai-components/voice-visualization/useReduceMotion.js.map +1 -0
  48. package/lib/commonjs/ai-components/voice-visualization/useVoiceVisualization.js +129 -0
  49. package/lib/commonjs/ai-components/voice-visualization/useVoiceVisualization.js.map +1 -0
  50. package/lib/commonjs/ai-components/voice-visualization/waveGeometry.js +124 -0
  51. package/lib/commonjs/ai-components/voice-visualization/waveGeometry.js.map +1 -0
  52. package/lib/commonjs/version.json +1 -1
  53. package/lib/module/ai-components/README.md +50 -2
  54. package/lib/module/ai-components/index.js +24 -0
  55. package/lib/module/ai-components/index.js.map +1 -1
  56. package/lib/module/ai-components/message-composer/ComposerView.js +4 -2
  57. package/lib/module/ai-components/message-composer/ComposerView.js.map +1 -1
  58. package/lib/module/ai-components/message-composer/SpeechToTextButton.js +4 -1
  59. package/lib/module/ai-components/message-composer/SpeechToTextButton.js.map +1 -1
  60. package/lib/module/ai-components/native/AIDictation.js +6 -0
  61. package/lib/module/ai-components/native/AIDictation.js.map +1 -1
  62. package/lib/module/ai-components/store/dictation/store.js +4 -2
  63. package/lib/module/ai-components/store/dictation/store.js.map +1 -1
  64. package/lib/module/ai-components/store/dictation/useDictationState.js +4 -2
  65. package/lib/module/ai-components/store/dictation/useDictationState.js.map +1 -1
  66. package/lib/module/ai-components/store/index.js +22 -0
  67. package/lib/module/ai-components/store/index.js.map +1 -1
  68. package/lib/module/ai-components/theme.js +3 -0
  69. package/lib/module/ai-components/theme.js.map +1 -1
  70. package/lib/module/ai-components/transcription/useDictation.js +7 -3
  71. package/lib/module/ai-components/transcription/useDictation.js.map +1 -1
  72. package/lib/module/ai-components/transcription/useDictationAudioLevel.js +32 -0
  73. package/lib/module/ai-components/transcription/useDictationAudioLevel.js.map +1 -0
  74. package/lib/module/ai-components/voice-visualization/VoiceGlow.js +79 -0
  75. package/lib/module/ai-components/voice-visualization/VoiceGlow.js.map +1 -0
  76. package/lib/module/ai-components/voice-visualization/VoiceVisualizationView.js +147 -0
  77. package/lib/module/ai-components/voice-visualization/VoiceVisualizationView.js.map +1 -0
  78. package/lib/module/ai-components/voice-visualization/VoiceWave.js +58 -0
  79. package/lib/module/ai-components/voice-visualization/VoiceWave.js.map +1 -0
  80. package/lib/module/ai-components/voice-visualization/bounce.js +60 -0
  81. package/lib/module/ai-components/voice-visualization/bounce.js.map +1 -0
  82. package/lib/module/ai-components/voice-visualization/driver.js +208 -0
  83. package/lib/module/ai-components/voice-visualization/driver.js.map +1 -0
  84. package/lib/module/ai-components/voice-visualization/glowShader.js +218 -0
  85. package/lib/module/ai-components/voice-visualization/glowShader.js.map +1 -0
  86. package/lib/module/ai-components/voice-visualization/index.js +18 -0
  87. package/lib/module/ai-components/voice-visualization/index.js.map +1 -0
  88. package/lib/module/ai-components/voice-visualization/math.js +70 -0
  89. package/lib/module/ai-components/voice-visualization/math.js.map +1 -0
  90. package/lib/module/ai-components/voice-visualization/palette.js +47 -0
  91. package/lib/module/ai-components/voice-visualization/palette.js.map +1 -0
  92. package/lib/module/ai-components/voice-visualization/simplex.js +77 -0
  93. package/lib/module/ai-components/voice-visualization/simplex.js.map +1 -0
  94. package/lib/module/ai-components/voice-visualization/styles.js +33 -0
  95. package/lib/module/ai-components/voice-visualization/styles.js.map +1 -0
  96. package/lib/module/ai-components/voice-visualization/types.js +4 -0
  97. package/lib/module/ai-components/voice-visualization/types.js.map +1 -0
  98. package/lib/module/ai-components/voice-visualization/useReduceMotion.js +28 -0
  99. package/lib/module/ai-components/voice-visualization/useReduceMotion.js.map +1 -0
  100. package/lib/module/ai-components/voice-visualization/useVoiceVisualization.js +129 -0
  101. package/lib/module/ai-components/voice-visualization/useVoiceVisualization.js.map +1 -0
  102. package/lib/module/ai-components/voice-visualization/waveGeometry.js +124 -0
  103. package/lib/module/ai-components/voice-visualization/waveGeometry.js.map +1 -0
  104. package/lib/module/version.json +1 -1
  105. package/lib/typescript/ai-components/index.d.ts +3 -1
  106. package/lib/typescript/ai-components/index.d.ts.map +1 -1
  107. package/lib/typescript/ai-components/message-composer/ComposerView.d.ts.map +1 -1
  108. package/lib/typescript/ai-components/native/AIDictation.d.ts +6 -0
  109. package/lib/typescript/ai-components/native/AIDictation.d.ts.map +1 -1
  110. package/lib/typescript/ai-components/store/dictation/store.d.ts +7 -0
  111. package/lib/typescript/ai-components/store/dictation/store.d.ts.map +1 -1
  112. package/lib/typescript/ai-components/store/dictation/useDictationState.d.ts +1 -0
  113. package/lib/typescript/ai-components/store/dictation/useDictationState.d.ts.map +1 -1
  114. package/lib/typescript/ai-components/store/index.d.ts +2 -0
  115. package/lib/typescript/ai-components/store/index.d.ts.map +1 -1
  116. package/lib/typescript/ai-components/theme.d.ts +13 -0
  117. package/lib/typescript/ai-components/theme.d.ts.map +1 -1
  118. package/lib/typescript/ai-components/transcription/useDictation.d.ts +9 -1
  119. package/lib/typescript/ai-components/transcription/useDictation.d.ts.map +1 -1
  120. package/lib/typescript/ai-components/transcription/useDictationAudioLevel.d.ts +11 -0
  121. package/lib/typescript/ai-components/transcription/useDictationAudioLevel.d.ts.map +1 -0
  122. package/lib/typescript/ai-components/voice-visualization/VoiceGlow.d.ts +13 -0
  123. package/lib/typescript/ai-components/voice-visualization/VoiceGlow.d.ts.map +1 -0
  124. package/lib/typescript/ai-components/voice-visualization/VoiceVisualizationView.d.ts +26 -0
  125. package/lib/typescript/ai-components/voice-visualization/VoiceVisualizationView.d.ts.map +1 -0
  126. package/lib/typescript/ai-components/voice-visualization/VoiceWave.d.ts +13 -0
  127. package/lib/typescript/ai-components/voice-visualization/VoiceWave.d.ts.map +1 -0
  128. package/lib/typescript/ai-components/voice-visualization/bounce.d.ts +20 -0
  129. package/lib/typescript/ai-components/voice-visualization/bounce.d.ts.map +1 -0
  130. package/lib/typescript/ai-components/voice-visualization/driver.d.ts +96 -0
  131. package/lib/typescript/ai-components/voice-visualization/driver.d.ts.map +1 -0
  132. package/lib/typescript/ai-components/voice-visualization/glowShader.d.ts +7 -0
  133. package/lib/typescript/ai-components/voice-visualization/glowShader.d.ts.map +1 -0
  134. package/lib/typescript/ai-components/voice-visualization/index.d.ts +4 -0
  135. package/lib/typescript/ai-components/voice-visualization/index.d.ts.map +1 -0
  136. package/lib/typescript/ai-components/voice-visualization/math.d.ts +26 -0
  137. package/lib/typescript/ai-components/voice-visualization/math.d.ts.map +1 -0
  138. package/lib/typescript/ai-components/voice-visualization/palette.d.ts +18 -0
  139. package/lib/typescript/ai-components/voice-visualization/palette.d.ts.map +1 -0
  140. package/lib/typescript/ai-components/voice-visualization/simplex.d.ts +11 -0
  141. package/lib/typescript/ai-components/voice-visualization/simplex.d.ts.map +1 -0
  142. package/lib/typescript/ai-components/voice-visualization/styles.d.ts +24 -0
  143. package/lib/typescript/ai-components/voice-visualization/styles.d.ts.map +1 -0
  144. package/lib/typescript/ai-components/voice-visualization/types.d.ts +45 -0
  145. package/lib/typescript/ai-components/voice-visualization/types.d.ts.map +1 -0
  146. package/lib/typescript/ai-components/voice-visualization/useReduceMotion.d.ts +10 -0
  147. package/lib/typescript/ai-components/voice-visualization/useReduceMotion.d.ts.map +1 -0
  148. package/lib/typescript/ai-components/voice-visualization/useVoiceVisualization.d.ts +18 -0
  149. package/lib/typescript/ai-components/voice-visualization/useVoiceVisualization.d.ts.map +1 -0
  150. package/lib/typescript/ai-components/voice-visualization/waveGeometry.d.ts +25 -0
  151. package/lib/typescript/ai-components/voice-visualization/waveGeometry.d.ts.map +1 -0
  152. package/package.json +1 -1
  153. package/src/ai-components/README.md +50 -2
  154. package/src/ai-components/__tests__/VoiceVisualization.test.tsx +208 -0
  155. package/src/ai-components/__tests__/dictation.test.tsx +58 -1
  156. package/src/ai-components/__tests__/voiceDriver.test.ts +317 -0
  157. package/src/ai-components/__tests__/voicePalette.test.ts +124 -0
  158. package/src/ai-components/index.ts +3 -1
  159. package/src/ai-components/message-composer/ComposerView.tsx +6 -3
  160. package/src/ai-components/message-composer/SpeechToTextButton.tsx +5 -2
  161. package/src/ai-components/native/AIDictation.ts +11 -0
  162. package/src/ai-components/store/dictation/store.ts +9 -0
  163. package/src/ai-components/store/dictation/useDictationState.ts +2 -1
  164. package/src/ai-components/store/index.ts +2 -0
  165. package/src/ai-components/theme.ts +16 -0
  166. package/src/ai-components/transcription/useDictation.ts +16 -3
  167. package/src/ai-components/transcription/useDictationAudioLevel.ts +38 -0
  168. package/src/ai-components/voice-visualization/VoiceGlow.tsx +79 -0
  169. package/src/ai-components/voice-visualization/VoiceVisualizationView.tsx +179 -0
  170. package/src/ai-components/voice-visualization/VoiceWave.tsx +67 -0
  171. package/src/ai-components/voice-visualization/bounce.ts +61 -0
  172. package/src/ai-components/voice-visualization/driver.ts +240 -0
  173. package/src/ai-components/voice-visualization/glowShader.ts +227 -0
  174. package/src/ai-components/voice-visualization/index.ts +3 -0
  175. package/src/ai-components/voice-visualization/math.ts +73 -0
  176. package/src/ai-components/voice-visualization/palette.ts +59 -0
  177. package/src/ai-components/voice-visualization/simplex.ts +92 -0
  178. package/src/ai-components/voice-visualization/styles.ts +42 -0
  179. package/src/ai-components/voice-visualization/types.ts +44 -0
  180. package/src/ai-components/voice-visualization/useReduceMotion.ts +34 -0
  181. package/src/ai-components/voice-visualization/useVoiceVisualization.ts +153 -0
  182. package/src/ai-components/voice-visualization/waveGeometry.ts +137 -0
  183. package/src/version.json +1 -1
@@ -0,0 +1,59 @@
1
+ import { processColor } from 'react-native';
2
+
3
+ import type { Hsv } from './types';
4
+
5
+ export const MAXIMUM_COLORS = 5;
6
+
7
+ export type Rgb = [number, number, number];
8
+
9
+ /** Used when neither the `colors` prop nor the theme gives a readable color. */
10
+ export const FALLBACK_COLOR = '#005fff';
11
+
12
+ /**
13
+ * Reads any color React Native understands (names, `#rgb[a]`, `#rrggbb[aa]`, `rgb()`, `hsl()`, ...)
14
+ * into sRGB channels from 0 to 1, or null when it cannot be read. Platform colors have no
15
+ * channels to read and count as unreadable.
16
+ */
17
+ export const parseColor = (color: string): Rgb | null => {
18
+ if (typeof color !== 'string' || !color.trim()) return null;
19
+ const processed = processColor(color.trim());
20
+ if (typeof processed !== 'number') return null;
21
+ // 0xAARRGGBB, signed on Android.
22
+ const argb = processed < 0 ? processed + 2 ** 32 : processed;
23
+ const channel = (shift: number) => (Math.floor(argb / 2 ** shift) % 256) / 255;
24
+ return [channel(16), channel(8), channel(0)];
25
+ };
26
+
27
+ /** RGB channels from 0 to 1 as HSV, keeping full saturation and value so muted colors render true. */
28
+ export const rgbToHsv = ([r, g, b]: Rgb): Hsv => {
29
+ const high = Math.max(r, g, b);
30
+ const low = Math.min(r, g, b);
31
+ const delta = high - low;
32
+ let hue = 0;
33
+ if (delta !== 0) {
34
+ if (high === r) hue = ((((g - b) / delta) % 6) + 6) % 6;
35
+ else if (high === g) hue = (b - r) / delta + 2;
36
+ else hue = (r - g) / delta + 4;
37
+ }
38
+ return {
39
+ hue: (((hue / 6) % 1) + 1) % 1,
40
+ saturation: high === 0 ? 0 : Math.min(Math.max(delta / high, 0), 1),
41
+ value: Math.min(Math.max(high, 0), 1),
42
+ };
43
+ };
44
+
45
+ /** Colors as the HSV the renderers read, at most five; colors that cannot be read are skipped. */
46
+ export const toHsvPalette = (colors: readonly string[]): Hsv[] =>
47
+ colors
48
+ .map(parseColor)
49
+ .filter((rgb): rgb is Rgb => rgb !== null)
50
+ .slice(0, MAXIMUM_COLORS)
51
+ .map(rgbToHsv);
52
+
53
+ /** Relative luminance of a color, or null when it cannot be read. */
54
+ export const luminance = (color: string) => {
55
+ const rgb = parseColor(color);
56
+ if (!rgb) return null;
57
+ const [r, g, b] = rgb.map((c) => (c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4)));
58
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b;
59
+ };
@@ -0,0 +1,92 @@
1
+ /**
2
+ * 3-D simplex noise, a port of the glow shader's `snoise` (Ashima Arts / Stefan Gustavson), so the
3
+ * wave drawn on the CPU moves with the same speech rhythm as the shader, as on iOS and the web.
4
+ *
5
+ * It runs in float32 like a GPU, with `mod` as a multiply by a float32 reciprocal, because the
6
+ * hash is sensitive to rounding: in exact arithmetic some inputs hash to a neighbouring gradient.
7
+ * Every function is a worklet, as the motion runs on the UI thread.
8
+ */
9
+
10
+ const INV_289 = Math.fround(1 / 289);
11
+ const ONE_THIRD = Math.fround(1 / 3);
12
+ const ONE_SIXTH = Math.fround(1 / 6);
13
+ const NS_X = Math.fround(2 / 7);
14
+ const NS_Y = Math.fround(Math.fround(0.5 * Math.fround(1 / 7)) - 1);
15
+ const NS_Z = Math.fround(1 / 7);
16
+
17
+ const mod289 = (x: number) => {
18
+ 'worklet';
19
+ const f = Math.fround;
20
+ return f(x - f(289 * Math.floor(f(x * INV_289))));
21
+ };
22
+
23
+ const permute = (x: number) => {
24
+ 'worklet';
25
+ const f = Math.fround;
26
+ return mod289(f(f(f(x * 34) + 1) * x));
27
+ };
28
+
29
+ const taylorInvSqrt = (r: number) => {
30
+ 'worklet';
31
+ const f = Math.fround;
32
+ return f(1.79284291400159 - f(0.85373472095314 * r));
33
+ };
34
+
35
+ /** A corner's contribution, its gradient from its hash as the shader's x_/y_/h/sh steps compute it. */
36
+ const corner = (p: number, x: number, y: number, z: number) => {
37
+ 'worklet';
38
+ const f = Math.fround;
39
+ const j = f(p - f(49 * Math.floor(f(f(p * NS_Z) * NS_Z))));
40
+ const xi = Math.floor(f(j * NS_Z));
41
+ const yi = Math.floor(f(j - f(7 * xi)));
42
+ const gx0 = f(f(xi * NS_X) + NS_Y);
43
+ const gy0 = f(f(yi * NS_X) + NS_Y);
44
+ const gz = f(f(1 - Math.abs(gx0)) - Math.abs(gy0));
45
+ const sh = gz <= 0 ? -1 : 0;
46
+ const gx = f(gx0 + f((Math.floor(gx0) * 2 + 1) * sh));
47
+ const gy = f(gy0 + f((Math.floor(gy0) * 2 + 1) * sh));
48
+ const norm = taylorInvSqrt(gx * gx + gy * gy + gz * gz);
49
+ const m = Math.max(0.6 - (x * x + y * y + z * z), 0);
50
+ return m * m * m * m * (gx * norm * x + gy * norm * y + gz * norm * z);
51
+ };
52
+
53
+ /** Simplex noise at (x, y, z), from about -1 to 1. */
54
+ export const snoise = (inX: number, inY: number, inZ: number) => {
55
+ 'worklet';
56
+ const f = Math.fround;
57
+ const vx = f(inX);
58
+ const vy = f(inY);
59
+ const vz = f(inZ);
60
+ const s = f(f(vx + vy + vz) * ONE_THIRD);
61
+ const ix = Math.floor(f(vx + s));
62
+ const iy = Math.floor(f(vy + s));
63
+ const iz = Math.floor(f(vz + s));
64
+ const t = f(f(ix + iy + iz) * ONE_SIXTH);
65
+ const x0 = f(f(vx - ix) + t);
66
+ const y0 = f(f(vy - iy) + t);
67
+ const z0 = f(f(vz - iz) + t);
68
+
69
+ const gx = x0 >= y0 ? 1 : 0;
70
+ const gy = y0 >= z0 ? 1 : 0;
71
+ const gz = z0 >= x0 ? 1 : 0;
72
+ const i1x = Math.min(gx, 1 - gz);
73
+ const i1y = Math.min(gy, 1 - gx);
74
+ const i1z = Math.min(gz, 1 - gy);
75
+ const i2x = Math.max(gx, 1 - gz);
76
+ const i2y = Math.max(gy, 1 - gx);
77
+ const i2z = Math.max(gz, 1 - gy);
78
+
79
+ const mx = mod289(ix);
80
+ const my = mod289(iy);
81
+ const mz = mod289(iz);
82
+ const hash = (dx: number, dy: number, dz: number) =>
83
+ permute(f(f(permute(f(f(permute(f(mz + dz)) + my) + dy)) + mx) + dx));
84
+
85
+ return (
86
+ 42 *
87
+ (corner(hash(0, 0, 0), x0, y0, z0) +
88
+ corner(hash(i1x, i1y, i1z), x0 - i1x + 1 / 6, y0 - i1y + 1 / 6, z0 - i1z + 1 / 6) +
89
+ corner(hash(i2x, i2y, i2z), x0 - i2x + 1 / 3, y0 - i2y + 1 / 3, z0 - i2z + 1 / 3) +
90
+ corner(hash(1, 1, 1), x0 - 0.5, y0 - 0.5, z0 - 0.5))
91
+ );
92
+ };
@@ -0,0 +1,42 @@
1
+ import { LEVEL_RESPONSE, type LevelResponse } from './driver';
2
+
3
+ /**
4
+ * What tells the glow and the wave apart besides how they draw, as on iOS.
5
+ *
6
+ * - `levelResponse`: the wave's height tracks the level directly, so it follows it smoothly;
7
+ * per-syllable jumps would make it jitter. The glow follows it closely.
8
+ * - `maxScale`: the densest it renders. The glow is soft all over, so 2x is enough; the wave's
9
+ * thin line is drawn at the screen's own scale, up to 3x, to stay crisp.
10
+ * - `contains`: whether a point, centred and normalized to the shorter side with y up, is on the
11
+ * shape rather than the empty space around it.
12
+ */
13
+ export type VoiceVisualizationStyle = {
14
+ contains: (x: number, y: number) => boolean;
15
+ /** The shape's bounds as a share of the shorter side, for the area that takes touches. */
16
+ hitArea: { width: number; height: number };
17
+ levelResponse: LevelResponse;
18
+ maxScale: number;
19
+ };
20
+
21
+ /** How far from the centre a tap still lands on the globe, as a share of the shorter side. */
22
+ const GLOBE_REACH = 0.32;
23
+
24
+ export const GLOW_STYLE: VoiceVisualizationStyle = {
25
+ contains: (x, y) => {
26
+ 'worklet';
27
+ return Math.hypot(x, y) < GLOBE_REACH;
28
+ },
29
+ hitArea: { height: 2 * GLOBE_REACH, width: 2 * GLOBE_REACH },
30
+ levelResponse: LEVEL_RESPONSE.steady,
31
+ maxScale: 2,
32
+ };
33
+
34
+ export const WAVE_STYLE: VoiceVisualizationStyle = {
35
+ contains: (x, y) => {
36
+ 'worklet';
37
+ return Math.abs(x) < 0.5 && Math.abs(y) < 0.14;
38
+ },
39
+ hitArea: { height: 0.28, width: 1 },
40
+ levelResponse: LEVEL_RESPONSE.smooth,
41
+ maxScale: 3,
42
+ };
@@ -0,0 +1,44 @@
1
+ import type { ViewProps } from 'react-native';
2
+
3
+ import type { SharedValue } from 'react-native-reanimated';
4
+
5
+ /**
6
+ * What a voice agent is doing, which a voice visualization shows as motion.
7
+ *
8
+ * The states differ only in how a visualization moves, never in its colors, and changing the
9
+ * state eases from one motion into the next.
10
+ *
11
+ * - `idle`: waiting to be spoken to, a calm, barely-there shimmer.
12
+ * - `connecting`: not ready yet, a loader sweep with a pulsing breath.
13
+ * - `listening`: hearing somebody, a gentle expand and contract.
14
+ * - `thinking`: working on an answer, a slow turn or a travelling wave.
15
+ * - `speaking`: talking, strong motion that ebbs and pauses like speech.
16
+ */
17
+ export type VoiceAgentState = 'idle' | 'connecting' | 'listening' | 'thinking' | 'speaking';
18
+
19
+ export type VoiceVisualizationProps = Omit<ViewProps, 'children'> & {
20
+ /** What the agent is doing, which sets how the visualization moves. */
21
+ state: VoiceAgentState;
22
+ /**
23
+ * The level of whoever is talking, from 0 to 1, read once a frame on the UI thread. Feed it at
24
+ * 15 Hz or more for the visualization to move with the words; `null` animates speech on its own.
25
+ * See `useDictationAudioLevel` for the level of the microphone while dictating.
26
+ */
27
+ audioLevel?: SharedValue<number | null>;
28
+ /**
29
+ * One to five colors. Past five are ignored, and colors that cannot be read are skipped. When
30
+ * omitted or empty, the theme's `voiceVisualization.colors` are used.
31
+ */
32
+ colors?: string[];
33
+ /** From 0 to 2: how far and how fast it moves. Colors never change with it. Defaults to 1. */
34
+ expressivity?: number;
35
+ /**
36
+ * What screen readers read as the value for each state, for example to translate them. Defaults
37
+ * to "Ready", "Connecting", "Listening", "Thinking" and "Speaking". The label, "Voice agent", is
38
+ * `accessibilityLabel`.
39
+ */
40
+ stateLabels?: Partial<Record<VoiceAgentState, string>>;
41
+ };
42
+
43
+ /** A color as hue (a fraction of a turn), saturation and value, each from 0 to 1. */
44
+ export type Hsv = { hue: number; saturation: number; value: number };
@@ -0,0 +1,34 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { AccessibilityInfo } from 'react-native';
3
+
4
+ /**
5
+ * Whether the system asks for less motion: Reduce Motion on iOS, animations off on Android. It
6
+ * follows the setting while mounted.
7
+ *
8
+ * The AI entry point cannot reach the core SDK's own hook, which also only applies once its
9
+ * accessibility layer is enabled; the voice visualizations always honour the setting, as on iOS
10
+ * and Android.
11
+ */
12
+ export const useReduceMotion = () => {
13
+ const [reduceMotion, setReduceMotion] = useState(false);
14
+
15
+ useEffect(() => {
16
+ let mounted = true;
17
+ AccessibilityInfo.isReduceMotionEnabled?.()
18
+ .then((enabled) => {
19
+ // It starts off; only turning it on needs a render.
20
+ if (mounted && enabled) setReduceMotion(true);
21
+ })
22
+ .catch(() => undefined);
23
+ const subscription = AccessibilityInfo.addEventListener?.(
24
+ 'reduceMotionChanged',
25
+ setReduceMotion,
26
+ );
27
+ return () => {
28
+ mounted = false;
29
+ subscription?.remove();
30
+ };
31
+ }, []);
32
+
33
+ return reduceMotion;
34
+ };
@@ -0,0 +1,153 @@
1
+ import { useCallback, useEffect, useMemo, useState } from 'react';
2
+ import { useColorScheme } from 'react-native';
3
+
4
+ import {
5
+ type FrameInfo,
6
+ runOnUI,
7
+ useFrameCallback,
8
+ useSharedValue,
9
+ withTiming,
10
+ } from 'react-native-reanimated';
11
+
12
+ import {
13
+ advanceDriver,
14
+ createDriverState,
15
+ hoverDriver,
16
+ tapDriver,
17
+ type VoiceDriverInput,
18
+ } from './driver';
19
+ import { audioLevelInput, expressivityInput } from './math';
20
+ import { FALLBACK_COLOR, luminance, toHsvPalette } from './palette';
21
+ import type { VoiceVisualizationStyle } from './styles';
22
+ import type { VoiceVisualizationProps } from './types';
23
+ import { useReduceMotion } from './useReduceMotion';
24
+
25
+ import { useTheme } from '../contexts';
26
+
27
+ /** How long the visualization takes to fade in once its first frame is drawn, as on iOS. */
28
+ const FADE_IN_MS = 300;
29
+
30
+ /** Whether to draw for a dark background: the theme's backdrop when readable, else the scheme. */
31
+ const useDark = (backdropColor: string | undefined) => {
32
+ const scheme = useColorScheme();
33
+ return useMemo(() => {
34
+ const backdrop = backdropColor ? luminance(backdropColor) : null;
35
+ if (backdrop !== null) return backdrop < 0.5 ? 1 : 0;
36
+ return scheme === 'dark' ? 1 : 0;
37
+ }, [backdropColor, scheme]);
38
+ };
39
+
40
+ /**
41
+ * A voice visualization's motion, advanced once a frame on the UI thread.
42
+ *
43
+ * The driver lives in a shared value the renderers derive from, so a frame redraws without
44
+ * re-rendering React, and `audioLevel` is read in the frame callback, so a level changing many
45
+ * times a second re-renders nothing either.
46
+ */
47
+ export const useVoiceVisualization = (
48
+ { audioLevel, colors, expressivity, state }: VoiceVisualizationProps,
49
+ style: VoiceVisualizationStyle,
50
+ ) => {
51
+ const { theme } = useTheme();
52
+ const reduceMotion = useReduceMotion();
53
+ const themeColors = theme.voiceVisualization?.colors;
54
+ const dark = useDark(theme.voiceVisualization?.backdropColor);
55
+
56
+ const colorsKey = (colors ?? []).join('|');
57
+ const themeKey = (themeColors ?? []).join('|');
58
+ const palette = useMemo(() => {
59
+ const own = toHsvPalette(colors ?? []);
60
+ if (own.length) return own;
61
+ const fromTheme = toHsvPalette(themeColors ?? []);
62
+ return fromTheme.length ? fromTheme : toHsvPalette([FALLBACK_COLOR]);
63
+ // Keyed by the colors themselves, so a new array of the same colors changes nothing.
64
+ // eslint-disable-next-line react-hooks/exhaustive-deps
65
+ }, [colorsKey, themeKey]);
66
+
67
+ const input: VoiceDriverInput = {
68
+ audioLevel: null,
69
+ colors: palette,
70
+ expressivity: expressivityInput(expressivity),
71
+ reduceMotion,
72
+ state,
73
+ };
74
+
75
+ // Starts settled on the first input, so the first frame shows it rather than easing into it.
76
+ const [initialDriver] = useState(() => createDriverState(input, style.levelResponse));
77
+ const driver = useSharedValue(initialDriver);
78
+ const inputValue = useSharedValue(input);
79
+ const darkValue = useSharedValue(dark);
80
+ const opacity = useSharedValue(0);
81
+ const fadingIn = useSharedValue(false);
82
+
83
+ useEffect(() => {
84
+ inputValue.value = {
85
+ audioLevel: null,
86
+ colors: palette,
87
+ expressivity: expressivityInput(expressivity),
88
+ reduceMotion,
89
+ state,
90
+ };
91
+ }, [expressivity, inputValue, palette, reduceMotion, state]);
92
+
93
+ useEffect(() => {
94
+ darkValue.value = dark;
95
+ }, [dark, darkValue]);
96
+
97
+ const onFrame = useCallback(
98
+ (frame: FrameInfo) => {
99
+ 'worklet';
100
+ const seconds = (frame.timeSincePreviousFrame ?? 0) / 1000;
101
+ const current = inputValue.value;
102
+ const level = audioLevelInput(audioLevel ? audioLevel.value : null);
103
+ const next = level === current.audioLevel ? current : { ...current, audioLevel: level };
104
+ driver.modify((d) => {
105
+ 'worklet';
106
+ advanceDriver(d, next, seconds);
107
+ return d;
108
+ });
109
+ // It fades in once it has something to show, rather than flashing empty while the first
110
+ // frame lands.
111
+ if (!fadingIn.value) {
112
+ fadingIn.value = true;
113
+ opacity.value = withTiming(1, { duration: FADE_IN_MS });
114
+ }
115
+ },
116
+ [audioLevel, driver, fadingIn, inputValue, opacity],
117
+ );
118
+ useFrameCallback(onFrame);
119
+
120
+ /** A tap at a point centred and normalized to the shorter side, y up. */
121
+ const tap = useCallback(
122
+ (x: number, y: number) => {
123
+ runOnUI((tapX: number, tapY: number) => {
124
+ 'worklet';
125
+ driver.modify((d) => {
126
+ 'worklet';
127
+ tapDriver(d, inputValue.value, tapX, tapY);
128
+ return d;
129
+ });
130
+ })(x, y);
131
+ },
132
+ [driver, inputValue],
133
+ );
134
+
135
+ /** Where a pointer hovers, or null once it has left. */
136
+ const hover = useCallback(
137
+ (x: number | null, y: number | null) => {
138
+ runOnUI((hoverX: number | null, hoverY: number | null) => {
139
+ 'worklet';
140
+ driver.modify((d) => {
141
+ 'worklet';
142
+ hoverDriver(d, inputValue.value, hoverX, hoverY);
143
+ return d;
144
+ });
145
+ })(x, y);
146
+ },
147
+ [driver, inputValue],
148
+ );
149
+
150
+ return { dark: darkValue, driver, hover, opacity, tap };
151
+ };
152
+
153
+ export type VoiceVisualization = ReturnType<typeof useVoiceVisualization>;
@@ -0,0 +1,137 @@
1
+ import { Skia, type SkPath } from '@shopify/react-native-skia';
2
+
3
+ import { driverReact, driverScale, type VoiceDriverState } from './driver';
4
+ import { clamp, mix, saturate, smoothstep, speechEnv, vivid } from './math';
5
+
6
+ const POINTS = 160;
7
+ export const WAVE_STOPS = 13;
8
+ export const WAVE_START = -0.42;
9
+ export const WAVE_END = 0.42;
10
+
11
+ /** The stops' positions along the gradient, evenly spaced. */
12
+ export const WAVE_POSITIONS = Array.from({ length: WAVE_STOPS }, (_, s) => s / (WAVE_STOPS - 1));
13
+
14
+ const sq = (x: number) => {
15
+ 'worklet';
16
+ return x * x;
17
+ };
18
+
19
+ const bandCenter = (i: number, seg: number, t: number) => {
20
+ 'worklet';
21
+ switch (i) {
22
+ case 0:
23
+ return 0.5 * seg + 0.12 * Math.sin(t * 0.13);
24
+ case 1:
25
+ return 1.5 * seg + 0.13 * Math.sin(t * 0.11 + 2.1);
26
+ case 2:
27
+ return 2.5 * seg + 0.12 * Math.sin(t * 0.17 + 4.2);
28
+ case 3:
29
+ return 3.5 * seg + 0.12 * Math.sin(t * 0.15 + 1);
30
+ default:
31
+ return 4.5 * seg + 0.12 * Math.sin(t * 0.1 + 3.3);
32
+ }
33
+ };
34
+
35
+ /** The string's height above its centre line at each point, centred and normalized, y up. */
36
+ export const waveHeights = (d: VoiceDriverState) => {
37
+ 'worklet';
38
+ const t = d.time;
39
+ const react = driverReact(d);
40
+ const center = d.load * 0.3 * Math.sin(t * 1.3);
41
+ const freq = 24 + 26 * react + 10 * d.flow;
42
+ const speak = smoothstep(0.6, 1, react);
43
+ const drive = mix(speak * 0.26 * speechEnv(t), 0.3 * d.mic, d.voice);
44
+ const reach = 0.018 + 0.022 * react + drive + 0.14 * d.flow + 0.1 * d.load;
45
+ const tapAlive = d.tapAge < 1.5;
46
+ const hovered = d.hoverAmount > 0.001;
47
+ const xs: number[] = [];
48
+ const ys: number[] = [];
49
+ for (let i = 0; i <= POINTS; i++) {
50
+ const x = WAVE_START + ((WAVE_END - WAVE_START) * i) / POINTS;
51
+ let y = Math.exp(-sq((x - center) / 0.18)) * reach * Math.sin(x * freq - t * 5);
52
+ if (tapAlive) {
53
+ y +=
54
+ Math.exp(-sq((x - d.tapX) / 0.12)) *
55
+ Math.sin((x - d.tapX) * 40 - d.tapAge * 24) *
56
+ Math.exp(-d.tapAge * 2.6) *
57
+ 0.05;
58
+ }
59
+ if (hovered) {
60
+ y +=
61
+ Math.exp(-sq((x - d.hoverX) / 0.12)) *
62
+ Math.sin((x - d.hoverX) * 40 - t * 7) *
63
+ d.hoverAmount *
64
+ 0.04;
65
+ }
66
+ xs.push(x);
67
+ ys.push(y);
68
+ }
69
+ return { xs, ys };
70
+ };
71
+
72
+ type LegacyPath = SkPath & {
73
+ lineTo: (x: number, y: number) => void;
74
+ moveTo: (x: number, y: number) => void;
75
+ };
76
+
77
+ /**
78
+ * The wave as a path in a `width` by `height` canvas. Newer Skia versions build immutable paths with
79
+ * `PathBuilder`; earlier 2.x versions mutate `SkPath` itself.
80
+ */
81
+ export const wavePath = (d: VoiceDriverState, width: number, height: number): SkPath => {
82
+ 'worklet';
83
+ const m = Math.min(width, height);
84
+ const scale = driverScale(d);
85
+ const { xs, ys } = waveHeights(d);
86
+ const px = (i: number) => width / 2 + xs[i] * m * scale;
87
+ const py = (i: number) => height / 2 - ys[i] * m * scale;
88
+ if (Skia.PathBuilder) {
89
+ const builder = Skia.PathBuilder.Make();
90
+ builder.moveTo(px(0), py(0));
91
+ for (let i = 1; i < xs.length; i++) builder.lineTo(px(i), py(i));
92
+ return builder.build();
93
+ }
94
+ const path = Skia.Path.Make() as LegacyPath;
95
+ path.moveTo(px(0), py(0));
96
+ for (let i = 1; i < xs.length; i++) path.lineTo(px(i), py(i));
97
+ return path;
98
+ };
99
+
100
+ /**
101
+ * The string's colors at its gradient stops, as RGBA from 0 to 1: up to five bands drifting along
102
+ * it, weighed by how near their centres are, its ends fading out.
103
+ */
104
+ export const waveColors = (d: VoiceDriverState, dark: number) => {
105
+ 'worklet';
106
+ const band: number[][] = [];
107
+ for (let i = 0; i < 5; i++) {
108
+ const rgb = [0, 0, 0];
109
+ vivid(d.colors[i], dark, rgb);
110
+ band.push(rgb);
111
+ }
112
+ const seg = 1 / Math.max(d.count, 1);
113
+ const bandWidth = seg * 0.7;
114
+ const stops: Float32Array[] = [];
115
+ for (let s = 0; s < WAVE_STOPS; s++) {
116
+ const x = WAVE_START + ((WAVE_END - WAVE_START) * s) / (WAVE_STOPS - 1);
117
+ const gx = clamp(x + 0.5, 0, 1);
118
+ const color = [0, 0, 0];
119
+ let total = 1e-4;
120
+ for (let i = 0; i < 5; i++) {
121
+ const present = i === 0 ? 1 : clamp(d.count - i, 0, 1);
122
+ const weight = Math.exp(-sq((gx - bandCenter(i, seg, d.time)) / bandWidth)) * present;
123
+ for (let j = 0; j < 3; j++) color[j] += band[i][j] * weight;
124
+ total += weight;
125
+ }
126
+ for (let j = 0; j < 3; j++) color[j] /= total;
127
+ saturate(color, 1.2);
128
+ saturate(color, d.sat);
129
+ const alpha = clamp(smoothstep(0.4, 0.24, Math.abs(x)) * d.bright, 0, 1);
130
+ stops.push(new Float32Array([color[0], color[1], color[2], alpha]));
131
+ }
132
+ return stops;
133
+ };
134
+
135
+ /** The line is 0.6% of the shorter side, and never under 1.5 device pixels, as on Android and iOS. */
136
+ export const waveStrokeWidth = (width: number, height: number, pixelRatio: number) =>
137
+ Math.max(0.006 * Math.min(width, height), 1.5 / pixelRatio);
package/src/version.json CHANGED
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": "9.10.2-beta.4"
2
+ "version": "9.11.0-beta.1"
3
3
  }