@robono/messaging-react-native 0.9.0 → 0.10.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 (52) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/README.md +52 -0
  3. package/SBOM.spdx.json +494 -206
  4. package/dist/account.js +2 -1
  5. package/dist/attachment-menu.js +2 -1
  6. package/dist/call-message.js +2 -1
  7. package/dist/calling.js +4 -2
  8. package/dist/conversation-details.d.ts +1 -0
  9. package/dist/conversation-details.js +3 -2
  10. package/dist/custom-message.d.ts +7 -0
  11. package/dist/custom-message.js +53 -0
  12. package/dist/customization.d.ts +49 -0
  13. package/dist/customization.js +1 -0
  14. package/dist/expo-connected-calling.js +4 -2
  15. package/dist/expo-messenger.d.ts +4 -1
  16. package/dist/expo-messenger.js +34 -8
  17. package/dist/expo-screen.js +20 -18
  18. package/dist/expo-social.js +2 -1
  19. package/dist/extras.js +2 -1
  20. package/dist/forward.js +2 -1
  21. package/dist/group-editor.js +2 -1
  22. package/dist/header-calls.js +2 -1
  23. package/dist/icons.js +2 -1
  24. package/dist/inbox-search.d.ts +3 -1
  25. package/dist/inbox-search.js +30 -9
  26. package/dist/inbox-view.d.ts +21 -0
  27. package/dist/inbox-view.js +32 -0
  28. package/dist/inbox.d.ts +6 -1
  29. package/dist/inbox.js +24 -17
  30. package/dist/index.d.ts +10 -3
  31. package/dist/index.js +40 -29
  32. package/dist/maps-connection.js +2 -1
  33. package/dist/media-ui.js +2 -1
  34. package/dist/message-menu.js +2 -1
  35. package/dist/native-outbox.d.ts +1 -1
  36. package/dist/polls.js +4 -3
  37. package/dist/popup.js +2 -1
  38. package/dist/public-rooms.d.ts +10 -0
  39. package/dist/public-rooms.js +78 -0
  40. package/dist/quick-send.js +2 -1
  41. package/dist/room-moderation.d.ts +22 -0
  42. package/dist/room-moderation.js +87 -0
  43. package/dist/safety.js +2 -1
  44. package/dist/saved.js +2 -1
  45. package/dist/scheduled.js +2 -1
  46. package/dist/search.js +2 -1
  47. package/dist/social.js +2 -1
  48. package/dist/translation-settings.js +2 -1
  49. package/dist/typography.d.ts +12 -0
  50. package/dist/typography.js +31 -0
  51. package/dist/voice-composer.js +2 -1
  52. package/package.json +6 -6
