@repzo/widget-react-native 1.2.1 → 1.3.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.
Files changed (161) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/README.md +40 -3
  3. package/android/build.gradle +22 -0
  4. package/android/src/main/java/expo/modules/repzowidget/RepzoVoicePlayer.kt +222 -0
  5. package/android/src/main/java/expo/modules/repzowidget/RepzoWidgetAudioModule.kt +68 -0
  6. package/expo-module.config.json +9 -0
  7. package/ios/RepzoVoicePlayer.swift +175 -0
  8. package/ios/RepzoWidgetAudio.podspec +26 -0
  9. package/ios/RepzoWidgetAudioModule.swift +65 -0
  10. package/lib/commonjs/api/client.js +47 -2
  11. package/lib/commonjs/api/client.js.map +1 -1
  12. package/lib/commonjs/audio/native-player.js +97 -0
  13. package/lib/commonjs/audio/native-player.js.map +1 -0
  14. package/lib/commonjs/audio/voice.js +146 -0
  15. package/lib/commonjs/audio/voice.js.map +1 -0
  16. package/lib/commonjs/i18n.js +6 -0
  17. package/lib/commonjs/i18n.js.map +1 -1
  18. package/lib/commonjs/locales.js +4 -0
  19. package/lib/commonjs/locales.js.map +1 -1
  20. package/lib/commonjs/state/use-widget-state.js +31 -0
  21. package/lib/commonjs/state/use-widget-state.js.map +1 -1
  22. package/lib/commonjs/state/widget-store.js +44 -1
  23. package/lib/commonjs/state/widget-store.js.map +1 -1
  24. package/lib/commonjs/ui/WidgetProvider.js +7 -3
  25. package/lib/commonjs/ui/WidgetProvider.js.map +1 -1
  26. package/lib/commonjs/ui/components/FileCard.js +134 -0
  27. package/lib/commonjs/ui/components/FileCard.js.map +1 -0
  28. package/lib/commonjs/ui/components/ImageViewer.js +17 -35
  29. package/lib/commonjs/ui/components/ImageViewer.js.map +1 -1
  30. package/lib/commonjs/ui/components/Launcher.js +15 -1
  31. package/lib/commonjs/ui/components/Launcher.js.map +1 -1
  32. package/lib/commonjs/ui/components/MessagesSkeleton.js +126 -0
  33. package/lib/commonjs/ui/components/MessagesSkeleton.js.map +1 -0
  34. package/lib/commonjs/ui/components/VoiceNote.js +353 -0
  35. package/lib/commonjs/ui/components/VoiceNote.js.map +1 -0
  36. package/lib/commonjs/ui/components/ZoomableImage.js +299 -0
  37. package/lib/commonjs/ui/components/ZoomableImage.js.map +1 -0
  38. package/lib/commonjs/ui/components/icons.js +57 -0
  39. package/lib/commonjs/ui/components/icons.js.map +1 -1
  40. package/lib/commonjs/ui/screens/chat/ChatView.js +39 -18
  41. package/lib/commonjs/ui/screens/chat/ChatView.js.map +1 -1
  42. package/lib/commonjs/ui/screens/chat/ChatView.styles.js +5 -16
  43. package/lib/commonjs/ui/screens/chat/ChatView.styles.js.map +1 -1
  44. package/lib/module/api/client.js +48 -2
  45. package/lib/module/api/client.js.map +1 -1
  46. package/lib/module/audio/native-player.js +93 -0
  47. package/lib/module/audio/native-player.js.map +1 -0
  48. package/lib/module/audio/voice.js +136 -0
  49. package/lib/module/audio/voice.js.map +1 -0
  50. package/lib/module/i18n.js +6 -0
  51. package/lib/module/i18n.js.map +1 -1
  52. package/lib/module/locales.js +4 -0
  53. package/lib/module/locales.js.map +1 -1
  54. package/lib/module/state/use-widget-state.js +30 -1
  55. package/lib/module/state/use-widget-state.js.map +1 -1
  56. package/lib/module/state/widget-store.js +44 -1
  57. package/lib/module/state/widget-store.js.map +1 -1
  58. package/lib/module/ui/WidgetProvider.js +7 -3
  59. package/lib/module/ui/WidgetProvider.js.map +1 -1
  60. package/lib/module/ui/components/FileCard.js +130 -0
  61. package/lib/module/ui/components/FileCard.js.map +1 -0
  62. package/lib/module/ui/components/ImageViewer.js +18 -36
  63. package/lib/module/ui/components/ImageViewer.js.map +1 -1
  64. package/lib/module/ui/components/Launcher.js +16 -2
  65. package/lib/module/ui/components/Launcher.js.map +1 -1
  66. package/lib/module/ui/components/MessagesSkeleton.js +124 -0
  67. package/lib/module/ui/components/MessagesSkeleton.js.map +1 -0
  68. package/lib/module/ui/components/VoiceNote.js +349 -0
  69. package/lib/module/ui/components/VoiceNote.js.map +1 -0
  70. package/lib/module/ui/components/ZoomableImage.js +291 -0
  71. package/lib/module/ui/components/ZoomableImage.js.map +1 -0
  72. package/lib/module/ui/components/icons.js +55 -0
  73. package/lib/module/ui/components/icons.js.map +1 -1
  74. package/lib/module/ui/screens/chat/ChatView.js +39 -18
  75. package/lib/module/ui/screens/chat/ChatView.js.map +1 -1
  76. package/lib/module/ui/screens/chat/ChatView.styles.js +5 -16
  77. package/lib/module/ui/screens/chat/ChatView.styles.js.map +1 -1
  78. package/lib/typescript/commonjs/api/client.d.ts +12 -0
  79. package/lib/typescript/commonjs/api/client.d.ts.map +1 -1
  80. package/lib/typescript/commonjs/audio/native-player.d.ts +36 -0
  81. package/lib/typescript/commonjs/audio/native-player.d.ts.map +1 -0
  82. package/lib/typescript/commonjs/audio/voice.d.ts +42 -0
  83. package/lib/typescript/commonjs/audio/voice.d.ts.map +1 -0
  84. package/lib/typescript/commonjs/i18n.d.ts +4 -0
  85. package/lib/typescript/commonjs/i18n.d.ts.map +1 -1
  86. package/lib/typescript/commonjs/locales.d.ts.map +1 -1
  87. package/lib/typescript/commonjs/state/use-widget-state.d.ts +8 -0
  88. package/lib/typescript/commonjs/state/use-widget-state.d.ts.map +1 -1
  89. package/lib/typescript/commonjs/state/widget-store.d.ts +13 -0
  90. package/lib/typescript/commonjs/state/widget-store.d.ts.map +1 -1
  91. package/lib/typescript/commonjs/ui/WidgetProvider.d.ts.map +1 -1
  92. package/lib/typescript/commonjs/ui/components/FileCard.d.ts +21 -0
  93. package/lib/typescript/commonjs/ui/components/FileCard.d.ts.map +1 -0
  94. package/lib/typescript/commonjs/ui/components/ImageViewer.d.ts +2 -2
  95. package/lib/typescript/commonjs/ui/components/ImageViewer.d.ts.map +1 -1
  96. package/lib/typescript/commonjs/ui/components/Launcher.d.ts.map +1 -1
  97. package/lib/typescript/commonjs/ui/components/MessagesSkeleton.d.ts +10 -0
  98. package/lib/typescript/commonjs/ui/components/MessagesSkeleton.d.ts.map +1 -0
  99. package/lib/typescript/commonjs/ui/components/VoiceNote.d.ts +32 -0
  100. package/lib/typescript/commonjs/ui/components/VoiceNote.d.ts.map +1 -0
  101. package/lib/typescript/commonjs/ui/components/ZoomableImage.d.ts +45 -0
  102. package/lib/typescript/commonjs/ui/components/ZoomableImage.d.ts.map +1 -0
  103. package/lib/typescript/commonjs/ui/components/icons.d.ts +4 -0
  104. package/lib/typescript/commonjs/ui/components/icons.d.ts.map +1 -1
  105. package/lib/typescript/commonjs/ui/screens/chat/ChatView.d.ts.map +1 -1
  106. package/lib/typescript/commonjs/ui/screens/chat/ChatView.styles.d.ts +4 -16
  107. package/lib/typescript/commonjs/ui/screens/chat/ChatView.styles.d.ts.map +1 -1
  108. package/lib/typescript/commonjs/ui/screens/chat/ChatView.types.d.ts +4 -0
  109. package/lib/typescript/commonjs/ui/screens/chat/ChatView.types.d.ts.map +1 -1
  110. package/lib/typescript/module/api/client.d.ts +12 -0
  111. package/lib/typescript/module/api/client.d.ts.map +1 -1
  112. package/lib/typescript/module/audio/native-player.d.ts +36 -0
  113. package/lib/typescript/module/audio/native-player.d.ts.map +1 -0
  114. package/lib/typescript/module/audio/voice.d.ts +42 -0
  115. package/lib/typescript/module/audio/voice.d.ts.map +1 -0
  116. package/lib/typescript/module/i18n.d.ts +4 -0
  117. package/lib/typescript/module/i18n.d.ts.map +1 -1
  118. package/lib/typescript/module/locales.d.ts.map +1 -1
  119. package/lib/typescript/module/state/use-widget-state.d.ts +8 -0
  120. package/lib/typescript/module/state/use-widget-state.d.ts.map +1 -1
  121. package/lib/typescript/module/state/widget-store.d.ts +13 -0
  122. package/lib/typescript/module/state/widget-store.d.ts.map +1 -1
  123. package/lib/typescript/module/ui/WidgetProvider.d.ts.map +1 -1
  124. package/lib/typescript/module/ui/components/FileCard.d.ts +21 -0
  125. package/lib/typescript/module/ui/components/FileCard.d.ts.map +1 -0
  126. package/lib/typescript/module/ui/components/ImageViewer.d.ts +2 -2
  127. package/lib/typescript/module/ui/components/ImageViewer.d.ts.map +1 -1
  128. package/lib/typescript/module/ui/components/Launcher.d.ts.map +1 -1
  129. package/lib/typescript/module/ui/components/MessagesSkeleton.d.ts +10 -0
  130. package/lib/typescript/module/ui/components/MessagesSkeleton.d.ts.map +1 -0
  131. package/lib/typescript/module/ui/components/VoiceNote.d.ts +32 -0
  132. package/lib/typescript/module/ui/components/VoiceNote.d.ts.map +1 -0
  133. package/lib/typescript/module/ui/components/ZoomableImage.d.ts +45 -0
  134. package/lib/typescript/module/ui/components/ZoomableImage.d.ts.map +1 -0
  135. package/lib/typescript/module/ui/components/icons.d.ts +4 -0
  136. package/lib/typescript/module/ui/components/icons.d.ts.map +1 -1
  137. package/lib/typescript/module/ui/screens/chat/ChatView.d.ts.map +1 -1
  138. package/lib/typescript/module/ui/screens/chat/ChatView.styles.d.ts +4 -16
  139. package/lib/typescript/module/ui/screens/chat/ChatView.styles.d.ts.map +1 -1
  140. package/lib/typescript/module/ui/screens/chat/ChatView.types.d.ts +4 -0
  141. package/lib/typescript/module/ui/screens/chat/ChatView.types.d.ts.map +1 -1
  142. package/package.json +14 -3
  143. package/react-native.config.js +12 -0
  144. package/src/api/client.ts +56 -2
  145. package/src/audio/native-player.ts +106 -0
  146. package/src/audio/voice.ts +127 -0
  147. package/src/i18n.ts +6 -0
  148. package/src/locales.ts +4 -0
  149. package/src/state/use-widget-state.ts +30 -1
  150. package/src/state/widget-store.ts +45 -1
  151. package/src/ui/WidgetProvider.tsx +7 -3
  152. package/src/ui/components/FileCard.tsx +111 -0
  153. package/src/ui/components/ImageViewer.tsx +11 -30
  154. package/src/ui/components/Launcher.tsx +7 -2
  155. package/src/ui/components/MessagesSkeleton.tsx +92 -0
  156. package/src/ui/components/VoiceNote.tsx +355 -0
  157. package/src/ui/components/ZoomableImage.tsx +250 -0
  158. package/src/ui/components/icons.tsx +33 -0
  159. package/src/ui/screens/chat/ChatView.styles.ts +5 -16
  160. package/src/ui/screens/chat/ChatView.tsx +43 -16
  161. package/src/ui/screens/chat/ChatView.types.ts +4 -0
