@robono/messaging-react-native 0.3.0 → 0.3.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.1 — 2026-11-03
4
+
5
+ Match Messenger’s bottom communication tools: keep the surrounding area on the chat background, place attachment/text/microphone controls inside one rounded field, and keep Send or the quick reaction outside it. Apply the same neutral field to recording and voice-only mode.
6
+
3
7
  ## 0.3.0 — 2026-11-03
4
8
 
5
9
  Add the complete ExpoMessenger setup and supplied inbox search, contact/shared-media, group owner, replies and pinned-message screens. Reuse Messenger popup boundaries and composer. Add opt-in native Apple/Google notification registration with account-safe logout, and expiring local received history. Notification credentials remain on the customer server.
package/SBOM.spdx.json CHANGED
@@ -2,10 +2,10 @@
2
2
  "spdxVersion": "SPDX-2.3",
3
3
  "dataLicense": "CC0-1.0",
4
4
  "SPDXID": "SPDXRef-DOCUMENT",
5
- "name": "@robono/messaging-react-native@0.3.0",
6
- "documentNamespace": "https://robono.com/sbom/messaging-react-native/0.3.0/33652cfb3b8b9d54c0beb9c1800f5e1028d10801",
5
+ "name": "@robono/messaging-react-native@0.3.1",
6
+ "documentNamespace": "https://robono.com/sbom/messaging-react-native/0.3.1/e12a43a51836b6fb4b795f4fd364d7c879c993e7",
7
7
  "creationInfo": {
8
- "created": "2026-11-03T12:41:00.558Z",
8
+ "created": "2026-11-03T12:47:55.132Z",
9
9
  "creators": [
10
10
  "Organization: Robono (support@robono.com)"
11
11
  ]
@@ -14,11 +14,11 @@
14
14
  {
15
15
  "name": "@robono/messaging-react-native",
16
16
  "SPDXID": "SPDXRef-Package",
17
- "versionInfo": "0.3.0",
17
+ "versionInfo": "0.3.1",
18
18
  "downloadLocation": "NOASSERTION",
19
19
  "filesAnalyzed": true,
20
20
  "packageVerificationCode": {
21
- "packageVerificationCodeValue": "33652cfb3b8b9d54c0beb9c1800f5e1028d10801"
21
+ "packageVerificationCodeValue": "e12a43a51836b6fb4b795f4fd364d7c879c993e7"
22
22
  },
23
23
  "licenseConcluded": "LicenseRef-RobonoSDK",
24
24
  "licenseDeclared": "LicenseRef-RobonoSDK",
@@ -36,11 +36,11 @@
36
36
  "checksums": [
37
37
  {
38
38
  "algorithm": "SHA256",
39
- "checksumValue": "41f23b43c6807a9bec045f638878da86ff0aebaa10ad16d352b33d0b6c9da369"
39
+ "checksumValue": "c4503a7a48fbeff6b8fa3460c3f684c56dc0f167746cd337d14206a762dcf42d"
40
40
  },
41
41
  {
42
42
  "algorithm": "SHA1",
43
- "checksumValue": "567a2db84d992546cc234dcc727e34572589781d"
43
+ "checksumValue": "6f022a2582b75a8c92917afb4e62d6ea76fbe8c4"
44
44
  }
45
45
  ],
46
46
  "licenseConcluded": "LicenseRef-RobonoSDK",
@@ -986,11 +986,11 @@
986
986
  "checksums": [
987
987
  {
988
988
  "algorithm": "SHA256",
989
- "checksumValue": "c0f2253c4e4e639d2e3638e08785704062e95fe1026d0c8458a7b699e8627cee"
989
+ "checksumValue": "fa31ff34fc4687c06612cc4ee34c1e15b31e7a14582bc8a47439a52796242286"
990
990
  },
991
991
  {
992
992
  "algorithm": "SHA1",
993
- "checksumValue": "218282d102bbd803b5bfba2e46c582c2d83ed603"
993
+ "checksumValue": "db45cb7f9f55f4073ca9cb59987b7a595e255636"
994
994
  }
995
995
  ],
996
996
  "licenseConcluded": "LicenseRef-RobonoSDK",
@@ -1005,11 +1005,11 @@
1005
1005
  "checksums": [
1006
1006
  {
1007
1007
  "algorithm": "SHA256",
1008
- "checksumValue": "4f0af981f2b16804b863b58b17a911d472110af579c875e633fbb664a891884e"
1008
+ "checksumValue": "5fca47f6e70faf7f8ed7974d2ade04dedad3a031421460c432e4c76054a66310"
1009
1009
  },
1010
1010
  {
1011
1011
  "algorithm": "SHA1",
1012
- "checksumValue": "9ada021c29a2b0773fc32bc9c6cd6a02f287cdd7"
1012
+ "checksumValue": "003138e61e6889d1ff5559d657edc26e9bc81acd"
1013
1013
  }
1014
1014
  ],
1015
1015
  "licenseConcluded": "LicenseRef-RobonoSDK",
@@ -1119,11 +1119,11 @@
1119
1119
  "checksums": [
1120
1120
  {
1121
1121
  "algorithm": "SHA256",
1122
- "checksumValue": "0a66c7a0abff7daeb1d54b20b119b948e8f83fe0c1f8b35bda0736894d6369b3"
1122
+ "checksumValue": "be2538116816b2b3f9b8553577f832633676e09986e51c62d5ddd5e0f35ba06d"
1123
1123
  },
1124
1124
  {
1125
1125
  "algorithm": "SHA1",
1126
- "checksumValue": "3d5506c83b97aa6f917d70f8529130da42c86f86"
1126
+ "checksumValue": "6a3ef9311c29859f6a09960a42424402d0a6163b"
1127
1127
  }
1128
1128
  ],
1129
1129
  "licenseConcluded": "LicenseRef-RobonoSDK",
@@ -2145,11 +2145,11 @@
2145
2145
  "checksums": [
2146
2146
  {
2147
2147
  "algorithm": "SHA256",
2148
- "checksumValue": "947b96420b90523c19aacae354285cb26e4b7002c53adf7e2f02b541478d1e0a"
2148
+ "checksumValue": "024e84ddb02f1574ce9909ec6d179fa6385d74400b65b6a659ff1a1403ebdb08"
2149
2149
  },
2150
2150
  {
2151
2151
  "algorithm": "SHA1",
2152
- "checksumValue": "6eaf7de177ab354469c2988cb6439f714059f35f"
2152
+ "checksumValue": "39d14d7d7fa380321ef3733c6778d15fe58064c2"
2153
2153
  }
2154
2154
  ],
2155
2155
  "licenseConcluded": "LicenseRef-RobonoSDK",
@@ -2259,11 +2259,11 @@
2259
2259
  "checksums": [
2260
2260
  {
2261
2261
  "algorithm": "SHA256",
2262
- "checksumValue": "5d45645613a1016d7e534d30d51f5d45dcc7b58b2d43cb9c0b48bf15df474c50"
2262
+ "checksumValue": "a1b6ec7d61021c4af7d5a7def803f80fbf648790123b98298375573cddeacf96"
2263
2263
  },
2264
2264
  {
2265
2265
  "algorithm": "SHA1",
2266
- "checksumValue": "de403cac4a3e06f47066c8498015e049574b817f"
2266
+ "checksumValue": "730c85cc174ed787867f6b0090fa6ad1db375c39"
2267
2267
  }
2268
2268
  ],
2269
2269
  "licenseConcluded": "LicenseRef-RobonoSDK",
package/dist/icons.d.ts CHANGED
@@ -5,3 +5,5 @@ export declare function duration(ms?: number): string;
5
5
  export declare function fileSize(bytes?: number): string;
6
6
  /** Neutral app chrome stays legible for light and dark customer palettes. */
7
7
  export declare function chatSurface(background: string): "#1e262b" | "#ffffff";
8
+ /** Messenger shades the rounded input field, while the surrounding tools use the chat background. */
9
+ export declare function composerSurface(background: string): "#ffffff" | "#111111";
package/dist/icons.js CHANGED
@@ -8,3 +8,5 @@ export function chatSurface(background) {
8
8
  const rgb = background.slice(1).match(/../g)?.map(v => parseInt(v, 16)) ?? [255, 255, 255];
9
9
  return rgb[0] * 0.299 + rgb[1] * 0.587 + rgb[2] * 0.114 < 128 ? "#1e262b" : "#ffffff";
10
10
  }
11
+ /** Messenger shades the rounded input field, while the surrounding tools use the chat background. */
12
+ export function composerSurface(background) { return chatSurface(background) === "#ffffff" ? "#ffffff" : "#111111"; }
package/dist/index.js CHANGED
@@ -25,7 +25,7 @@ export { PollMessage, PollComposer } from "./polls.js";
25
25
  import { createContext, useContext, useEffect, useRef, useState, useSyncExternalStore, } from "react";
26
26
  import { Animated, ActivityIndicator, AppState, BackHandler, FlatList, Image, KeyboardAvoidingView, Keyboard, Linking, Modal, Platform, Pressable, ScrollView, Text, TextInput, View, useWindowDimensions, } from "react-native";
27
27
  import { useQuoteGesture, messageInfoRevealWidth, messageReplyRevealWidth } from "./use-quote-gesture.js";
28
- import { MessageIcon, duration, fileSize, chatSurface } from "./icons.js";
28
+ import { MessageIcon, duration, fileSize, chatSurface, composerSurface } from "./icons.js";
29
29
  import { VoiceMessageComposer } from "./voice-composer.js";
30
30
  export { VoiceMessageComposer, VoiceDraftPlayback } from "./voice-composer.js";
31
31
  import { VoicePlayback } from "./media-ui.js";
@@ -692,7 +692,7 @@ export function MessagingComposer() {
692
692
  setAttachmentFilter(null); }, [attachmentsOpen]);
693
693
  useEffect(() => { if (attachmentFilter && !state.settings?.features[attachmentFilter])
694
694
  setAttachmentFilter(null); }, [attachmentFilter, state.settings]);
695
- const [inputHeight, setInputHeight] = useState(42);
695
+ const [inputHeight, setInputHeight] = useState(44);
696
696
  useEffect(() => { if (!draft)
697
697
  setInputHeight(42); }, [draft]);
698
698
  const inFlight = useRef(false);
@@ -854,18 +854,18 @@ export function MessagingComposer() {
854
854
  }
855
855
  function renderTextComposer(voice = { hasVoice: false, blocked: false }) {
856
856
  const blocked = voice.blocked || busy;
857
- return _jsxs(View, { style: { flexDirection: 'row', alignItems: 'flex-end', gap: 2 }, children: [(camera || library.image || library.video || library.document || !editing && (features?.poll || weatherAllowed)) && _jsx(Pressable, { accessibilityRole: "button", accessibilityLabel: "Attach", accessibilityState: { expanded: attachmentsOpen }, disabled: busy || blocked, onPress: () => { if (attachmentsOpen) {
858
- setAttachmentsOpen(false);
859
- input.current?.focus();
860
- }
861
- else {
862
- Keyboard.dismiss();
863
- setAttachmentsOpen(true);
864
- } }, style: { width: 40, height: 44, alignItems: 'center', justifyContent: 'center' }, children: _jsx(MessageIcon, { name: attachmentsOpen ? "keyboard" : "plus", color: c.foreground, size: 27 }) }), features?.text && _jsx(View, { style: { flex: 1, minWidth: 0, flexDirection: 'row', alignItems: 'flex-end', borderWidth: 1, borderColor: c.secondary + '30', backgroundColor: chatSurface(c.background), borderRadius: 23 }, children: _jsx(TextInput, { ref: input, accessibilityLabel: design.labels.compose, placeholder: design.labels.compose, placeholderTextColor: c.secondary, value: draft, editable: !blocked, onChangeText: setDraft, onContentSizeChange: e => setInputHeight(Math.max(42, Math.min(120, e.nativeEvent.contentSize.height))), multiline: true, numberOfLines: 1, maxLength: 10000, ...(Platform.OS === 'web' ? { onKeyPress: (event) => { const native = event.nativeEvent; if (native.key === 'Enter' && !native.shiftKey && !native.isComposing && native.keyCode !== 229) {
865
- event.preventDefault();
866
- if (!blocked)
867
- void send();
868
- } } } : {}), onFocus: () => setAttachmentsOpen(false), style: { ...(Platform.OS === 'web' ? { outlineStyle: 'none' } : {}), flex: 1, minWidth: 0, minHeight: 42, height: inputHeight, maxHeight: 120, color: c.foreground, fontSize: 16, lineHeight: 21, paddingHorizontal: 13, paddingVertical: 10 } }) }), !features?.text && _jsx(View, { style: { flex: 1 } }), features?.voice && _jsx(Pressable, { accessibilityRole: "button", accessibilityLabel: "Voice message", disabled: voicePending || busy || blocked, onPress: () => void pick('voice'), style: { width: 44, height: 44, alignItems: 'center', justifyContent: 'center', opacity: voicePending || busy || blocked ? 0.5 : 1 }, children: _jsx(MessageIcon, { name: "mic", color: c.foreground, size: 25 }) }), (features?.text || selected.length > 0) && _jsx(QuickSendButton, { choice: state.quickMessage, empty: !draft.trim() && !selected.length && !voice.hasVoice && !voice.sendVoice, enabled: !!features?.text, disabled: blocked, editing: !!editing, sendAvailable: !!draft.trim() || !!voice.sendVoice || selected.length > 0, appearance: design, onChoose: emoji => { void client.setQuickMessage(emoji).catch(() => setError('This quick message could not be saved on this device.')); }, onSend: () => { if (voice.sendVoice)
857
+ return _jsxs(View, { style: { flexDirection: 'row', alignItems: 'flex-end', gap: 4 }, children: [_jsxs(View, { testID: "messaging-composer-field", style: { flex: 1, minWidth: 0, minHeight: 44, borderRadius: 22, backgroundColor: composerSurface(c.background), flexDirection: 'row', alignItems: 'flex-end', overflow: 'hidden' }, children: [(camera || library.image || library.video || library.document || !editing && (features?.poll || weatherAllowed)) && _jsx(Pressable, { accessibilityRole: "button", accessibilityLabel: "Attach", accessibilityState: { expanded: attachmentsOpen }, disabled: busy || blocked, onPress: () => { if (attachmentsOpen) {
858
+ setAttachmentsOpen(false);
859
+ input.current?.focus();
860
+ }
861
+ else {
862
+ Keyboard.dismiss();
863
+ setAttachmentsOpen(true);
864
+ } }, style: { width: 42, height: 44, flexShrink: 0, alignItems: 'center', justifyContent: 'center' }, children: _jsx(MessageIcon, { name: attachmentsOpen ? "keyboard" : "plus", color: c.accent, size: 25 }) }), features?.text && _jsx(TextInput, { ref: input, accessibilityLabel: design.labels.compose, placeholder: design.labels.compose, placeholderTextColor: c.secondary, value: draft, editable: !blocked, onChangeText: setDraft, onContentSizeChange: e => setInputHeight(Math.max(44, Math.min(120, e.nativeEvent.contentSize.height))), multiline: true, numberOfLines: 1, maxLength: 10000, ...(Platform.OS === 'web' ? { onKeyPress: (event) => { const native = event.nativeEvent; if (native.key === 'Enter' && !native.shiftKey && !native.isComposing && native.keyCode !== 229) {
865
+ event.preventDefault();
866
+ if (!blocked)
867
+ void send();
868
+ } } } : {}), onFocus: () => setAttachmentsOpen(false), style: { ...(Platform.OS === 'web' ? { outlineStyle: 'none' } : {}), flex: 1, minWidth: 0, minHeight: 44, height: inputHeight, maxHeight: 120, color: c.foreground, fontSize: 16, lineHeight: 22, paddingHorizontal: 4, paddingVertical: 8, backgroundColor: 'transparent' } }), !features?.text && _jsx(View, { style: { flex: 1 } }), features?.voice && _jsx(Pressable, { accessibilityRole: "button", accessibilityLabel: "Voice message", disabled: voicePending || busy || blocked, onPress: () => void pick('voice'), style: { width: 42, height: 44, flexShrink: 0, alignItems: 'center', justifyContent: 'center', opacity: voicePending || busy || blocked ? 0.5 : 1 }, children: _jsx(MessageIcon, { name: "mic", color: c.accent, size: 23 }) })] }), (features?.text || selected.length > 0) && _jsx(QuickSendButton, { choice: state.quickMessage, empty: !draft.trim() && !selected.length && !voice.hasVoice && !voice.sendVoice, enabled: !!features?.text, disabled: blocked, editing: !!editing, sendAvailable: !!draft.trim() || !!voice.sendVoice || selected.length > 0, appearance: design, onChoose: emoji => { void client.setQuickMessage(emoji).catch(() => setError('This quick message could not be saved on this device.')); }, onSend: () => { if (voice.sendVoice)
869
869
  voice.sendVoice();
870
870
  else
871
871
  void send(); }, onQuickSend: emoji => { void submit({ kind: 'text', text: emoji }).catch(() => { }); } }, state.conversationId)] });
@@ -873,7 +873,7 @@ export function MessagingComposer() {
873
873
  if (!enabled || state.conversation?.canSend === false) {
874
874
  return (_jsx(Text, { style: { padding: 16, color: c.secondary }, children: state.offline ? 'Offline · Recent messages only. Connect to send and load more.' : state.conversation?.safety?.blockedByMe ? 'You blocked this contact. Use contact options to unblock.' : state.conversation?.canSend === false ? (state.conversation.kind === 'group' ? 'Only members allowed to send can post in this group.' : 'Messaging is unavailable for this conversation.') : design.labels.unavailable }));
875
875
  }
876
- return (_jsxs(View, { style: { paddingHorizontal: 6, paddingTop: 5, paddingBottom: 6, gap: 6, backgroundColor: chatSurface(c.background) }, children: [mentionSuggestions.length > 0 && _jsx(ScrollView, { keyboardShouldPersistTaps: "handled", style: { maxHeight: 144 }, children: mentionSuggestions.map(id => _jsx(Pressable, { accessibilityRole: "button", accessibilityLabel: 'Mention ' + participants[id].name, onPress: () => chooseMention(id), style: { minHeight: 44, paddingHorizontal: 12, justifyContent: 'center', borderBottomWidth: .5, borderBottomColor: c.secondary + '30' }, children: _jsxs(Text, { style: { color: c.accent, fontSize: 16 }, children: ["@", participants[id].name] }) }, id)) }), mentionQuery !== undefined && !!mentionError && _jsx(Text, { style: { color: c.secondary, fontSize: 13 }, children: mentionError }), editing && _jsxs(View, { style: { flexDirection: 'row', alignItems: 'center', gap: 8 }, children: [_jsxs(Text, { numberOfLines: 1, style: { flex: 1, color: c.secondary }, children: ["Editing: ", editing.text || editing.media?.fileName] }), _jsx(Pressable, { accessibilityRole: "button", accessibilityLabel: "Cancel editing", disabled: busy, onPress: () => setEditing(null), style: { minHeight: 36, justifyContent: 'center' }, children: _jsx(Text, { style: { color: c.accent }, children: "Cancel" }) })] }), !editing && reply && (_jsxs(View, { testID: "composer-quote", style: { flexDirection: "row", gap: 8 }, children: [_jsx(View, { style: { flex: 1 }, children: _jsx(QuotedPreview, { message: reply, ink: c.secondary }) }), _jsx(Pressable, { accessibilityRole: "button", accessibilityLabel: "Cancel quote", onPress: () => setReply(null), children: _jsx(Text, { style: { color: c.foreground }, children: "Cancel" }) })] })), state.settings?.history?.mode === 'last_two' && _jsx(Text, { style: { color: c.secondary, fontSize: 11, textAlign: 'center' }, children: "Only the latest two messages are kept." }), editing ? _jsxs(_Fragment, { children: [renderAttachments(), renderTextComposer()] }) : features?.voice ? _jsx(VoiceComposer, { appearance: design, maxSeconds: state.settings?.voice?.maxDurationSeconds ?? 60, busy: busy, onRecord: () => { Keyboard.dismiss(); void pick('voice'); }, renderAttachments: renderAttachments, renderTextComposer: features?.text || selected.length ? renderTextComposer : undefined, actions: _jsx(_Fragment, { children: (camera || library.image || library.video || library.document || !editing && (features?.poll || weatherAllowed)) && _jsx(Pressable, { accessibilityRole: "button", accessibilityLabel: "Attach", accessibilityState: { expanded: attachmentsOpen }, disabled: busy, onPress: () => setAttachmentsOpen(!attachmentsOpen), style: { width: 44, height: 44, alignItems: 'center', justifyContent: 'center' }, children: _jsx(MessageIcon, { name: attachmentsOpen ? 'close' : 'plus', color: c.foreground, size: 25 }) }) }) }) : _jsxs(_Fragment, { children: [renderAttachments(), renderTextComposer()] }), attachmentsOpen && (camera || library.image || library.video || library.document || features?.poll || weatherAllowed) && _jsxs(View, { accessibilityLabel: "Attachments", style: { paddingTop: 24, paddingBottom: 16, paddingHorizontal: 8, backgroundColor: chatSurface(c.background) }, children: [_jsxs(View, { style: { flexDirection: 'row', flexWrap: 'wrap', justifyContent: 'flex-start' }, children: [['image', 'video', 'camera', 'document'].filter(kind => kind === 'camera' ? camera : library[kind]).map(kind => {
876
+ return (_jsxs(View, { testID: "messaging-composer", style: { paddingStart: 8, paddingEnd: 0, paddingVertical: 4, gap: 8, backgroundColor: c.background }, children: [mentionSuggestions.length > 0 && _jsx(ScrollView, { keyboardShouldPersistTaps: "handled", style: { maxHeight: 144 }, children: mentionSuggestions.map(id => _jsx(Pressable, { accessibilityRole: "button", accessibilityLabel: 'Mention ' + participants[id].name, onPress: () => chooseMention(id), style: { minHeight: 44, paddingHorizontal: 12, justifyContent: 'center', borderBottomWidth: .5, borderBottomColor: c.secondary + '30' }, children: _jsxs(Text, { style: { color: c.accent, fontSize: 16 }, children: ["@", participants[id].name] }) }, id)) }), mentionQuery !== undefined && !!mentionError && _jsx(Text, { style: { color: c.secondary, fontSize: 13 }, children: mentionError }), editing && _jsxs(View, { style: { flexDirection: 'row', alignItems: 'center', gap: 8 }, children: [_jsxs(Text, { numberOfLines: 1, style: { flex: 1, color: c.secondary }, children: ["Editing: ", editing.text || editing.media?.fileName] }), _jsx(Pressable, { accessibilityRole: "button", accessibilityLabel: "Cancel editing", disabled: busy, onPress: () => setEditing(null), style: { minHeight: 36, justifyContent: 'center' }, children: _jsx(Text, { style: { color: c.accent }, children: "Cancel" }) })] }), !editing && reply && (_jsxs(View, { testID: "composer-quote", style: { flexDirection: "row", gap: 8 }, children: [_jsx(View, { style: { flex: 1 }, children: _jsx(QuotedPreview, { message: reply, ink: c.secondary }) }), _jsx(Pressable, { accessibilityRole: "button", accessibilityLabel: "Cancel quote", onPress: () => setReply(null), children: _jsx(Text, { style: { color: c.foreground }, children: "Cancel" }) })] })), state.settings?.history?.mode === 'last_two' && _jsx(Text, { style: { color: c.secondary, fontSize: 11, textAlign: 'center' }, children: "Only the latest two messages are kept." }), editing ? _jsxs(_Fragment, { children: [renderAttachments(), renderTextComposer()] }) : features?.voice ? _jsx(VoiceComposer, { appearance: design, maxSeconds: state.settings?.voice?.maxDurationSeconds ?? 60, busy: busy, onRecord: () => { Keyboard.dismiss(); void pick('voice'); }, renderAttachments: renderAttachments, renderTextComposer: features?.text || selected.length ? renderTextComposer : undefined, actions: _jsx(_Fragment, { children: (camera || library.image || library.video || library.document || !editing && (features?.poll || weatherAllowed)) && _jsx(Pressable, { accessibilityRole: "button", accessibilityLabel: "Attach", accessibilityState: { expanded: attachmentsOpen }, disabled: busy, onPress: () => setAttachmentsOpen(!attachmentsOpen), style: { width: 44, height: 44, alignItems: 'center', justifyContent: 'center' }, children: _jsx(MessageIcon, { name: attachmentsOpen ? 'close' : 'plus', color: c.foreground, size: 25 }) }) }) }) : _jsxs(_Fragment, { children: [renderAttachments(), renderTextComposer()] }), attachmentsOpen && (camera || library.image || library.video || library.document || features?.poll || weatherAllowed) && _jsxs(View, { accessibilityLabel: "Attachments", style: { paddingTop: 24, paddingBottom: 16, paddingHorizontal: 8, backgroundColor: chatSurface(c.background) }, children: [_jsxs(View, { style: { flexDirection: 'row', flexWrap: 'wrap', justifyContent: 'flex-start' }, children: [['image', 'video', 'camera', 'document'].filter(kind => kind === 'camera' ? camera : library[kind]).map(kind => {
877
877
  const color = { image: '#1677e8', video: '#7b58cf', camera: c.accent, document: '#079ec3' }[kind];
878
878
  const label = { image: 'Photos', video: 'Videos', camera: 'Camera', document: 'Files' }[kind];
879
879
  return _jsxs(Pressable, { accessibilityRole: "button", accessibilityLabel: kind === 'camera' ? 'Camera' : mediaNames[kind], accessibilityState: { selected: attachmentFilter === kind }, disabled: busy, onPress: () => { if (kind === 'camera')
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { ActivityIndicator, Pressable, Text, View } from 'react-native';
3
- import { MessageIcon, duration } from './icons.js';
3
+ import { MessageIcon, duration, composerSurface } from './icons.js';
4
4
  function Waveform({ samples = [], color }) {
5
5
  return _jsx(View, { accessibilityLabel: "Recording waveform", style: { flex: 1, minWidth: 0, height: 24, flexDirection: 'row', alignItems: 'center', gap: 2, overflow: 'hidden' }, children: Array.from({ length: 28 }, (_, i) => { const value = samples[Math.floor(i * samples.length / 28)] ?? 0; return _jsx(View, { style: { flex: 1, borderRadius: 2, height: 3 + Math.max(0, Math.min(1, value)) * 21, backgroundColor: color, opacity: samples.length ? .8 : .25 } }, i); }) });
6
6
  }
@@ -10,7 +10,7 @@ export function VoiceMessageComposer({ appearance, maxSeconds, busy, onRecord, a
10
10
  const canSend = !!review && (phase === 'review' || phase === 'failed') && !!onSend;
11
11
  if (renderTextComposer && !recording && phase !== 'preparing' && phase !== 'uploading')
12
12
  return _jsxs(View, { style: { gap: 4 }, children: [renderAttachments ? renderAttachments(review, waiting) : review && _jsx(View, { style: { paddingHorizontal: 8, paddingTop: 4 }, children: review }), renderTextComposer({ sendVoice: canSend ? onSend : undefined, hasVoice: !!review, blocked: waiting }), !!error && _jsxs(View, { style: { flexDirection: 'row', alignItems: 'center', gap: 8, paddingHorizontal: 8 }, children: [_jsx(Text, { accessibilityRole: "alert", style: { color: c.foreground, fontSize: 12, flex: 1 }, children: error }), onRemove && _jsx(Pressable, { accessibilityRole: "button", accessibilityLabel: "Cancel recording", onPress: onRemove, style: { padding: 8 }, children: _jsx(MessageIcon, { name: "close", color: c.foreground, size: 18 }) })] })] });
13
- return _jsxs(View, { testID: "voice-only-composer", style: { gap: 5 }, children: [renderAttachments?.(review, waiting || recording), _jsxs(View, { style: { flexDirection: 'row', alignItems: 'flex-end', gap: 4 }, children: [_jsxs(View, { style: { flex: 1, minWidth: 0, minHeight: 44, borderRadius: 22, borderWidth: 1, borderColor: c.secondary + '20', backgroundColor: c.incoming, flexDirection: 'row', alignItems: 'center', overflow: 'hidden' }, children: [!recording && !review && !waiting && actions, review && !renderAttachments ? _jsx(View, { style: { flex: 1, minWidth: 0, paddingLeft: 10 }, children: review }) : recording ? _jsxs(View, { style: { flex: 1, minWidth: 0, flexDirection: 'row', alignItems: 'center', gap: 6, paddingLeft: 10 }, children: [_jsx(Text, { style: { fontSize: 11, fontWeight: '800', color: c.incomingText, fontVariant: ['tabular-nums'] }, children: duration(Math.floor(elapsedMs / 1000) * 1000) }), appearance.showWaveform !== false ? _jsx(Waveform, { samples: waveform, color: c.accent }) : _jsx(View, { style: { flex: 1 } }), _jsx(Text, { style: { fontSize: 11, fontWeight: '800', color: c.incomingText, fontVariant: ['tabular-nums'] }, children: duration(maxSeconds * 1000) })] }) : _jsx(View, { style: { flex: 1, minWidth: 0, paddingHorizontal: 12 }, children: _jsx(Text, { style: { color: c.secondary, fontSize: 14, fontWeight: '800' }, children: waiting ? (phase === 'uploading' ? 'Sending…' : 'Preparing…') : `Voice up to ${maxSeconds}s` }) }), _jsx(Pressable, { accessibilityRole: "button", accessibilityLabel: recording ? 'Stop recording' : 'Voice message', disabled: waiting || !!review || phase === 'failed', onPress: recording ? onStop : onRecord, style: { width: 42, height: 44, alignItems: 'center', justifyContent: 'center', opacity: (waiting || !!review || phase === 'failed') ? .45 : 1 }, children: waiting ? _jsx(ActivityIndicator, { size: "small", color: c.accent }) : _jsx(MessageIcon, { name: "mic", color: recording ? '#c94040' : c.accent, size: 23 }) })] }), _jsx(Pressable, { accessibilityRole: "button", accessibilityLabel: canSend ? 'Send voice message' : 'Send unavailable', accessibilityState: { disabled: !canSend }, disabled: !canSend, onPress: onSend, style: { width: 44, height: 44, alignItems: 'center', justifyContent: 'center', opacity: canSend ? 1 : .4 }, children: _jsx(MessageIcon, { name: "send", color: c.accent, size: 24 }) })] }), !!error && _jsxs(View, { style: { flexDirection: 'row', alignItems: 'center', gap: 8, paddingHorizontal: 8 }, children: [_jsx(Text, { accessibilityRole: "alert", style: { color: c.foreground, fontSize: 12, flex: 1 }, children: error }), onRemove && _jsx(Pressable, { accessibilityRole: "button", accessibilityLabel: "Cancel recording", onPress: onRemove, style: { padding: 8 }, children: _jsx(MessageIcon, { name: "close", color: c.foreground, size: 18 }) })] })] });
13
+ return _jsxs(View, { testID: "voice-only-composer", style: { gap: 5 }, children: [renderAttachments?.(review, waiting || recording), _jsxs(View, { style: { flexDirection: 'row', alignItems: 'flex-end', gap: 4 }, children: [_jsxs(View, { style: { flex: 1, minWidth: 0, minHeight: 44, borderRadius: 22, backgroundColor: composerSurface(c.background), flexDirection: 'row', alignItems: 'center', overflow: 'hidden' }, children: [!recording && !review && !waiting && actions, review && !renderAttachments ? _jsx(View, { style: { flex: 1, minWidth: 0, paddingLeft: 10 }, children: review }) : recording ? _jsxs(View, { style: { flex: 1, minWidth: 0, flexDirection: 'row', alignItems: 'center', gap: 6, paddingLeft: 10 }, children: [_jsx(Text, { style: { fontSize: 11, fontWeight: '800', color: c.incomingText, fontVariant: ['tabular-nums'] }, children: duration(Math.floor(elapsedMs / 1000) * 1000) }), appearance.showWaveform !== false ? _jsx(Waveform, { samples: waveform, color: c.accent }) : _jsx(View, { style: { flex: 1 } }), _jsx(Text, { style: { fontSize: 11, fontWeight: '800', color: c.incomingText, fontVariant: ['tabular-nums'] }, children: duration(maxSeconds * 1000) })] }) : _jsx(View, { style: { flex: 1, minWidth: 0, paddingHorizontal: 12 }, children: _jsx(Text, { style: { color: c.secondary, fontSize: 14, fontWeight: '800' }, children: waiting ? (phase === 'uploading' ? 'Sending…' : 'Preparing…') : `Voice up to ${maxSeconds}s` }) }), _jsx(Pressable, { accessibilityRole: "button", accessibilityLabel: recording ? 'Stop recording' : 'Voice message', disabled: waiting || !!review || phase === 'failed', onPress: recording ? onStop : onRecord, style: { width: 42, height: 44, alignItems: 'center', justifyContent: 'center', opacity: (waiting || !!review || phase === 'failed') ? .45 : 1 }, children: waiting ? _jsx(ActivityIndicator, { size: "small", color: c.accent }) : _jsx(MessageIcon, { name: "mic", color: recording ? '#c94040' : c.accent, size: 23 }) })] }), _jsx(Pressable, { accessibilityRole: "button", accessibilityLabel: canSend ? 'Send voice message' : 'Send unavailable', accessibilityState: { disabled: !canSend }, disabled: !canSend, onPress: onSend, style: { width: 44, height: 44, alignItems: 'center', justifyContent: 'center', opacity: canSend ? 1 : .4 }, children: _jsx(MessageIcon, { name: "send", color: c.accent, size: 24 }) })] }), !!error && _jsxs(View, { style: { flexDirection: 'row', alignItems: 'center', gap: 8, paddingHorizontal: 8 }, children: [_jsx(Text, { accessibilityRole: "alert", style: { color: c.foreground, fontSize: 12, flex: 1 }, children: error }), onRemove && _jsx(Pressable, { accessibilityRole: "button", accessibilityLabel: "Cancel recording", onPress: onRemove, style: { padding: 8 }, children: _jsx(MessageIcon, { name: "close", color: c.foreground, size: 18 }) })] })] });
14
14
  }
15
15
  export function VoiceDraftPlayback({ appearance, playing, positionMs = 0, durationMs = 0, waveform, onToggle, onRemove, disabled = false, error, compact = false }) {
16
16
  const c = appearance.colors;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@robono/messaging-react-native",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "description": "Optional, composable direct and group messaging screens for Robono.",