@@ -0,0 +1,87 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ import { useEffect, useRef, useState } from 'react';
3
+ import { ActivityIndicator, FlatList, Image, Pressable, View } from 'react-native';
4
+ import { Text, TextInput } from './typography.js';
5
+ import { useRobonoLocale } from './localization.js';
6
+ import { MessagingPopup, messagingPopupColors as c } from './popup.js';
7
+ /** Owners/moderators see current roles; the service decides whether each change is allowed. */
8
+ export function MessagingRoomModeration({ client, userId, participants = {}, accent, onClose }) {
9
+ const { text } = useRobonoLocale();
10
+ const [rows, setRows] = useState([]), [next, setNext] = useState(null), [loading, setLoading] = useState(false), [busy, setBusy] = useState(false), [error, setError] = useState(''), [selected, setSelected] = useState(null), [confirm, setConfirm] = useState(null);
11
+ const generation = useRef(0), attempt = useRef(null), working = useRef(false);
12
+ const owner = client.getSnapshot().conversation?.roomRole === 'owner';
13
+ async function load(more = false) {
14
+ const gen = ++generation.current;
15
+ setLoading(true);
16
+ setError('');
17
+ try {
18
+ const page = await client.roomMembers(more && next ? next : undefined);
19
+ if (gen !== generation.current)
20
+ return;
21
+ setRows(current => [...new Map([...(more ? current : []), ...page.members].map(m => [m.userId, m])).values()]);
22
+ setNext(page.hasMore ? page.nextCursor : null);
23
+ }
24
+ catch (e) {
25
+ if (gen === generation.current)
26
+ setError(e instanceof Error ? e.message : text('Group members could not be loaded.'));
27
+ }
28
+ finally {
29
+ if (gen === generation.current)
30
+ setLoading(false);
31
+ }
32
+ }
33
+ useEffect(() => { void load(); return () => { ++generation.current; }; }, [client]);
34
+ async function apply() {
35
+ if (!confirm || working.current)
36
+ return;
37
+ working.current = true;
38
+ setBusy(true);
39
+ setError('');
40
+ const key = JSON.stringify(confirm);
41
+ if (attempt.current?.key !== key)
42
+ attempt.current = { key, id: client.createForwardId() };
43
+ try {
44
+ await client.moderateRoom({ ...confirm, requestId: attempt.current.id });
45
+ attempt.current = null;
46
+ setConfirm(null);
47
+ setSelected(null);
48
+ await load();
49
+ }
50
+ catch (e) {
51
+ setError(e instanceof Error ? e.message : text('This change could not be saved.'));
52
+ }
53
+ finally {
54
+ working.current = false;
55
+ setBusy(false);
56
+ }
57
+ }
58
+ const labels = { promote: 'Make moderator', demote: 'Remove moderator role', mute: 'Mute member', unmute: 'Unmute member', ban: 'Ban member', unban: 'Remove ban' };
59
+ return _jsxs(MessagingPopup, { title: text('Group moderation'), background: c.background, onClose: onClose, children: [confirm ? _jsxs(View, { style: { gap: 16, paddingVertical: 12 }, children: [_jsxs(Text, { style: { color: c.foreground, fontSize: 20, fontWeight: '600' }, children: [text(labels[confirm.action]), " \u00B7 ", participants[confirm.userId]?.name ?? confirm.userId] }), _jsx(Text, { style: { color: c.secondary, fontSize: 16, lineHeight: 23 }, children: text(confirm.action === 'ban' ? 'They will leave this group and cannot rejoin until the ban is removed.' : confirm.action === 'mute' ? 'They can read this group but cannot post or react.' : confirm.action === 'promote' ? 'They can mute and ban members. Only the owner can change moderator roles.' : confirm.action === 'unban' ? 'They can choose to join again. This does not automatically rejoin them.' : 'This changes their permissions in this group.') }), _jsxs(View, { style: { flexDirection: 'row', gap: 16 }, children: [_jsx(Pressable, { accessibilityRole: "button", disabled: busy, onPress: () => void apply(), style: { minHeight: 48, justifyContent: 'center', paddingHorizontal: 16, borderRadius: 12, backgroundColor: confirm.action === 'ban' ? '#b72d34' : accent }, children: _jsx(Text, { style: { color: confirm.action === 'ban' ? '#fff' : '#000', fontWeight: '600' }, children: busy ? text('Saving…') : text(labels[confirm.action]) }) }), _jsx(Pressable, { accessibilityRole: "button", disabled: busy, onPress: () => setConfirm(null), style: { minHeight: 48, justifyContent: 'center' }, children: _jsx(Text, { style: { color: accent }, children: text('Back') }) })] })] }) : _jsx(FlatList, { data: rows, keyExtractor: m => m.userId, style: { flex: 1 }, contentContainerStyle: { paddingHorizontal: 8, paddingBottom: 12 }, renderItem: ({ item }) => {
60
+ const allowed = item.userId !== userId && item.role !== 'owner' && (owner || item.role !== 'moderator');
61
+ const actions = item.banned ? ['unban'] : ['mute', 'ban'];
62
+ if (!item.banned && item.muted)
63
+ actions[0] = 'unmute';
64
+ if (owner && !item.banned)
65
+ actions.unshift(item.role === 'moderator' ? 'demote' : 'promote');
66
+ return _jsxs(View, { style: { borderBottomWidth: 1, borderBottomColor: '#ffffff20' }, children: [_jsxs(Pressable, { accessibilityRole: "button", accessibilityLabel: text('Manage {name}', { name: participants[item.userId]?.name ?? item.userId }), disabled: !allowed, onPress: () => setSelected(selected === item.userId ? null : item.userId), style: { minHeight: 64, flexDirection: 'row', alignItems: 'center', gap: 12 }, children: [participants[item.userId]?.avatarUrl && _jsx(Image, { source: { uri: participants[item.userId].avatarUrl }, style: { width: 36, height: 36, borderRadius: 18 } }), _jsxs(View, { style: { flex: 1, gap: 4 }, children: [_jsx(Text, { style: { color: c.foreground, fontSize: 17 }, children: item.userId === userId ? text('You') : participants[item.userId]?.name ?? item.userId }), _jsx(Text, { style: { color: c.secondary, fontSize: 14 }, children: text(item.banned ? 'Banned' : item.role === 'owner' ? 'Owner' : item.role === 'moderator' ? 'Moderator' : item.muted ? 'Muted' : 'Member') })] }), allowed && _jsx(Text, { style: { color: accent, fontSize: 15 }, children: text('Manage') })] }), allowed && selected === item.userId && _jsx(View, { style: { gap: 4, paddingBottom: 12 }, children: actions.map(action => _jsx(Pressable, { accessibilityRole: "button", onPress: () => { setError(''); setConfirm({ userId: item.userId, action }); }, style: { minHeight: 44, justifyContent: 'center', paddingHorizontal: 12, borderRadius: 10, backgroundColor: '#ffffff0c' }, children: _jsx(Text, { style: { color: action === 'ban' ? '#ff8282' : accent, fontSize: 16 }, children: text(labels[action]) }) }, action)) })] });
67
+ }, ListFooterComponent: _jsxs(View, { children: [loading && _jsx(ActivityIndicator, { color: accent, accessibilityLabel: text('Loading group members'), style: { padding: 16 } }), next && !loading && _jsx(Pressable, { accessibilityRole: "button", onPress: () => void load(true), style: { minHeight: 48, alignItems: 'center', justifyContent: 'center' }, children: _jsx(Text, { style: { color: accent }, children: text('Load more members') }) })] }) }), !!error && _jsxs(View, { style: { padding: 12, gap: 4 }, children: [_jsx(Text, { accessibilityRole: "alert", style: { color: c.foreground }, children: error }), !confirm && _jsx(Pressable, { accessibilityRole: "button", onPress: () => void load(), style: { minHeight: 44, justifyContent: 'center' }, children: _jsx(Text, { style: { color: accent }, children: text('Retry') }) })] })] });
68
+ }
69
+ export function MessagingRoomRemoval({ client, message, accent, onClose }) {
70
+ const { text } = useRobonoLocale();
71
+ const [reason, setReason] = useState(''), [busy, setBusy] = useState(false), [error, setError] = useState('');
72
+ const attempt = useRef(null), working = useRef(false);
73
+ async function remove() { if (working.current || !reason.trim())
74
+ return; working.current = true; setBusy(true); setError(''); if (attempt.current?.reason !== reason.trim())
75
+ attempt.current = { reason: reason.trim(), id: client.createForwardId() }; try {
76
+ await client.removeRoomMessage({ messageId: message.id, revision: message.revision ?? 0, reason: attempt.current.reason, requestId: attempt.current.id });
77
+ onClose();
78
+ }
79
+ catch (e) {
80
+ setError(e instanceof Error ? e.message : text('This message could not be removed.'));
81
+ }
82
+ finally {
83
+ working.current = false;
84
+ setBusy(false);
85
+ } }
86
+ return _jsx(MessagingPopup, { compact: true, title: text('Remove from group?'), background: c.background, closeDisabled: busy, onClose: onClose, children: _jsxs(View, { style: { gap: 12, paddingTop: 12 }, children: [_jsx(Text, { style: { color: c.secondary, fontSize: 16, lineHeight: 23 }, children: text('This removes the message for everyone in this group, even if it has been read. Your reason is kept in the moderation record.') }), _jsx(TextInput, { accessibilityLabel: text('Reason for removing message'), placeholder: text('Reason'), placeholderTextColor: c.secondary, multiline: true, maxLength: 1000, value: reason, onChangeText: setReason, editable: !busy, style: { color: c.foreground, fontSize: 16, minHeight: 80, padding: 12, borderWidth: 1, borderColor: c.secondary, borderRadius: 12 } }), !!error && _jsx(Text, { accessibilityRole: "alert", style: { color: c.foreground }, children: error }), _jsx(Pressable, { accessibilityRole: "button", accessibilityLabel: text('Remove message for group'), disabled: busy || !reason.trim(), onPress: () => void remove(), style: { minHeight: 48, borderRadius: 12, alignItems: 'center', justifyContent: 'center', backgroundColor: '#b72d34', opacity: busy || !reason.trim() ? .5 : 1 }, children: _jsx(Text, { style: { color: '#fff', fontSize: 16, fontWeight: '600' }, children: busy ? text('Removing…') : text('Remove message') }) })] }) });
87
+ }
package/dist/safety.js CHANGED
@@ -1,11 +1,12 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { Text, TextInput } from './typography.js';
2
3
  import { useRobonoLocale } from './localization.js';