@@ -0,0 +1,353 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.VoiceNote = VoiceNote;
7
+ var _react = require("react");
8
+ var _reactNative = require("react-native");
9
+ var _voice = require("../../audio/voice.js");
10
+ var _icons = require("./icons.js");
11
+ var _theme = require("../styles/theme.js");
12
+ var _jsxRuntime = require("react/jsx-runtime");
13
+ /**
14
+ * A voice note (or any audio attachment) played inside the chat, laid out the
15
+ * way messaging apps do it: a play button beside a waveform the visitor can
16
+ * tap or drag to jump through the note, the time under it, and a speed chip
17
+ * once the note has been started. Only rendered when the app can play audio
18
+ * (see `src/audio/voice.ts`); otherwise audio stays a file row.
19
+ */
20
+
21
+ const BAR_COUNT = 32;
22
+ const BAR_WIDTH = 3;
23
+ const BAR_MIN_HEIGHT = 4;
24
+ const BAR_MAX_HEIGHT = 24;
25
+ const BUTTON_SIZE = 36;
26
+ const KNOB_SIZE = 12;
27
+ const SPEEDS = [1, 1.5, 2];
28
+
29
+ /**
30
+ * The player is only created once the visitor taps play. The whole thread
31
+ * renders at once, so a player per note would download every note in the
32
+ * conversation as soon as the chat opened.
33
+ */
34
+ function VoiceNote(props) {
35
+ const [started, setStarted] = (0, _react.useState)(false);
36
+ if (started) return /*#__PURE__*/(0, _jsxRuntime.jsx)(ActiveVoiceNote, {
37
+ ...props
38
+ });
39
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(VoiceNoteLayout, {
40
+ ...props,
41
+ playing: false,
42
+ loading: false,
43
+ progress: 0,
44
+ time: null,
45
+ onToggle: () => setStarted(true)
46
+ });
47
+ }
48
+
49
+ /** A note that has been started: owns its player and plays on mount. */
50
+ function ActiveVoiceNote(props) {
51
+ const {
52
+ url,
53
+ onError
54
+ } = props;
55
+ const {
56
+ player,
57
+ status
58
+ } = (0, _voice.useVoicePlayer)(url);
59
+ const [speedIndex, setSpeedIndex] = (0, _react.useState)(0);
60
+ const [scrub, setScrub] = (0, _react.useState)(null);
61
+ const [trackWidth, setTrackWidth] = (0, _react.useState)(0);
62
+ // Whether the visitor asked for playback, so a note still loading shows a
63
+ // spinner, and one they paused while it loaded doesn't.
64
+ const [wantsPlay, setWantsPlay] = (0, _react.useState)(true);
65
+ // This note's hold on playback: starting it pauses any other note.
66
+ const releasePlayback = (0, _react.useRef)(null);
67
+ const duration = Math.max(Number(status?.duration) || 0, 0);
68
+ const currentTime = Math.max(Number(status?.currentTime) || 0, 0);
69
+ const playing = !!status?.playing;
70
+ const finished = !!status?.didJustFinish || duration > 0 && currentTime >= duration - 0.1;
71
+ // `isLoaded` / `isBuffering` are compared strictly: a player that
72
+ // doesn't report them never gets a spinner that can't go away.
73
+ const loading = wantsPlay && !playing && (status?.isLoaded === false || status?.isBuffering === true);
74
+ const pause = () => {
75
+ setWantsPlay(false);
76
+ player.pause();
77
+ };
78
+ const start = () => {
79
+ releasePlayback.current?.();
80
+ releasePlayback.current = (0, _voice.claimVoicePlayback)(pause);
81
+ setWantsPlay(true);
82
+ player.play();
83
+ };
84
+
85
+ // The visitor tapped play on the idle note, which created this player.
86
+ // Neither callback changes for the player's lifetime.
87
+ (0, _react.useEffect)(() => {
88
+ start();
89
+ return () => releasePlayback.current?.();
90
+ // eslint-disable-next-line react-hooks/exhaustive-deps
91
+ }, []);
92
+
93
+ // The error is only on the status update that reports it (the next one
94
+ // clears it), so it is acted on as it arrives.
95
+ const error = status?.error;
96
+ (0, _react.useEffect)(() => {
97
+ if (error) onError();
98
+ }, [error, onError]);
99
+ const togglePlay = async () => {
100
+ if (playing || loading) {
101
+ pause();
102
+ return;
103
+ }
104
+ if (finished) await player.seekTo(0);
105
+ start();
106
+ };
107
+ const cycleSpeed = () => {
108
+ const next = (speedIndex + 1) % SPEEDS.length;
109
+ setSpeedIndex(next);
110
+ try {
111
+ player.setPlaybackRate(SPEEDS[next], 'high');
112
+ } catch {
113
+ // Some versions take no pitch-correction argument.
114
+ try {
115
+ player.setPlaybackRate(SPEEDS[next]);
116
+ } catch {
117
+ // Speed unsupported: the note keeps playing at 1×.
118
+ }
119
+ }
120
+ };
121
+
122
+ // The latest values for the (stable) PanResponder below.
123
+ const latest = (0, _react.useRef)({
124
+ trackWidth,
125
+ duration,
126
+ player
127
+ });
128
+ latest.current = {
129
+ trackWidth,
130
+ duration,
131
+ player
132
+ };
133
+ const ratioAt = x => {
134
+ const width = latest.current.trackWidth;
135
+ return width > 0 ? Math.min(1, Math.max(0, x / width)) : 0;
136
+ };
137
+ // Where the finger first touched the track. Later positions are that plus
138
+ // the drag distance: a move event's own `locationX` is measured from
139
+ // whatever view is under the finger, not from the track.
140
+ const startX = (0, _react.useRef)(0);
141
+ const pan = (0, _react.useRef)(_reactNative.PanResponder.create({
142
+ onStartShouldSetPanResponder: () => true,
143
+ onMoveShouldSetPanResponder: () => true,
144
+ onPanResponderGrant: event => {
145
+ startX.current = event.nativeEvent.locationX;
146
+ setScrub(ratioAt(startX.current));
147
+ },
148
+ onPanResponderMove: (_, gesture) => setScrub(ratioAt(startX.current + gesture.dx)),
149
+ onPanResponderRelease: (_, gesture) => {
150
+ const {
151
+ duration: length,
152
+ player: current
153
+ } = latest.current;
154
+ if (length > 0) void current.seekTo(ratioAt(startX.current + gesture.dx) * length);
155
+ setScrub(null);
156
+ },
157
+ onPanResponderTerminate: () => setScrub(null)
158
+ })).current;
159
+ const displayTime = scrub != null ? scrub * duration : playing ? currentTime : finished ? duration : currentTime || duration;
160
+ // The chip joins once the note was started, so an untouched note shows only its length.
161
+ const showSpeed = speedIndex > 0 || playing || currentTime > 0 && !finished;
162
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(VoiceNoteLayout, {
163
+ ...props,
164
+ playing: playing,
165
+ loading: loading,
166
+ progress: scrub ?? (duration > 0 ? Math.min(1, currentTime / duration) : 0)
167
+ // Nothing to show until the player knows the note's length.
168
+ ,
169
+ time: duration > 0 ? (0, _voice.formatVoiceClock)(displayTime) : null,
170
+ onToggle: () => void togglePlay(),
171
+ track: {
172
+ panHandlers: pan.panHandlers,
173
+ onWidth: setTrackWidth
174
+ },
175
+ speed: showSpeed ? {
176
+ value: SPEEDS[speedIndex],
177
+ changed: speedIndex > 0,
178
+ onPress: cycleSpeed
179
+ } : null
180
+ });
181
+ }
182
+ function VoiceNoteLayout({
183
+ seed,
184
+ color,
185
+ onBrandBubble,
186
+ labels,
187
+ playing,
188
+ loading,
189
+ progress,
190
+ time,
191
+ onToggle,
192
+ track,
193
+ speed
194
+ }) {
195
+ const accent = onBrandBubble ? _theme.palette.white : color;
196
+ const onAccent = onBrandBubble ? color : _theme.palette.white;
197
+ const trackColor = onBrandBubble ? (0, _theme.withAlpha)(_theme.palette.white, 0.4) : _theme.palette.disabled;
198
+ const subtle = onBrandBubble ? (0, _theme.withAlpha)(_theme.palette.white, 0.8) : _theme.palette.textMuted;
199
+ const levels = (0, _react.useMemo)(() => (0, _voice.voiceWaveformLevels)(seed, BAR_COUNT), [seed]);
200
+ const [trackWidth, setTrackWidth] = (0, _react.useState)(0);
201
+ const onTrackLayout = event => {
202
+ setTrackWidth(event.nativeEvent.layout.width);
203
+ track?.onWidth(event.nativeEvent.layout.width);
204
+ };
205
+
206
+ // Bar i's centre sits at this share of the track; the playhead travels the same span.
207
+ const barCentre = i => trackWidth > BAR_WIDTH ? (BAR_WIDTH / 2 + i * (trackWidth - BAR_WIDTH) / (BAR_COUNT - 1)) / trackWidth : i / (BAR_COUNT - 1);
208
+ const knobOffset = trackWidth > 0 ? Math.round(progress * (trackWidth - KNOB_SIZE)) : 0;
209
+ let icon = /*#__PURE__*/(0, _jsxRuntime.jsx)(_icons.PlayIcon, {
210
+ size: 14,
211
+ color: onAccent
212
+ });
213
+ if (loading) icon = /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ActivityIndicator, {
214
+ size: "small",
215
+ color: onAccent
216
+ });else if (playing) icon = /*#__PURE__*/(0, _jsxRuntime.jsx)(_icons.PauseIcon, {
217
+ size: 14,
218
+ color: onAccent
219
+ });
220
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
221
+ style: styles.root,
222
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
223
+ style: styles.row,
224
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Pressable, {
225
+ onPress: onToggle,
226
+ accessibilityRole: "button",
227
+ accessibilityLabel: playing || loading ? labels.pause : labels.play,
228
+ accessibilityState: {
229
+ busy: loading
230
+ },
231
+ hitSlop: 4,
232
+ style: ({
233
+ pressed
234
+ }) => [styles.playButton, {
235
+ backgroundColor: accent,
236
+ opacity: pressed ? 0.8 : 1
237
+ }],
238
+ children: icon
239
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
240
+ style: styles.track,
241
+ onLayout: onTrackLayout,
242
+ ...track?.panHandlers,
243
+ children: [levels.map((level, i) => /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
244
+ // Touches land on the track itself, so their position is measured from its edge.
245
+ pointerEvents: "none",
246
+ style: [styles.bar, {
247
+ height: BAR_MIN_HEIGHT + level * (BAR_MAX_HEIGHT - BAR_MIN_HEIGHT),
248
+ backgroundColor: barCentre(i) <= progress ? accent : trackColor
249
+ }]
250
+ }, i)), trackWidth > 0 ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
251
+ pointerEvents: "none",
252
+ style: [styles.knob, {
253
+ backgroundColor: accent,
254
+ transform: [{
255
+ translateX: knobOffset
256
+ }]
257
+ }]
258
+ }) : null]
259
+ })]
260
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
261
+ style: styles.meta,
262
+ children: [time != null ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
263
+ style: [styles.time, {
264
+ color: subtle
265
+ }],
266
+ children: time
267
+ }) : null, speed ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Pressable, {
268
+ onPress: speed.onPress,
269
+ accessibilityRole: "button",
270
+ accessibilityLabel: labels.speed,
271
+ hitSlop: 12,
272
+ style: ({
273
+ pressed
274
+ }) => [styles.speed, {
275
+ backgroundColor: speed.changed ? accent : trackColor,
276
+ opacity: pressed ? 0.6 : 1
277
+ }],
278
+ children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
279
+ style: [styles.speedText, {
280
+ color: speed.changed ? onAccent : onBrandBubble ? _theme.palette.white : _theme.palette.text
281
+ }],
282
+ children: [speed.value, "\xD7"]
283
+ })
284
+ }) : null]
285
+ })]
286
+ });
287
+ }
288
+ const styles = _reactNative.StyleSheet.create({
289
+ // A timeline runs left to right in every language, so RTL doesn't mirror it
290
+ // (the touch position and the playhead stay in the same coordinates).
291
+ root: {
292
+ minWidth: 200,
293
+ direction: 'ltr'
294
+ },
295
+ row: {
296
+ flexDirection: 'row',
297
+ alignItems: 'center',
298
+ gap: 10
299
+ },
300
+ playButton: {
301
+ width: BUTTON_SIZE,
302
+ height: BUTTON_SIZE,
303
+ borderRadius: BUTTON_SIZE / 2,
304
+ alignItems: 'center',
305
+ justifyContent: 'center'
306
+ },
307
+ track: {
308
+ flex: 1,
309
+ minWidth: 0,
310
+ height: BUTTON_SIZE,
311
+ flexDirection: 'row',
312
+ alignItems: 'center',
313
+ justifyContent: 'space-between'
314
+ },
315
+ bar: {
316
+ width: BAR_WIDTH,
317
+ borderRadius: BAR_WIDTH / 2
318
+ },
319
+ knob: {
320
+ position: 'absolute',
321
+ left: 0,
322
+ width: KNOB_SIZE,
323
+ height: KNOB_SIZE,
324
+ borderRadius: KNOB_SIZE / 2
325
+ },
326
+ meta: {
327
+ minHeight: 18,
328
+ marginTop: -2,
329
+ marginLeft: BUTTON_SIZE + 10,
330
+ flexDirection: 'row',
331
+ alignItems: 'center',
332
+ gap: 6
333
+ },
334
+ time: {
335
+ fontSize: _theme.fontSize.caption,
336
+ fontWeight: '500',
337
+ fontVariant: ['tabular-nums']
338
+ },
339
+ speed: {
340
+ minWidth: 30,
341
+ height: 18,
342
+ paddingHorizontal: 6,
343
+ borderRadius: 9,
344
+ alignItems: 'center',
345
+ justifyContent: 'center'
346
+ },
347
+ speedText: {
348
+ fontSize: 11,
349
+ fontWeight: '700',
350
+ fontVariant: ['tabular-nums']
351
+ }
352
+ });
353
+ //# sourceMappingURL=VoiceNote.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["_react","require","_reactNative","_voice","_icons","_theme","_jsxRuntime","BAR_COUNT","BAR_WIDTH","BAR_MIN_HEIGHT","BAR_MAX_HEIGHT","BUTTON_SIZE","KNOB_SIZE","SPEEDS","VoiceNote","props","started","setStarted","useState","jsx","ActiveVoiceNote","VoiceNoteLayout","playing","loading","progress","time","onToggle","url","onError","player","status","useVoicePlayer","speedIndex","setSpeedIndex","scrub","setScrub","trackWidth","setTrackWidth","wantsPlay","setWantsPlay","releasePlayback","useRef","duration","Math","max","Number","currentTime","finished","didJustFinish","isLoaded","isBuffering","pause","start","current","claimVoicePlayback","play","useEffect","error","togglePlay","seekTo","cycleSpeed","next","length","setPlaybackRate","latest","ratioAt","x","width","min","startX","pan","PanResponder","create","onStartShouldSetPanResponder","onMoveShouldSetPanResponder","onPanResponderGrant","event","nativeEvent","locationX","onPanResponderMove","_","gesture","dx","onPanResponderRelease","onPanResponderTerminate","displayTime","showSpeed","formatVoiceClock","track","panHandlers","onWidth","speed","value","changed","onPress","seed","color","onBrandBubble","labels","accent","palette","white","onAccent","trackColor","withAlpha","disabled","subtle","textMuted","levels","useMemo","voiceWaveformLevels","onTrackLayout","layout","barCentre","i","knobOffset","round","icon","PlayIcon","size","ActivityIndicator","PauseIcon","jsxs","View","style","styles","root","children","row","Pressable","accessibilityRole","accessibilityLabel","accessibilityState","busy","hitSlop","pressed","playButton","backgroundColor","opacity","onLayout","map","level","pointerEvents","bar","height","knob","transform","translateX","meta","Text","speedText","text","StyleSheet","minWidth","direction","flexDirection","alignItems","gap","borderRadius","justifyContent","flex","position","left","minHeight","marginTop","marginLeft","fontSize","caption","fontWeight","fontVariant","paddingHorizontal"],"sourceRoot":"../../../../src","sources":["ui/components/VoiceNote.tsx"],"mappings":";;;;;;AAQA,IAAAA,MAAA,GAAAC,OAAA;AACA,IAAAC,YAAA,GAAAD,OAAA;AAWA,IAAAE,MAAA,GAAAF,OAAA;AACA,IAAAG,MAAA,GAAAH,OAAA;AACA,IAAAI,MAAA,GAAAJ,OAAA;AAA+D,IAAAK,WAAA,GAAAL,OAAA;AAtB/D;AACA;AACA;AACA;AACA;AACA;AACA;;AAqCA,MAAMM,SAAS,GAAG,EAAE;AACpB,MAAMC,SAAS,GAAG,CAAC;AACnB,MAAMC,cAAc,GAAG,CAAC;AACxB,MAAMC,cAAc,GAAG,EAAE;AACzB,MAAMC,WAAW,GAAG,EAAE;AACtB,MAAMC,SAAS,GAAG,EAAE;AACpB,MAAMC,MAAM,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,CAAU;;AAEnC;AACA;AACA;AACA;AACA;AACO,SAASC,SAASA,CAACC,KAAqB,EAAE;EAC/C,MAAM,CAACC,OAAO,EAAEC,UAAU,CAAC,GAAG,IAAAC,eAAQ,EAAC,KAAK,CAAC;EAC7C,IAAIF,OAAO,EAAE,oBAAO,IAAAV,WAAA,CAAAa,GAAA,EAACC,eAAe;IAAA,GAAKL;EAAK,CAAG,CAAC;EAClD,oBACE,IAAAT,WAAA,CAAAa,GAAA,EAACE,eAAe;IAAA,GACVN,KAAK;IACTO,OAAO,EAAE,KAAM;IACfC,OAAO,EAAE,KAAM;IACfC,QAAQ,EAAE,CAAE;IACZC,IAAI,EAAE,IAAK;IACXC,QAAQ,EAAEA,CAAA,KAAMT,UAAU,CAAC,IAAI;EAAE,CAClC,CAAC;AAEN;;AAEA;AACA,SAASG,eAAeA,CAACL,KAAqB,EAAE;EAC9C,MAAM;IAAEY,GAAG;IAAEC;EAAQ,CAAC,GAAGb,KAAK;EAC9B,MAAM;IAAEc,MAAM;IAAEC;EAAO,CAAC,GAAG,IAAAC,qBAAc,EAACJ,GAAG,CAAC;EAC9C,MAAM,CAACK,UAAU,EAAEC,aAAa,CAAC,GAAG,IAAAf,eAAQ,EAAC,CAAC,CAAC;EAC/C,MAAM,CAACgB,KAAK,EAAEC,QAAQ,CAAC,GAAG,IAAAjB,eAAQ,EAAgB,IAAI,CAAC;EACvD,MAAM,CAACkB,UAAU,EAAEC,aAAa,CAAC,GAAG,IAAAnB,eAAQ,EAAC,CAAC,CAAC;EAC/C;EACA;EACA,MAAM,CAACoB,SAAS,EAAEC,YAAY,CAAC,GAAG,IAAArB,eAAQ,EAAC,IAAI,CAAC;EAChD;EACA,MAAMsB,eAAe,GAAG,IAAAC,aAAM,EAAsB,IAAI,CAAC;EAEzD,MAAMC,QAAQ,GAAGC,IAAI,CAACC,GAAG,CAACC,MAAM,CAACf,MAAM,EAAEY,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;EAC3D,MAAMI,WAAW,GAAGH,IAAI,CAACC,GAAG,CAACC,MAAM,CAACf,MAAM,EAAEgB,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;EACjE,MAAMxB,OAAO,GAAG,CAAC,CAACQ,MAAM,EAAER,OAAO;EACjC,MAAMyB,QAAQ,GAAG,CAAC,CAACjB,MAAM,EAAEkB,aAAa,IAAKN,QAAQ,GAAG,CAAC,IAAII,WAAW,IAAIJ,QAAQ,GAAG,GAAI;EAC3F;EACA;EACA,MAAMnB,OAAO,GAAGe,SAAS,IAAI,CAAChB,OAAO,KAAKQ,MAAM,EAAEmB,QAAQ,KAAK,KAAK,IAAInB,MAAM,EAAEoB,WAAW,KAAK,IAAI,CAAC;EAErG,MAAMC,KAAK,GAAGA,CAAA,KAAM;IAClBZ,YAAY,CAAC,KAAK,CAAC;IACnBV,MAAM,CAACsB,KAAK,CAAC,CAAC;EAChB,CAAC;EACD,MAAMC,KAAK,GAAGA,CAAA,KAAM;IAClBZ,eAAe,CAACa,OAAO,GAAG,CAAC;IAC3Bb,eAAe,CAACa,OAAO,GAAG,IAAAC,yBAAkB,EAACH,KAAK,CAAC;IACnDZ,YAAY,CAAC,IAAI,CAAC;IAClBV,MAAM,CAAC0B,IAAI,CAAC,CAAC;EACf,CAAC;;EAED;EACA;EACA,IAAAC,gBAAS,EAAC,MAAM;IACdJ,KAAK,CAAC,CAAC;IACP,OAAO,MAAMZ,eAAe,CAACa,OAAO,GAAG,CAAC;IACxC;EACF,CAAC,EAAE,EAAE,CAAC;;EAEN;EACA;EACA,MAAMI,KAAK,GAAG3B,MAAM,EAAE2B,KAAK;EAC3B,IAAAD,gBAAS,EAAC,MAAM;IACd,IAAIC,KAAK,EAAE7B,OAAO,CAAC,CAAC;EACtB,CAAC,EAAE,CAAC6B,KAAK,EAAE7B,OAAO,CAAC,CAAC;EAEpB,MAAM8B,UAAU,GAAG,MAAAA,CAAA,KAAY;IAC7B,IAAIpC,OAAO,IAAIC,OAAO,EAAE;MACtB4B,KAAK,CAAC,CAAC;MACP;IACF;IACA,IAAIJ,QAAQ,EAAE,MAAMlB,MAAM,CAAC8B,MAAM,CAAC,CAAC,CAAC;IACpCP,KAAK,CAAC,CAAC;EACT,CAAC;EAED,MAAMQ,UAAU,GAAGA,CAAA,KAAM;IACvB,MAAMC,IAAI,GAAG,CAAC7B,UAAU,GAAG,CAAC,IAAInB,MAAM,CAACiD,MAAM;IAC7C7B,aAAa,CAAC4B,IAAI,CAAC;IACnB,IAAI;MACFhC,MAAM,CAACkC,eAAe,CAAClD,MAAM,CAACgD,IAAI,CAAC,EAAE,MAAM,CAAC;IAC9C,CAAC,CAAC,MAAM;MACN;MACA,IAAI;QACFhC,MAAM,CAACkC,eAAe,CAAClD,MAAM,CAACgD,IAAI,CAAC,CAAC;MACtC,CAAC,CAAC,MAAM;QACN;MAAA;IAEJ;EACF,CAAC;;EAED;EACA,MAAMG,MAAM,GAAG,IAAAvB,aAAM,EAAC;IAAEL,UAAU;IAAEM,QAAQ;IAAEb;EAAO,CAAC,CAAC;EACvDmC,MAAM,CAACX,OAAO,GAAG;IAAEjB,UAAU;IAAEM,QAAQ;IAAEb;EAAO,CAAC;EACjD,MAAMoC,OAAO,GAAIC,CAAS,IAAK;IAC7B,MAAMC,KAAK,GAAGH,MAAM,CAACX,OAAO,CAACjB,UAAU;IACvC,OAAO+B,KAAK,GAAG,CAAC,GAAGxB,IAAI,CAACyB,GAAG,CAAC,CAAC,EAAEzB,IAAI,CAACC,GAAG,CAAC,CAAC,EAAEsB,CAAC,GAAGC,KAAK,CAAC,CAAC,GAAG,CAAC;EAC5D,CAAC;EACD;EACA;EACA;EACA,MAAME,MAAM,GAAG,IAAA5B,aAAM,EAAC,CAAC,CAAC;EACxB,MAAM6B,GAAG,GAAG,IAAA7B,aAAM,EAChB8B,yBAAY,CAACC,MAAM,CAAC;IAClBC,4BAA4B,EAAEA,CAAA,KAAM,IAAI;IACxCC,2BAA2B,EAAEA,CAAA,KAAM,IAAI;IACvCC,mBAAmB,EAAGC,KAAK,IAAK;MAC9BP,MAAM,CAAChB,OAAO,GAAGuB,KAAK,CAACC,WAAW,CAACC,SAAS;MAC5C3C,QAAQ,CAAC8B,OAAO,CAACI,MAAM,CAAChB,OAAO,CAAC,CAAC;IACnC,CAAC;IACD0B,kBAAkB,EAAEA,CAACC,CAAC,EAAEC,OAAO,KAAK9C,QAAQ,CAAC8B,OAAO,CAACI,MAAM,CAAChB,OAAO,GAAG4B,OAAO,CAACC,EAAE,CAAC,CAAC;IAClFC,qBAAqB,EAAEA,CAACH,CAAC,EAAEC,OAAO,KAAK;MACrC,MAAM;QAAEvC,QAAQ,EAAEoB,MAAM;QAAEjC,MAAM,EAAEwB;MAAQ,CAAC,GAAGW,MAAM,CAACX,OAAO;MAC5D,IAAIS,MAAM,GAAG,CAAC,EAAE,KAAKT,OAAO,CAACM,MAAM,CAACM,OAAO,CAACI,MAAM,CAAChB,OAAO,GAAG4B,OAAO,CAACC,EAAE,CAAC,GAAGpB,MAAM,CAAC;MAClF3B,QAAQ,CAAC,IAAI,CAAC;IAChB,CAAC;IACDiD,uBAAuB,EAAEA,CAAA,KAAMjD,QAAQ,CAAC,IAAI;EAC9C,CAAC,CACH,CAAC,CAACkB,OAAO;EAET,MAAMgC,WAAW,GACfnD,KAAK,IAAI,IAAI,GAAGA,KAAK,GAAGQ,QAAQ,GAAGpB,OAAO,GAAGwB,WAAW,GAAGC,QAAQ,GAAGL,QAAQ,GAAGI,WAAW,IAAIJ,QAAQ;EAC1G;EACA,MAAM4C,SAAS,GAAGtD,UAAU,GAAG,CAAC,IAAIV,OAAO,IAAKwB,WAAW,GAAG,CAAC,IAAI,CAACC,QAAS;EAE7E,oBACE,IAAAzC,WAAA,CAAAa,GAAA,EAACE,eAAe;IAAA,GACVN,KAAK;IACTO,OAAO,EAAEA,OAAQ;IACjBC,OAAO,EAAEA,OAAQ;IACjBC,QAAQ,EAAEU,KAAK,KAAKQ,QAAQ,GAAG,CAAC,GAAGC,IAAI,CAACyB,GAAG,CAAC,CAAC,EAAEtB,WAAW,GAAGJ,QAAQ,CAAC,GAAG,CAAC;IAC1E;IAAA;IACAjB,IAAI,EAAEiB,QAAQ,GAAG,CAAC,GAAG,IAAA6C,uBAAgB,EAACF,WAAW,CAAC,GAAG,IAAK;IAC1D3D,QAAQ,EAAEA,CAAA,KAAM,KAAKgC,UAAU,CAAC,CAAE;IAClC8B,KAAK,EAAE;MAAEC,WAAW,EAAEnB,GAAG,CAACmB,WAAW;MAAEC,OAAO,EAAErD;IAAc,CAAE;IAChEsD,KAAK,EAAEL,SAAS,GAAG;MAAEM,KAAK,EAAE/E,MAAM,CAACmB,UAAU,CAAC;MAAE6D,OAAO,EAAE7D,UAAU,GAAG,CAAC;MAAE8D,OAAO,EAAElC;IAAW,CAAC,GAAG;EAAK,CACvG,CAAC;AAEN;AAEA,SAASvC,eAAeA,CAAC;EACvB0E,IAAI;EACJC,KAAK;EACLC,aAAa;EACbC,MAAM;EACN5E,OAAO;EACPC,OAAO;EACPC,QAAQ;EACRC,IAAI;EACJC,QAAQ;EACR8D,KAAK;EACLG;AAWF,CAAC,EAAE;EACD,MAAMQ,MAAM,GAAGF,aAAa,GAAGG,cAAO,CAACC,KAAK,GAAGL,KAAK;EACpD,MAAMM,QAAQ,GAAGL,aAAa,GAAGD,KAAK,GAAGI,cAAO,CAACC,KAAK;EACtD,MAAME,UAAU,GAAGN,aAAa,GAAG,IAAAO,gBAAS,EAACJ,cAAO,CAACC,KAAK,EAAE,GAAG,CAAC,GAAGD,cAAO,CAACK,QAAQ;EACnF,MAAMC,MAAM,GAAGT,aAAa,GAAG,IAAAO,gBAAS,EAACJ,cAAO,CAACC,KAAK,EAAE,GAAG,CAAC,GAAGD,cAAO,CAACO,SAAS;EAChF,MAAMC,MAAM,GAAG,IAAAC,cAAO,EAAC,MAAM,IAAAC,0BAAmB,EAACf,IAAI,EAAExF,SAAS,CAAC,EAAE,CAACwF,IAAI,CAAC,CAAC;EAC1E,MAAM,CAAC3D,UAAU,EAAEC,aAAa,CAAC,GAAG,IAAAnB,eAAQ,EAAC,CAAC,CAAC;EAC/C,MAAM6F,aAAa,GAAInC,KAAwB,IAAK;IAClDvC,aAAa,CAACuC,KAAK,CAACC,WAAW,CAACmC,MAAM,CAAC7C,KAAK,CAAC;IAC7CqB,KAAK,EAAEE,OAAO,CAACd,KAAK,CAACC,WAAW,CAACmC,MAAM,CAAC7C,KAAK,CAAC;EAChD,CAAC;;EAED;EACA,MAAM8C,SAAS,GAAIC,CAAS,IAC1B9E,UAAU,GAAG5B,SAAS,GAClB,CAACA,SAAS,GAAG,CAAC,GAAI0G,CAAC,IAAI9E,UAAU,GAAG5B,SAAS,CAAC,IAAKD,SAAS,GAAG,CAAC,CAAC,IAAI6B,UAAU,GAC/E8E,CAAC,IAAI3G,SAAS,GAAG,CAAC,CAAC;EACzB,MAAM4G,UAAU,GAAG/E,UAAU,GAAG,CAAC,GAAGO,IAAI,CAACyE,KAAK,CAAC5F,QAAQ,IAAIY,UAAU,GAAGxB,SAAS,CAAC,CAAC,GAAG,CAAC;EAEvF,IAAIyG,IAAe,gBAAG,IAAA/G,WAAA,CAAAa,GAAA,EAACf,MAAA,CAAAkH,QAAQ;IAACC,IAAI,EAAE,EAAG;IAACvB,KAAK,EAAEM;EAAS,CAAE,CAAC;EAC7D,IAAI/E,OAAO,EAAE8F,IAAI,gBAAG,IAAA/G,WAAA,CAAAa,GAAA,EAACjB,YAAA,CAAAsH,iBAAiB;IAACD,IAAI,EAAC,OAAO;IAACvB,KAAK,EAAEM;EAAS,CAAE,CAAC,CAAC,KACnE,IAAIhF,OAAO,EAAE+F,IAAI,gBAAG,IAAA/G,WAAA,CAAAa,GAAA,EAACf,MAAA,CAAAqH,SAAS;IAACF,IAAI,EAAE,EAAG;IAACvB,KAAK,EAAEM;EAAS,CAAE,CAAC;EAEjE,oBACE,IAAAhG,WAAA,CAAAoH,IAAA,EAACxH,YAAA,CAAAyH,IAAI;IAACC,KAAK,EAAEC,MAAM,CAACC,IAAK;IAAAC,QAAA,gBACvB,IAAAzH,WAAA,CAAAoH,IAAA,EAACxH,YAAA,CAAAyH,IAAI;MAACC,KAAK,EAAEC,MAAM,CAACG,GAAI;MAAAD,QAAA,gBACtB,IAAAzH,WAAA,CAAAa,GAAA,EAACjB,YAAA,CAAA+H,SAAS;QACRnC,OAAO,EAAEpE,QAAS;QAClBwG,iBAAiB,EAAC,QAAQ;QAC1BC,kBAAkB,EAAE7G,OAAO,IAAIC,OAAO,GAAG2E,MAAM,CAAC/C,KAAK,GAAG+C,MAAM,CAAC3C,IAAK;QACpE6E,kBAAkB,EAAE;UAAEC,IAAI,EAAE9G;QAAQ,CAAE;QACtC+G,OAAO,EAAE,CAAE;QACXV,KAAK,EAAEA,CAAC;UAAEW;QAAQ,CAAC,KAAK,CACtBV,MAAM,CAACW,UAAU,EACjB;UAAEC,eAAe,EAAEtC,MAAM;UAAEuC,OAAO,EAAEH,OAAO,GAAG,GAAG,GAAG;QAAE,CAAC,CACvD;QAAAR,QAAA,EACDV;MAAI,CACI,CAAC,eAEZ,IAAA/G,WAAA,CAAAoH,IAAA,EAACxH,YAAA,CAAAyH,IAAI;QACHC,KAAK,EAAEC,MAAM,CAACrC,KAAM;QACpBmD,QAAQ,EAAE5B,aAAc;QAAA,GACpBvB,KAAK,EAAEC,WAAW;QAAAsC,QAAA,GACrBnB,MAAM,CAACgC,GAAG,CAAC,CAACC,KAAK,EAAE3B,CAAC,kBACnB,IAAA5G,WAAA,CAAAa,GAAA,EAACjB,YAAA,CAAAyH,IAAI;UAEH;UACAmB,aAAa,EAAC,MAAM;UACpBlB,KAAK,EAAE,CACLC,MAAM,CAACkB,GAAG,EACV;YACEC,MAAM,EAAEvI,cAAc,GAAGoI,KAAK,IAAInI,cAAc,GAAGD,cAAc,CAAC;YAClEgI,eAAe,EAAExB,SAAS,CAACC,CAAC,CAAC,IAAI1F,QAAQ,GAAG2E,MAAM,GAAGI;UACvD,CAAC;QACD,GATGW,CAUN,CACF,CAAC,EACD9E,UAAU,GAAG,CAAC,gBACb,IAAA9B,WAAA,CAAAa,GAAA,EAACjB,YAAA,CAAAyH,IAAI;UACHmB,aAAa,EAAC,MAAM;UACpBlB,KAAK,EAAE,CACLC,MAAM,CAACoB,IAAI,EACX;YAAER,eAAe,EAAEtC,MAAM;YAAE+C,SAAS,EAAE,CAAC;cAAEC,UAAU,EAAEhC;YAAW,CAAC;UAAE,CAAC;QACpE,CACH,CAAC,GACA,IAAI;MAAA,CACJ,CAAC;IAAA,CACH,CAAC,eAEP,IAAA7G,WAAA,CAAAoH,IAAA,EAACxH,YAAA,CAAAyH,IAAI;MAACC,KAAK,EAAEC,MAAM,CAACuB,IAAK;MAAArB,QAAA,GACtBtG,IAAI,IAAI,IAAI,gBAAG,IAAAnB,WAAA,CAAAa,GAAA,EAACjB,YAAA,CAAAmJ,IAAI;QAACzB,KAAK,EAAE,CAACC,MAAM,CAACpG,IAAI,EAAE;UAAEuE,KAAK,EAAEU;QAAO,CAAC,CAAE;QAAAqB,QAAA,EAAEtG;MAAI,CAAO,CAAC,GAAG,IAAI,EAClFkE,KAAK,gBACJ,IAAArF,WAAA,CAAAa,GAAA,EAACjB,YAAA,CAAA+H,SAAS;QACRnC,OAAO,EAAEH,KAAK,CAACG,OAAQ;QACvBoC,iBAAiB,EAAC,QAAQ;QAC1BC,kBAAkB,EAAEjC,MAAM,CAACP,KAAM;QACjC2C,OAAO,EAAE,EAAG;QACZV,KAAK,EAAEA,CAAC;UAAEW;QAAQ,CAAC,KAAK,CACtBV,MAAM,CAAClC,KAAK,EACZ;UAAE8C,eAAe,EAAE9C,KAAK,CAACE,OAAO,GAAGM,MAAM,GAAGI,UAAU;UAAEmC,OAAO,EAAEH,OAAO,GAAG,GAAG,GAAG;QAAE,CAAC,CACpF;QAAAR,QAAA,eACF,IAAAzH,WAAA,CAAAoH,IAAA,EAACxH,YAAA,CAAAmJ,IAAI;UACHzB,KAAK,EAAE,CACLC,MAAM,CAACyB,SAAS,EAChB;YAAEtD,KAAK,EAAEL,KAAK,CAACE,OAAO,GAAGS,QAAQ,GAAGL,aAAa,GAAGG,cAAO,CAACC,KAAK,GAAGD,cAAO,CAACmD;UAAK,CAAC,CAClF;UAAAxB,QAAA,GACDpC,KAAK,CAACC,KAAK,EAAC,MACf;QAAA,CAAM;MAAC,CACE,CAAC,GACV,IAAI;IAAA,CACJ,CAAC;EAAA,CACH,CAAC;AAEX;AAEA,MAAMiC,MAAM,GAAG2B,uBAAU,CAAChF,MAAM,CAAC;EAC/B;EACA;EACAsD,IAAI,EAAE;IAAE2B,QAAQ,EAAE,GAAG;IAAEC,SAAS,EAAE;EAAM,CAAC;EACzC1B,GAAG,EAAE;IAAE2B,aAAa,EAAE,KAAK;IAAEC,UAAU,EAAE,QAAQ;IAAEC,GAAG,EAAE;EAAG,CAAC;EAC5DrB,UAAU,EAAE;IACVrE,KAAK,EAAExD,WAAW;IAClBqI,MAAM,EAAErI,WAAW;IACnBmJ,YAAY,EAAEnJ,WAAW,GAAG,CAAC;IAC7BiJ,UAAU,EAAE,QAAQ;IACpBG,cAAc,EAAE;EAClB,CAAC;EACDvE,KAAK,EAAE;IACLwE,IAAI,EAAE,CAAC;IACPP,QAAQ,EAAE,CAAC;IACXT,MAAM,EAAErI,WAAW;IACnBgJ,aAAa,EAAE,KAAK;IACpBC,UAAU,EAAE,QAAQ;IACpBG,cAAc,EAAE;EAClB,CAAC;EACDhB,GAAG,EAAE;IAAE5E,KAAK,EAAE3D,SAAS;IAAEsJ,YAAY,EAAEtJ,SAAS,GAAG;EAAE,CAAC;EACtDyI,IAAI,EAAE;IACJgB,QAAQ,EAAE,UAAU;IACpBC,IAAI,EAAE,CAAC;IACP/F,KAAK,EAAEvD,SAAS;IAChBoI,MAAM,EAAEpI,SAAS;IACjBkJ,YAAY,EAAElJ,SAAS,GAAG;EAC5B,CAAC;EACDwI,IAAI,EAAE;IACJe,SAAS,EAAE,EAAE;IACbC,SAAS,EAAE,CAAC,CAAC;IACbC,UAAU,EAAE1J,WAAW,GAAG,EAAE;IAC5BgJ,aAAa,EAAE,KAAK;IACpBC,UAAU,EAAE,QAAQ;IACpBC,GAAG,EAAE;EACP,CAAC;EACDpI,IAAI,EAAE;IAAE6I,QAAQ,EAAEA,eAAQ,CAACC,OAAO;IAAEC,UAAU,EAAE,KAAK;IAAEC,WAAW,EAAE,CAAC,cAAc;EAAE,CAAC;EACtF9E,KAAK,EAAE;IACL8D,QAAQ,EAAE,EAAE;IACZT,MAAM,EAAE,EAAE;IACV0B,iBAAiB,EAAE,CAAC;IACpBZ,YAAY,EAAE,CAAC;IACfF,UAAU,EAAE,QAAQ;IACpBG,cAAc,EAAE;EAClB,CAAC;EACDT,SAAS,EAAE;IAAEgB,QAAQ,EAAE,EAAE;IAAEE,UAAU,EAAE,KAAK;IAAEC,WAAW,EAAE,CAAC,cAAc;EAAE;AAC9E,CAAC,CAAC","ignoreList":[]}
@@ -0,0 +1,299 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.MAX_ZOOM = exports.DOUBLE_TAP_ZOOM = void 0;
7
+ exports.ZoomableImage = ZoomableImage;
8
+ exports.clampView = clampView;
9
+ exports.fittedSize = fittedSize;
10
+ exports.zoomAround = zoomAround;
11
+ var _react = require("react");
12
+ var _reactNative = require("react-native");
13
+ var _useReduceMotion = require("../hooks/use-reduce-motion.js");
14
+ var _jsxRuntime = require("react/jsx-runtime");
15
+ /**
16
+ * An image the visitor can pinch to zoom, drag around once zoomed, and
17
+ * double-tap to zoom in on a spot (and again to zoom back out). Built on
18
+ * PanResponder and Animated, so it behaves the same on iOS and Android
19
+ * without a gesture library in the host app.
20
+ */
21
+
22
+ const MAX_ZOOM = exports.MAX_ZOOM = 4;
23
+ const DOUBLE_TAP_ZOOM = exports.DOUBLE_TAP_ZOOM = 2.5;
24
+ const DOUBLE_TAP_MS = 300;
25
+ const DOUBLE_TAP_SLOP = 30;
26
+ const TAP_SLOP = 10;
27
+
28
+ /** Scale and offset (from the centre of the view) of the image. */
29
+
30
+ /** The size the image is drawn at with `contain`, before any zoom. */
31
+ function fittedSize(box, image) {
32
+ if (!image || image.width <= 0 || image.height <= 0) return box;
33
+ const fit = Math.min(box.width / image.width, box.height / image.height);
34
+ return {
35
+ width: image.width * fit,
36
+ height: image.height * fit
37
+ };
38
+ }
39
+
40
+ /**
41
+ * Keeps the image inside the screen: zoom between 1× and `MAX_ZOOM`, and no
42
+ * empty space past an edge the zoomed image is wider (or taller) than.
43
+ */
44
+ function clampView(view, box, content) {
45
+ const scale = Math.min(MAX_ZOOM, Math.max(1, view.scale));
46
+ const maxX = Math.max(0, (content.width * scale - box.width) / 2);
47
+ const maxY = Math.max(0, (content.height * scale - box.height) / 2);
48
+ return {
49
+ scale,
50
+ x: Math.min(maxX, Math.max(-maxX, view.x)),
51
+ y: Math.min(maxY, Math.max(-maxY, view.y))
52
+ };
53
+ }
54
+
55
+ /**
56
+ * The view at `scale` that keeps the spot that was under `from` (a point
57
+ * relative to the centre, in the `start` view) under `to`. Pinching zooms
58
+ * around the fingers this way, and moving them pans at the same time.
59
+ */
60
+ function zoomAround(start, scale, from, to) {
61
+ const spotX = (from.x - start.x) / start.scale;
62
+ const spotY = (from.y - start.y) / start.scale;
63
+ return {
64
+ scale,
65
+ x: to.x - scale * spotX,
66
+ y: to.y - scale * spotY
67
+ };
68
+ }
69
+ const centreOf = touches => ({
70
+ x: touches.reduce((sum, t) => sum + t.pageX, 0) / touches.length,
71
+ y: touches.reduce((sum, t) => sum + t.pageY, 0) / touches.length
72
+ });
73
+ const spreadOf = touches => touches.length < 2 ? 0 : Math.hypot(touches[0].pageX - touches[1].pageX, touches[0].pageY - touches[1].pageY);
74
+ function ZoomableImage({
75
+ uri,
76
+ accessibilityLabel,
77
+ onLoad,
78
+ onError
79
+ }) {
80
+ const reduceMotion = (0, _useReduceMotion.useReduceMotion)();
81
+ const [scale] = (0, _react.useState)(() => new _reactNative.Animated.Value(1));
82
+ const [x] = (0, _react.useState)(() => new _reactNative.Animated.Value(0));
83
+ const [y] = (0, _react.useState)(() => new _reactNative.Animated.Value(0));
84
+
85
+ // Gesture state, read by the (stable) PanResponder below.
86
+ const g = (0, _react.useRef)({
87
+ view: {
88
+ scale: 1,
89
+ x: 0,
90
+ y: 0
91
+ },
92
+ box: {
93
+ width: 0,
94
+ height: 0
95
+ },
96
+ // Where the box sits on screen, so touches can be measured from its centre.
97
+ origin: {
98
+ x: 0,
99
+ y: 0
100
+ },
101
+ image: null,
102
+ // The view, finger centre and finger spread when the current set of fingers went down.
103
+ start: {
104
+ view: {
105
+ scale: 1,
106
+ x: 0,
107
+ y: 0
108
+ },
109
+ centre: {
110
+ x: 0,
111
+ y: 0
112
+ },
113
+ spread: 0,
114
+ count: 0
115
+ },
116
+ moved: false,
117
+ grantedAt: 0,
118
+ lastTap: null,
119
+ reduceMotion: false,
120
+ native: false
121
+ }).current;
122
+ g.reduceMotion = reduceMotion;
123
+ const boxRef = (0, _react.useRef)(null);
124
+ const show = (view, animate) => {
125
+ g.view = view;
126
+ if (!animate || g.reduceMotion !== false) {
127
+ scale.setValue(view.scale);
128
+ x.setValue(view.x);
129
+ y.setValue(view.y);
130
+ return;
131
+ }
132
+ const spring = (value, toValue) => _reactNative.Animated.spring(value, {
133
+ toValue,
134
+ useNativeDriver: true,
135
+ bounciness: 0,
136
+ speed: 18
137
+ });
138
+ _reactNative.Animated.parallel([spring(scale, view.scale), spring(x, view.x), spring(y, view.y)]).start();
139
+ };
140
+
141
+ // Hands the transform to the native driver as soon as the image is on
142
+ // screen. Until a native animation had run, Android (new architecture) didn't
143
+ // draw finger moves, so pinching only worked after a double tap.
144
+ const goNative = () => {
145
+ if (g.native) return;
146
+ g.native = true;
147
+ const settle = (value, toValue) => _reactNative.Animated.spring(value, {
148
+ toValue,
149
+ useNativeDriver: true,
150
+ bounciness: 0,
151
+ speed: 18
152
+ });
153
+ _reactNative.Animated.parallel([settle(scale, g.view.scale), settle(x, g.view.x), settle(y, g.view.y)]).start();
154
+ };
155
+ const fromCentre = point => ({
156
+ x: point.x - g.origin.x - g.box.width / 2,
157
+ y: point.y - g.origin.y - g.box.height / 2
158
+ });
159
+ const content = () => fittedSize(g.box, g.image);
160
+ const begin = touches => {
161
+ g.start = {
162
+ view: g.view,
163
+ centre: fromCentre(centreOf(touches)),
164
+ spread: spreadOf(touches),
165
+ count: touches.length
166
+ };
167
+ };
168
+ const pan = (0, _react.useRef)(_reactNative.PanResponder.create({
169
+ onStartShouldSetPanResponder: () => true,
170
+ onMoveShouldSetPanResponder: () => true,
171
+ // Never hand the gesture over: nothing else in the full-screen viewer
172
+ // wants it, and giving it up at 1× ended a pinch as it started.
173
+ onPanResponderTerminationRequest: () => false,
174
+ onPanResponderGrant: event => {
175
+ g.moved = false;
176
+ g.grantedAt = Date.now();
177
+ begin(event.nativeEvent.touches);
178
+ },
179
+ onPanResponderMove: (event, gesture) => {
180
+ const touches = event.nativeEvent.touches;
181
+ if (touches.length === 0) return;
182
+ // A finger landed or lifted: carry on from where the image is now.
183
+ if (touches.length !== g.start.count) begin(touches);
184
+ if (Math.abs(gesture.dx) > TAP_SLOP || Math.abs(gesture.dy) > TAP_SLOP || touches.length > 1) {
185
+ g.moved = true;
186
+ }
187
+ const centre = fromCentre(centreOf(touches));
188
+ if (touches.length > 1 && g.start.spread > 0) {
189
+ // Past the limits while pinching, then springs back on release.
190
+ const zoom = Math.min(MAX_ZOOM * 1.25, Math.max(0.75, g.start.view.scale * spreadOf(touches) / g.start.spread));
191
+ show(zoomAround(g.start.view, zoom, g.start.centre, centre), false);
192
+ } else if (g.start.view.scale > 1) {
193
+ // One finger drags the zoomed image; at 1× there's nothing to drag.
194
+ const dragged = {
195
+ ...g.start.view,
196
+ x: g.start.view.x + centre.x - g.start.centre.x,
197
+ y: g.start.view.y + centre.y - g.start.centre.y
198
+ };
199
+ show(clampView(dragged, g.box, content()), false);
200
+ }
201
+ },
202
+ onPanResponderRelease: event => {
203
+ const now = Date.now();
204
+ const {
205
+ pageX,
206
+ pageY
207
+ } = event.nativeEvent;
208
+ const isTap = !g.moved && now - g.grantedAt < DOUBLE_TAP_MS;
209
+ const last = g.lastTap;
210
+ if (isTap && last && now - last.at < DOUBLE_TAP_MS && Math.hypot(pageX - last.x, pageY - last.y) < DOUBLE_TAP_SLOP) {
211
+ g.lastTap = null;
212
+ // Zooming in keeps the tapped spot under the finger (as far as the edges allow).
213
+ const tapped = fromCentre({
214
+ x: pageX,
215
+ y: pageY
216
+ });
217
+ const target = g.view.scale > 1.01 ? {
218
+ scale: 1,
219
+ x: 0,
220
+ y: 0
221
+ } : zoomAround({
222
+ scale: 1,
223
+ x: 0,
224
+ y: 0
225
+ }, DOUBLE_TAP_ZOOM, tapped, tapped);
226
+ show(clampView(target, g.box, content()), true);
227
+ return;
228
+ }
229
+ g.lastTap = isTap ? {
230
+ at: now,
231
+ x: pageX,
232
+ y: pageY
233
+ } : null;
234
+ show(clampView(g.view, g.box, content()), true);
235
+ },
236
+ onPanResponderTerminate: () => show(clampView(g.view, g.box, content()), true)
237
+ })).current;
238
+ const onLayout = event => {
239
+ const {
240
+ width,
241
+ height
242
+ } = event.nativeEvent.layout;
243
+ g.box = {
244
+ width,
245
+ height
246
+ };
247
+ goNative();
248
+ boxRef.current?.measureInWindow?.((left, top) => {
249
+ g.origin = {
250
+ x: left,
251
+ y: top
252
+ };
253
+ });
254
+ };
255
+ const handleLoad = event => {
256
+ const source = event?.nativeEvent?.source;
257
+ if (source && source.width > 0 && source.height > 0) g.image = {
258
+ width: source.width,
259
+ height: source.height
260
+ };
261
+ onLoad();
262
+ };
263
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
264
+ ref: boxRef,
265
+ style: styles.box,
266
+ onLayout: onLayout,
267
+ ...pan.panHandlers,
268
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Animated.Image, {
269
+ source: {
270
+ uri
271
+ },
272
+ style: [styles.image, {
273
+ transform: [{
274
+ translateX: x
275
+ }, {
276
+ translateY: y
277
+ }, {
278
+ scale
279
+ }]
280
+ }],
281
+ resizeMode: "contain",
282
+ accessibilityLabel: accessibilityLabel,
283
+ accessibilityIgnoresInvertColors: true,
284
+ onLoad: handleLoad,
285
+ onError: onError
286
+ })
287
+ });
288
+ }
289
+ const styles = _reactNative.StyleSheet.create({
290
+ box: {
291
+ flex: 1,
292
+ overflow: 'hidden'
293
+ },
294
+ image: {
295
+ width: '100%',
296
+ height: '100%'
297
+ }
298
+ });
299
+ //# sourceMappingURL=ZoomableImage.js.map