3
4
  import { MessagingMenuSurface } from './popup.js';
4
5
  import { messagingPopupColors } from './popup.js';
5
6
  import { useEffect, useRef, useState } from 'react';
6
7
  import { MessagingPopup } from './popup.js';
7
8
  import { MessageIcon } from './icons.js';
8
- import { Platform, Pressable, ScrollView, Text, TextInput, View, useWindowDimensions } from 'react-native';
9
+ import { Platform, Pressable, ScrollView, View, useWindowDimensions } from 'react-native';
9
10
  /** Uses the authenticated messaging client's durable safety operations, or the host can keep its own UI. */
10
11
  export function MessagingSafety({ client, state, appearance, name, messageId, onClose, onSearch, onDetails, onPins, onScheduled, onSaved, children, menuAnchor }) {
11
12
  const { text: uiText, locale: uiLocale, number: uiNumber, count: uiCount } = useRobonoLocale();
package/dist/saved.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { Text } from './typography.js';
2
3
  import { useRobonoLocale } from './localization.js';
3
4
  import { messagingPopupColors } from './popup.js';
4
5
  import { useEffect, useRef, useState } from 'react';
5
- import { Platform, Pressable, ScrollView, Text, View } from 'react-native';
6
+ import { Platform, Pressable, ScrollView, View } from 'react-native';
6
7
  import { MessagingPopup } from './popup.js';
7
8
  import { MessageIcon } from './icons.js';
8
9
  /** Same app-sized popup and media viewer as the conversation; no server-history copy. */
package/dist/scheduled.js CHANGED
@@ -1,7 +1,8 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Text, TextInput } from './typography.js';
2
3
  import { useRobonoLocale } from './localization.js';
3
4
  import { useEffect, useRef, useState } from 'react';
4
- import { Pressable, ScrollView, Text, TextInput, View } from 'react-native';
5
+ import { Pressable, ScrollView, View } from 'react-native';
5
6
  import { MessagingPopup, messagingPopupColors as colors } from './popup.js';
6
7
  const pad = (n) => String(n).padStart(2, '0');
7
8
  /** Same bounded popup as the other Messenger tools; all dates are shown locally. */
package/dist/search.js CHANGED
@@ -1,7 +1,8 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Text, TextInput } from './typography.js';
2
3
  import { useRobonoLocale } from './localization.js';
3
4
  import { useEffect, useRef, useState } from 'react';
4
- import { ActivityIndicator, Pressable, Text, TextInput, View } from 'react-native';
5
+ import { ActivityIndicator, Pressable, View } from 'react-native';
5
6
  import { MessageIcon } from './icons.js';
6
7
  /** Messenger's in-conversation search: keep the chat visible and move between matches. */
7
8
  export function MessagingSearch({ client, appearance, onClose }) {
package/dist/social.js CHANGED
@@ -1,7 +1,8 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Text } from './typography.js';
2
3
  import { useRobonoLocale } from './localization.js';
3
4
  import { forwardRef, useImperativeHandle, useEffect, useState } from 'react';
4
- import { ActivityIndicator, AppState, Linking, Pressable, Text, View } from 'react-native';
5
+ import { ActivityIndicator, AppState, Linking, Pressable, View } from 'react-native';
5
6
  import { socialShare } from '@robono/messaging';
6
7
  import { MessagingPopup } from './popup.js';
7
8
  import { MessageIcon } from './icons.js';
@@ -1,9 +1,10 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Text } from './typography.js';
2
3
  import { useRobonoLocale } from './localization.js';
3
4
  import { MessageIcon } from './icons.js';
4
5
  import { messagingPopupColors } from './popup.js';
5
6
  import { useState } from 'react';
6
- import { Pressable, ScrollView, Text, View } from 'react-native';
7
+ import { Pressable, ScrollView, View } from 'react-native';
7
8
  import { normalizeLanguageCode, supportedLanguages, supportedLanguageLabel } from '@robono/messaging';
8
9
  /** The choice belongs to the conversation, never to an individual message. */
9
10
  export function TranslationSettings({ client, appearance, preference, language, sourceLanguage, voiceAvailable, onSaved }) {
@@ -0,0 +1,12 @@
1
+ import React from 'react';
2
+ import { Text as NativeText, TextInput as NativeTextInput, type TextProps, type TextInputProps } from 'react-native';
3
+ import type { MessagingAppearance } from '@robono/messaging';
4
+ /** Local to this Messenger. Does not change host-app text or device accessibility scaling. */
5
+ export declare function MessagingTypography({ appearance, children }: {
6
+ appearance: MessagingAppearance;
7
+ children: React.ReactNode;
8
+ }): React.JSX.Element;
9
+ export type Text = NativeText;
10
+ export declare const Text: React.ForwardRefExoticComponent<TextProps & React.RefAttributes<NativeText>>;
11
+ export type TextInput = NativeTextInput;
12
+ export declare const TextInput: React.ForwardRefExoticComponent<TextInputProps & React.RefAttributes<NativeTextInput>>;
@@ -0,0 +1,31 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { createContext, forwardRef, useContext, useMemo } from 'react';
3
+ import { Platform, StyleSheet, Text as NativeText, TextInput as NativeTextInput } from 'react-native';
4
+ const defaults = { fontFamily: undefined, scale: 1 };
5
+ const Typography = createContext(defaults), InsideText = createContext(false);
6
+ /** Local to this Messenger. Does not change host-app text or device accessibility scaling. */
7
+ export function MessagingTypography({ appearance, children }) {
8
+ const font = appearance.presentation?.font ?? 'system', scale = appearance.presentation?.textScale ?? 1;
9
+ const value = useMemo(() => ({ fontFamily: font === 'serif' ? Platform.OS === 'ios' ? 'Georgia' : Platform.OS === 'web' ? 'Georgia, "Times New Roman", serif' : 'serif' : font === 'monospace' ? Platform.OS === 'ios' ? 'Menlo' : Platform.OS === 'web' ? 'Menlo, Consolas, monospace' : 'monospace' : undefined, scale: [1, 1.15, 1.3].includes(scale) ? scale : 1 }), [font, scale]);
10
+ return _jsx(Typography.Provider, { value: value, children: children });
11
+ }
12
+ function useTextStyle(style, nested = false) {
13
+ const { fontFamily, scale } = useContext(Typography), flat = StyleSheet.flatten(style) ?? {};
14
+ const base = typeof flat.fontSize === 'number' ? flat.fontSize : nested ? undefined : 14;
15
+ const resolved = { ...(fontFamily ? { fontFamily } : {}), ...flat };
16
+ if (scale !== 1) {
17
+ if (base !== undefined)
18
+ resolved.fontSize = base * scale;
19
+ if (typeof flat.lineHeight === 'number')
20
+ resolved.lineHeight = flat.lineHeight * scale;
21
+ }
22
+ return resolved;
23
+ }
24
+ export const Text = forwardRef(function MessagingText({ style, children, ...props }, ref) {
25
+ const nested = useContext(InsideText), resolved = useTextStyle(style, nested);
26
+ return _jsx(InsideText.Provider, { value: true, children: _jsx(NativeText, { ...props, ref: ref, style: resolved, children: children }) });
27
+ });
28
+ export const TextInput = forwardRef(function MessagingTextInput({ style, ...props }, ref) {
29
+ const resolved = useTextStyle(style);
30
+ return _jsx(NativeTextInput, { ...props, ref: ref, style: resolved });
31
+ });
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Text } from './typography.js';
2
3
  import { useRobonoLocale } from './localization.js';
3
- import { ActivityIndicator, Pressable, Text, View } from 'react-native';
4
+ import { ActivityIndicator, Pressable, View } from 'react-native';
4
5
  import { MessageIcon, duration, composerSurface } from './icons.js';
5
6
  function Waveform({ samples = [], color }) {
6
7
  const { text: uiText, locale: uiLocale, number: uiNumber, count: uiCount } = useRobonoLocale();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@robono/messaging-react-native",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "description": "Optional, composable direct and group messaging screens for Robono.",
@@ -55,7 +55,7 @@
55
55
  "prepack": "npm run build && node ../../scripts/generate-sdk-file-sbom.mjs"
56
56
  },
57
57
  "peerDependencies": {
58
- "@robono/messaging": "^0.6.0",
58
+ "@robono/messaging": "^0.7.0",
59
59
  "react": ">=18.2 <20",
60
60
  "react-native": ">=0.76 <1",
61
61
  "expo-audio": ">=55 <57",
@@ -67,7 +67,7 @@
67
67
  "expo-crypto": ">=55 <57",
68
68
  "react-native-safe-area-context": ">=5 <6",
69
69
  "react-native-svg": ">=15 <16",
70
- "@robono/calling-react-native": "^0.7.0",
70
+ "@robono/calling-react-native": "^0.8.0",
71
71
  "expo-clipboard": ">=8 <57",
72
72
  "expo-image-manipulator": ">=14 <57",
73
73
  "expo-video-thumbnails": ">=14 <57",
@@ -75,12 +75,12 @@
75
75
  "expo-blur": ">=55 <57",
76
76
  "expo-notifications": ">=55 <57",
77
77
  "expo-secure-store": ">=55 <57",
78
- "@robono/location-react-native": "^0.3.0",
78
+ "@robono/location-react-native": "^0.4.0",
79
79
  "expo-location": ">=55 <57",
80
80
  "expo-constants": ">=55 <57",
81
- "@robono/location": "^0.3.0",
81
+ "@robono/location": "^0.4.0",
82
82
  "expo-haptics": ">=55 <57",
83
- "@robono/calling": "^0.4.0"
83
+ "@robono/calling": "^0.5.0"
84
84
  },
85
85
  "license": "SEE LICENSE IN LICENSE",
86
86
  "peerDependenciesMeta": {