@adminide-stack/yantra-mobile 12.0.105-alpha.0 → 12.0.106-alpha.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 (33) hide show
  1. package/lib/features/messaging/BlockedContactsScreen.js +127 -0
  2. package/lib/features/messaging/BlockedContactsScreen.js.map +1 -0
  3. package/lib/features/messaging/ChatAttachments.js +4 -2
  4. package/lib/features/messaging/ChatAttachments.js.map +1 -1
  5. package/lib/features/messaging/ChatComponents.js +194 -10
  6. package/lib/features/messaging/ChatComponents.js.map +1 -1
  7. package/lib/features/messaging/ChatInfoScreen.js +173 -17
  8. package/lib/features/messaging/ChatInfoScreen.js.map +1 -1
  9. package/lib/features/messaging/ConversationListScreen.js +17 -1
  10. package/lib/features/messaging/ConversationListScreen.js.map +1 -1
  11. package/lib/features/messaging/MediaGalleryScreen.js +264 -0
  12. package/lib/features/messaging/MediaGalleryScreen.js.map +1 -0
  13. package/lib/features/messaging/NativeChatExperience.js +137 -18
  14. package/lib/features/messaging/NativeChatExperience.js.map +1 -1
  15. package/lib/features/messaging/NewGroupScreen.js +191 -0
  16. package/lib/features/messaging/NewGroupScreen.js.map +1 -0
  17. package/lib/features/messaging/PollComposer.js +137 -0
  18. package/lib/features/messaging/PollComposer.js.map +1 -0
  19. package/lib/features/messaging/PollVotesScreen.js +122 -0
  20. package/lib/features/messaging/PollVotesScreen.js.map +1 -0
  21. package/lib/features/messaging/ThreadScreen.js +180 -13
  22. package/lib/features/messaging/ThreadScreen.js.map +1 -1
  23. package/lib/features/messaging/chatLinks.js +39 -2
  24. package/lib/features/messaging/chatLinks.js.map +1 -1
  25. package/lib/features/messaging/chatModel.js +186 -19
  26. package/lib/features/messaging/chatModel.js.map +1 -1
  27. package/lib/features/messaging/chatOperations.js +111 -2
  28. package/lib/features/messaging/chatOperations.js.map +1 -1
  29. package/lib/features/messaging/nativeChatChannelQuery.js +7 -2
  30. package/lib/features/messaging/nativeChatChannelQuery.js.map +1 -1
  31. package/lib/features/messaging/useChatData.js +248 -10
  32. package/lib/features/messaging/useChatData.js.map +1 -1
  33. package/package.json +3 -3
@@ -0,0 +1,137 @@
1
+ import {jsx,jsxs}from'react/jsx-runtime';import*as React from'react';import {StyleSheet,Modal,View,Pressable,Text,KeyboardAvoidingView,ScrollView,TextInput,Switch,Platform}from'react-native';import {SafeAreaView}from'react-native-safe-area-context';import {MaterialCommunityIcons}from'@expo/vector-icons';const MAX_OPTIONS = 12;
2
+ const QUESTION_MAX = 255;
3
+ const OPTION_MAX = 100;
4
+ function PollComposer({
5
+ visible,
6
+ theme,
7
+ onCancel,
8
+ onCreate
9
+ }) {
10
+ const [question, setQuestion] = React.useState("");
11
+ const [options, setOptions] = React.useState(["", ""]);
12
+ const [multiple, setMultiple] = React.useState(false);
13
+ const [busy, setBusy] = React.useState(false);
14
+ React.useEffect(() => {
15
+ if (visible) {
16
+ setQuestion("");
17
+ setOptions(["", ""]);
18
+ setMultiple(false);
19
+ setBusy(false);
20
+ }
21
+ }, [visible]);
22
+ const setOption = (index, value) => {
23
+ setOptions((prev) => {
24
+ const next = prev.slice();
25
+ next[index] = value.slice(0, OPTION_MAX);
26
+ const nonEmpty = next.filter((o) => o.trim());
27
+ if (nonEmpty.length === next.length && next.length < MAX_OPTIONS) next.push("");
28
+ return next;
29
+ });
30
+ };
31
+ const filled = options.map((o) => o.trim()).filter(Boolean);
32
+ const canSend = question.trim().length > 0 && filled.length >= 2 && !busy;
33
+ const submit = async () => {
34
+ if (!canSend) return;
35
+ setBusy(true);
36
+ try {
37
+ await onCreate(question.trim(), filled, multiple);
38
+ } finally {
39
+ setBusy(false);
40
+ }
41
+ };
42
+ return /* @__PURE__ */ jsx(Modal, { visible, animationType: "slide", onRequestClose: onCancel, transparent: false, children: /* @__PURE__ */ jsxs(View, { style: [styles.fill, {
43
+ backgroundColor: theme.bg
44
+ }], testID: "chat-poll-composer", children: [
45
+ /* @__PURE__ */ jsx(SafeAreaView, { edges: ["top"], style: {
46
+ backgroundColor: theme.headerBg
47
+ }, children: /* @__PURE__ */ jsxs(View, { style: [styles.header, {
48
+ borderBottomColor: theme.border,
49
+ flexDirection: "row"
50
+ }], children: [
51
+ /* @__PURE__ */ jsx(Pressable, { onPress: onCancel, hitSlop: 10, accessibilityRole: "button", accessibilityLabel: "Cancel", children: /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: "close", size: 26, color: theme.text }) }),
52
+ /* @__PURE__ */ jsx(Text, { style: [styles.title, {
53
+ color: theme.text
54
+ }], children: "Create poll" }),
55
+ /* @__PURE__ */ jsx(Pressable, { onPress: submit, disabled: !canSend, hitSlop: 10, accessibilityRole: "button", accessibilityLabel: "Create poll", accessibilityState: {
56
+ disabled: !canSend
57
+ }, children: /* @__PURE__ */ jsx(Text, { style: [styles.send, {
58
+ color: canSend ? theme.accent : theme.textMuted
59
+ }], children: "Send" }) })
60
+ ] }) }),
61
+ /* @__PURE__ */ jsx(KeyboardAvoidingView, { style: styles.fill, behavior: Platform.OS === "ios" ? "padding" : void 0, keyboardVerticalOffset: 8, children: /* @__PURE__ */ jsxs(ScrollView, { contentContainerStyle: styles.body, keyboardShouldPersistTaps: "handled", children: [
62
+ /* @__PURE__ */ jsx(Text, { style: [styles.section, {
63
+ color: theme.textMuted
64
+ }], children: "QUESTION" }),
65
+ /* @__PURE__ */ jsx(TextInput, { value: question, onChangeText: (t) => setQuestion(t.slice(0, QUESTION_MAX)), placeholder: "Ask a question", placeholderTextColor: theme.textMuted, style: [styles.input, {
66
+ backgroundColor: theme.surface,
67
+ color: theme.text
68
+ }], multiline: true, accessibilityLabel: "Poll question" }),
69
+ /* @__PURE__ */ jsx(Text, { style: [styles.section, {
70
+ color: theme.textMuted
71
+ }], children: "OPTIONS" }),
72
+ options.map((opt, i) => /* @__PURE__ */ jsx(TextInput, { value: opt, onChangeText: (t) => setOption(i, t), placeholder: i < 2 ? `Option ${i + 1}` : "Add", placeholderTextColor: theme.textMuted, style: [styles.input, {
73
+ backgroundColor: theme.surface,
74
+ color: theme.text
75
+ }], accessibilityLabel: `Poll option ${i + 1}` }, i)),
76
+ /* @__PURE__ */ jsxs(View, { style: [styles.switchRow, {
77
+ borderTopColor: theme.border,
78
+ flexDirection: "row"
79
+ }], children: [
80
+ /* @__PURE__ */ jsx(Text, { style: [styles.switchLabel, {
81
+ color: theme.text
82
+ }], children: "Allow multiple answers" }),
83
+ /* @__PURE__ */ jsx(Switch, { value: multiple, onValueChange: setMultiple, trackColor: {
84
+ true: theme.accent
85
+ }, accessibilityLabel: "Allow multiple answers" })
86
+ ] })
87
+ ] }) })
88
+ ] }) });
89
+ }
90
+ const styles = StyleSheet.create({
91
+ fill: {
92
+ flex: 1
93
+ },
94
+ header: {
95
+ alignItems: "center",
96
+ justifyContent: "space-between",
97
+ paddingHorizontal: 16,
98
+ minHeight: 52,
99
+ borderBottomWidth: StyleSheet.hairlineWidth
100
+ },
101
+ title: {
102
+ fontSize: 17,
103
+ fontWeight: "600"
104
+ },
105
+ send: {
106
+ fontSize: 16,
107
+ fontWeight: "700"
108
+ },
109
+ body: {
110
+ padding: 16,
111
+ paddingBottom: 48
112
+ },
113
+ section: {
114
+ fontSize: 12,
115
+ fontWeight: "600",
116
+ marginBottom: 8,
117
+ marginTop: 8,
118
+ letterSpacing: 0.5
119
+ },
120
+ input: {
121
+ borderRadius: 10,
122
+ paddingHorizontal: 12,
123
+ paddingVertical: 12,
124
+ fontSize: 16,
125
+ marginBottom: 10
126
+ },
127
+ switchRow: {
128
+ alignItems: "center",
129
+ justifyContent: "space-between",
130
+ marginTop: 16,
131
+ paddingTop: 16,
132
+ borderTopWidth: StyleSheet.hairlineWidth
133
+ },
134
+ switchLabel: {
135
+ fontSize: 16
136
+ }
137
+ });export{PollComposer};//# sourceMappingURL=PollComposer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PollComposer.js","sources":["../../../src/features/messaging/PollComposer.tsx"],"sourcesContent":["/**\n * Create-a-poll screen, WhatsApp-style: a question, a growing list of option fields, and an\n * \"Allow multiple answers\" switch. Send is enabled once there is a question and at least two\n * non-empty options. Presented as a full-screen modal from the composer's \"+\" sheet.\n */\nimport * as React from 'react';\nimport {\n KeyboardAvoidingView,\n Modal,\n Platform,\n Pressable,\n ScrollView,\n StyleSheet,\n Switch,\n Text,\n TextInput,\n View,\n} from 'react-native';\nimport { SafeAreaView } from 'react-native-safe-area-context';\nimport { MaterialCommunityIcons } from '@expo/vector-icons';\nimport type { ChatTheme } from './ChatComponents';\n\n/** WhatsApp caps a poll at 12 options. */\nconst MAX_OPTIONS = 12;\nconst QUESTION_MAX = 255;\nconst OPTION_MAX = 100;\n\nexport function PollComposer({\n visible,\n theme,\n onCancel,\n onCreate,\n}: {\n visible: boolean;\n theme: ChatTheme;\n onCancel: () => void;\n /** Resolves when the poll has been created so the composer can close. */\n onCreate: (question: string, options: string[], multiple: boolean) => Promise<void> | void;\n}) {\n const [question, setQuestion] = React.useState('');\n // Always keep two empty slots at the end so there is somewhere to type the next option.\n const [options, setOptions] = React.useState<string[]>(['', '']);\n const [multiple, setMultiple] = React.useState(false);\n const [busy, setBusy] = React.useState(false);\n\n // Reset whenever the sheet opens.\n React.useEffect(() => {\n if (visible) {\n setQuestion('');\n setOptions(['', '']);\n setMultiple(false);\n setBusy(false);\n }\n }, [visible]);\n\n const setOption = (index: number, value: string) => {\n setOptions((prev) => {\n const next = prev.slice();\n next[index] = value.slice(0, OPTION_MAX);\n // Keep exactly one trailing empty slot (up to the cap), WhatsApp-style.\n const nonEmpty = next.filter((o) => o.trim());\n if (nonEmpty.length === next.length && next.length < MAX_OPTIONS) next.push('');\n return next;\n });\n };\n\n const filled = options.map((o) => o.trim()).filter(Boolean);\n const canSend = question.trim().length > 0 && filled.length >= 2 && !busy;\n\n const submit = async () => {\n if (!canSend) return;\n setBusy(true);\n try {\n await onCreate(question.trim(), filled, multiple);\n } finally {\n setBusy(false);\n }\n };\n\n return (\n <Modal visible={visible} animationType=\"slide\" onRequestClose={onCancel} transparent={false}>\n <View style={[styles.fill, { backgroundColor: theme.bg }]} testID=\"chat-poll-composer\">\n <SafeAreaView edges={['top']} style={{ backgroundColor: theme.headerBg }}>\n <View style={[styles.header, { borderBottomColor: theme.border, flexDirection: 'row' }]}>\n <Pressable\n onPress={onCancel}\n hitSlop={10}\n accessibilityRole=\"button\"\n accessibilityLabel=\"Cancel\"\n >\n <MaterialCommunityIcons name=\"close\" size={26} color={theme.text} />\n </Pressable>\n <Text style={[styles.title, { color: theme.text }]}>Create poll</Text>\n <Pressable\n onPress={submit}\n disabled={!canSend}\n hitSlop={10}\n accessibilityRole=\"button\"\n accessibilityLabel=\"Create poll\"\n accessibilityState={{ disabled: !canSend }}\n >\n <Text style={[styles.send, { color: canSend ? theme.accent : theme.textMuted }]}>Send</Text>\n </Pressable>\n </View>\n </SafeAreaView>\n <KeyboardAvoidingView\n style={styles.fill}\n behavior={Platform.OS === 'ios' ? 'padding' : undefined}\n keyboardVerticalOffset={8}\n >\n <ScrollView contentContainerStyle={styles.body} keyboardShouldPersistTaps=\"handled\">\n <Text style={[styles.section, { color: theme.textMuted }]}>QUESTION</Text>\n <TextInput\n value={question}\n onChangeText={(t) => setQuestion(t.slice(0, QUESTION_MAX))}\n placeholder=\"Ask a question\"\n placeholderTextColor={theme.textMuted}\n style={[styles.input, { backgroundColor: theme.surface, color: theme.text }]}\n multiline\n accessibilityLabel=\"Poll question\"\n />\n\n <Text style={[styles.section, { color: theme.textMuted }]}>OPTIONS</Text>\n {options.map((opt, i) => (\n <TextInput\n key={i}\n value={opt}\n onChangeText={(t) => setOption(i, t)}\n placeholder={i < 2 ? `Option ${i + 1}` : 'Add'}\n placeholderTextColor={theme.textMuted}\n style={[styles.input, { backgroundColor: theme.surface, color: theme.text }]}\n accessibilityLabel={`Poll option ${i + 1}`}\n />\n ))}\n\n <View style={[styles.switchRow, { borderTopColor: theme.border, flexDirection: 'row' }]}>\n <Text style={[styles.switchLabel, { color: theme.text }]}>Allow multiple answers</Text>\n <Switch\n value={multiple}\n onValueChange={setMultiple}\n trackColor={{ true: theme.accent }}\n accessibilityLabel=\"Allow multiple answers\"\n />\n </View>\n </ScrollView>\n </KeyboardAvoidingView>\n </View>\n </Modal>\n );\n}\n\nconst styles = StyleSheet.create({\n fill: { flex: 1 },\n header: {\n alignItems: 'center',\n justifyContent: 'space-between',\n paddingHorizontal: 16,\n minHeight: 52,\n borderBottomWidth: StyleSheet.hairlineWidth,\n },\n title: { fontSize: 17, fontWeight: '600' },\n send: { fontSize: 16, fontWeight: '700' },\n body: { padding: 16, paddingBottom: 48 },\n section: { fontSize: 12, fontWeight: '600', marginBottom: 8, marginTop: 8, letterSpacing: 0.5 },\n input: {\n borderRadius: 10,\n paddingHorizontal: 12,\n paddingVertical: 12,\n fontSize: 16,\n marginBottom: 10,\n },\n switchRow: {\n alignItems: 'center',\n justifyContent: 'space-between',\n marginTop: 16,\n paddingTop: 16,\n borderTopWidth: StyleSheet.hairlineWidth,\n },\n switchLabel: { fontSize: 16 },\n});\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,122 @@
1
+ import {jsxs,jsx}from'react/jsx-runtime';import {StyleSheet,View,Pressable,Text,ScrollView}from'react-native';import {SafeAreaView}from'react-native-safe-area-context';import {MaterialCommunityIcons}from'@expo/vector-icons';import {EmptyState,Avatar}from'./ChatComponents.js';function PollVotesScreen({
2
+ poll,
3
+ theme,
4
+ viewerId,
5
+ nameOf,
6
+ onBack
7
+ }) {
8
+ const total = poll ? poll.options.reduce((n, o) => n + o.count, 0) : 0;
9
+ const label = (id) => id === viewerId ? "You" : nameOf(id);
10
+ return /* @__PURE__ */ jsxs(View, { style: [styles.fill, {
11
+ backgroundColor: theme.bg
12
+ }], testID: "chat-poll-votes", children: [
13
+ /* @__PURE__ */ jsx(SafeAreaView, { edges: ["top"], style: {
14
+ backgroundColor: theme.headerBg
15
+ }, children: /* @__PURE__ */ jsxs(View, { style: [styles.header, {
16
+ borderBottomColor: theme.border,
17
+ flexDirection: "row"
18
+ }], children: [
19
+ /* @__PURE__ */ jsx(Pressable, { onPress: onBack, hitSlop: 10, accessibilityRole: "button", accessibilityLabel: "Back", style: styles.headerIcon, children: /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: "chevron-left", size: 32, color: theme.accent }) }),
20
+ /* @__PURE__ */ jsx(Text, { style: [styles.title, {
21
+ color: theme.text
22
+ }], children: "Poll votes" }),
23
+ /* @__PURE__ */ jsx(View, { style: styles.headerIcon })
24
+ ] }) }),
25
+ !poll ? /* @__PURE__ */ jsx(EmptyState, { icon: "poll", title: "Poll votes didn\u2019t load", detail: "Please try again later.", theme }) : /* @__PURE__ */ jsxs(ScrollView, { contentContainerStyle: styles.body, children: [
26
+ /* @__PURE__ */ jsx(Text, { style: [styles.question, {
27
+ color: theme.text
28
+ }], children: poll.question }),
29
+ /* @__PURE__ */ jsx(Text, { style: [styles.total, {
30
+ color: theme.textMuted
31
+ }], children: `${total} ${total === 1 ? "vote" : "votes"}` }),
32
+ poll.options.map((o) => /* @__PURE__ */ jsxs(View, { style: styles.optionBlock, children: [
33
+ /* @__PURE__ */ jsxs(View, { style: [styles.optionHead, {
34
+ borderBottomColor: theme.border,
35
+ flexDirection: "row"
36
+ }], children: [
37
+ /* @__PURE__ */ jsx(Text, { style: [styles.optionText, {
38
+ color: theme.text
39
+ }], numberOfLines: 2, children: o.text }),
40
+ /* @__PURE__ */ jsx(Text, { style: [styles.optionCount, {
41
+ color: theme.textMuted
42
+ }], children: `${o.count} ${o.count === 1 ? "vote" : "votes"}` })
43
+ ] }),
44
+ o.voterIds.length === 0 ? /* @__PURE__ */ jsx(Text, { style: [styles.noVotes, {
45
+ color: theme.textMuted
46
+ }], children: "No votes yet" }) : o.voterIds.map((id) => /* @__PURE__ */ jsxs(View, { style: [styles.voter, {
47
+ flexDirection: "row"
48
+ }], children: [
49
+ /* @__PURE__ */ jsx(Avatar, { name: label(id), theme, size: 30, variant: "initials" }),
50
+ /* @__PURE__ */ jsx(Text, { style: [styles.voterName, {
51
+ color: theme.text
52
+ }], children: label(id) })
53
+ ] }, id))
54
+ ] }, o.id))
55
+ ] })
56
+ ] });
57
+ }
58
+ const styles = StyleSheet.create({
59
+ fill: {
60
+ flex: 1
61
+ },
62
+ header: {
63
+ alignItems: "center",
64
+ justifyContent: "space-between",
65
+ paddingHorizontal: 4,
66
+ minHeight: 52,
67
+ borderBottomWidth: StyleSheet.hairlineWidth
68
+ },
69
+ headerIcon: {
70
+ width: 44,
71
+ height: 44,
72
+ alignItems: "center",
73
+ justifyContent: "center"
74
+ },
75
+ title: {
76
+ fontSize: 17,
77
+ fontWeight: "600"
78
+ },
79
+ body: {
80
+ padding: 16
81
+ },
82
+ question: {
83
+ fontSize: 18,
84
+ fontWeight: "700"
85
+ },
86
+ total: {
87
+ fontSize: 13,
88
+ marginTop: 2,
89
+ marginBottom: 12
90
+ },
91
+ optionBlock: {
92
+ marginBottom: 18
93
+ },
94
+ optionHead: {
95
+ alignItems: "center",
96
+ justifyContent: "space-between",
97
+ paddingBottom: 6,
98
+ marginBottom: 8,
99
+ borderBottomWidth: StyleSheet.hairlineWidth
100
+ },
101
+ optionText: {
102
+ fontSize: 16,
103
+ fontWeight: "600",
104
+ flex: 1,
105
+ marginRight: 8
106
+ },
107
+ optionCount: {
108
+ fontSize: 13
109
+ },
110
+ noVotes: {
111
+ fontSize: 14,
112
+ fontStyle: "italic"
113
+ },
114
+ voter: {
115
+ alignItems: "center",
116
+ marginBottom: 10
117
+ },
118
+ voterName: {
119
+ fontSize: 15,
120
+ marginLeft: 10
121
+ }
122
+ });export{PollVotesScreen};//# sourceMappingURL=PollVotesScreen.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PollVotesScreen.js","sources":["../../../src/features/messaging/PollVotesScreen.tsx"],"sourcesContent":["/**\n * \"Poll votes\": who picked what. WhatsApp polls are non-anonymous, so each option lists its\n * voters by name. Grouped by option, in the poll's own option order.\n */\nimport * as React from 'react';\nimport { Pressable, ScrollView, StyleSheet, Text, View } from 'react-native';\nimport { SafeAreaView } from 'react-native-safe-area-context';\nimport { MaterialCommunityIcons } from '@expo/vector-icons';\nimport { Avatar, EmptyState, type ChatTheme } from './ChatComponents';\nimport type { ChatPoll } from './chatModel';\n\nexport function PollVotesScreen({\n poll,\n theme,\n viewerId,\n nameOf,\n onBack,\n}: {\n poll: ChatPoll | null;\n theme: ChatTheme;\n viewerId: string;\n /** Resolve a voter id to a display name (the group's member list). */\n nameOf: (id: string) => string;\n onBack: () => void;\n}) {\n const total = poll ? poll.options.reduce((n, o) => n + o.count, 0) : 0;\n const label = (id: string) => (id === viewerId ? 'You' : nameOf(id));\n return (\n <View style={[styles.fill, { backgroundColor: theme.bg }]} testID=\"chat-poll-votes\">\n <SafeAreaView edges={['top']} style={{ backgroundColor: theme.headerBg }}>\n <View style={[styles.header, { borderBottomColor: theme.border, flexDirection: 'row' }]}>\n <Pressable\n onPress={onBack}\n hitSlop={10}\n accessibilityRole=\"button\"\n accessibilityLabel=\"Back\"\n style={styles.headerIcon}\n >\n <MaterialCommunityIcons name=\"chevron-left\" size={32} color={theme.accent} />\n </Pressable>\n <Text style={[styles.title, { color: theme.text }]}>Poll votes</Text>\n <View style={styles.headerIcon} />\n </View>\n </SafeAreaView>\n {!poll ? (\n <EmptyState icon=\"poll\" title=\"Poll votes didn’t load\" detail=\"Please try again later.\" theme={theme} />\n ) : (\n <ScrollView contentContainerStyle={styles.body}>\n <Text style={[styles.question, { color: theme.text }]}>{poll.question}</Text>\n <Text style={[styles.total, { color: theme.textMuted }]}>\n {`${total} ${total === 1 ? 'vote' : 'votes'}`}\n </Text>\n {poll.options.map((o) => (\n <View key={o.id} style={styles.optionBlock}>\n <View\n style={[styles.optionHead, { borderBottomColor: theme.border, flexDirection: 'row' }]}\n >\n <Text style={[styles.optionText, { color: theme.text }]} numberOfLines={2}>\n {o.text}\n </Text>\n <Text style={[styles.optionCount, { color: theme.textMuted }]}>\n {`${o.count} ${o.count === 1 ? 'vote' : 'votes'}`}\n </Text>\n </View>\n {o.voterIds.length === 0 ? (\n <Text style={[styles.noVotes, { color: theme.textMuted }]}>No votes yet</Text>\n ) : (\n o.voterIds.map((id) => (\n <View key={id} style={[styles.voter, { flexDirection: 'row' }]}>\n <Avatar name={label(id)} theme={theme} size={30} variant=\"initials\" />\n <Text style={[styles.voterName, { color: theme.text }]}>{label(id)}</Text>\n </View>\n ))\n )}\n </View>\n ))}\n </ScrollView>\n )}\n </View>\n );\n}\n\nconst styles = StyleSheet.create({\n fill: { flex: 1 },\n header: {\n alignItems: 'center',\n justifyContent: 'space-between',\n paddingHorizontal: 4,\n minHeight: 52,\n borderBottomWidth: StyleSheet.hairlineWidth,\n },\n headerIcon: { width: 44, height: 44, alignItems: 'center', justifyContent: 'center' },\n title: { fontSize: 17, fontWeight: '600' },\n body: { padding: 16 },\n question: { fontSize: 18, fontWeight: '700' },\n total: { fontSize: 13, marginTop: 2, marginBottom: 12 },\n optionBlock: { marginBottom: 18 },\n optionHead: {\n alignItems: 'center',\n justifyContent: 'space-between',\n paddingBottom: 6,\n marginBottom: 8,\n borderBottomWidth: StyleSheet.hairlineWidth,\n },\n optionText: { fontSize: 16, fontWeight: '600', flex: 1, marginRight: 8 },\n optionCount: { fontSize: 13 },\n noVotes: { fontSize: 14, fontStyle: 'italic' },\n voter: { alignItems: 'center', marginBottom: 10 },\n voterName: { fontSize: 15, marginLeft: 10 },\n});\n"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import {jsxs,jsx}from'react/jsx-runtime';import*as React from'react';import {StyleSheet,Platform,View,Pressable,Text,TextInput,ActivityIndicator,FlatList,Animated,PanResponder}from'react-native';import {SafeAreaView}from'react-native-safe-area-context';import {MaterialCommunityIcons}from'@expo/vector-icons';import {AppKeyboardAvoidingView}from'../../components/AppKeyboardAvoidingView.js';import {Avatar,ConnectionBanner,InlineBanner,DaySeparator,UnreadDivider,MessageBubble,ReplyQuote}from'./ChatComponents.js';import {ActionSheet}from'./ActionSheet.js';import {replyRefOf,canEditMessage,canDeleteForEveryone,firstUnreadId,dayLabel,messageStatus,timeLabel}from'./chatModel.js';import {MessageActionsOverlay}from'./MessageActions.js';import {StagedAttachments,RecordingBar,AttachSheet,ImageViewer}from'./ChatAttachments.js';var __defProp = Object.defineProperty;
1
+ import {jsxs,jsx}from'react/jsx-runtime';import*as React from'react';import {StyleSheet,Platform,View,Pressable,Text,TextInput,ActivityIndicator,FlatList,Animated,PanResponder}from'react-native';import {useSafeAreaInsets,SafeAreaView}from'react-native-safe-area-context';import {MaterialCommunityIcons}from'@expo/vector-icons';import {AppKeyboardAvoidingView}from'../../components/AppKeyboardAvoidingView.js';import {Avatar,ConnectionBanner,InlineBanner,DaySeparator,UnreadDivider,SystemRow,MessageBubble,ReplyQuote}from'./ChatComponents.js';import {ActionSheet}from'./ActionSheet.js';import {TapRow}from'./TapRow.js';import {replyRefOf,canEditMessage,canDeleteForEveryone,firstUnreadId,dayLabel,systemText,messageStatus,timeLabel}from'./chatModel.js';import {MessageActionsOverlay}from'./MessageActions.js';import {StagedAttachments,RecordingBar,AttachSheet,ImageViewer}from'./ChatAttachments.js';import {PollComposer}from'./PollComposer.js';var __defProp = Object.defineProperty;
2
2
  var __defProps = Object.defineProperties;
3
3
  var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
4
4
  var __getOwnPropSymbols = Object.getOwnPropertySymbols;
@@ -116,7 +116,17 @@ function ThreadScreen({
116
116
  audio,
117
117
  onSendAttachments,
118
118
  linkPreviews,
119
- onOpenLink
119
+ onOpenLink,
120
+ memberNameOf,
121
+ mentionCandidates,
122
+ composerLock,
123
+ onVote,
124
+ onViewVotes,
125
+ onCreatePoll,
126
+ onReportMessage,
127
+ onKeep,
128
+ peerBlocked,
129
+ onUnblockPeer
120
130
  }) {
121
131
  var _a;
122
132
  const [draft, setDraft] = React.useState(initialDraft != null ? initialDraft : "");
@@ -128,6 +138,7 @@ function ThreadScreen({
128
138
  }, [initialDraft]);
129
139
  const [staged, setStaged] = React.useState([]);
130
140
  const [attachOpen, setAttachOpen] = React.useState(false);
141
+ const [pollComposerOpen, setPollComposerOpen] = React.useState(false);
131
142
  const [recordingSince, setRecordingSince] = React.useState(null);
132
143
  const [viewerUrl, setViewerUrl] = React.useState(null);
133
144
  const [composerError, setComposerError] = React.useState(null);
@@ -144,6 +155,27 @@ function ThreadScreen({
144
155
  const listRef = React.useRef(null);
145
156
  const canAttach = Boolean(media && onSendAttachments);
146
157
  const canRecord = Boolean(audio && onSendAttachments);
158
+ const insets = useSafeAreaInsets();
159
+ const resolveName = React.useCallback((id) => {
160
+ var _a2;
161
+ return (_a2 = memberNameOf == null ? void 0 : memberNameOf(id)) != null ? _a2 : "Someone";
162
+ }, [memberNameOf]);
163
+ const insertedMentions = React.useRef([]);
164
+ const [mentionQuery, setMentionQuery] = React.useState(null);
165
+ const mentionMatches = React.useMemo(() => {
166
+ if (mentionQuery == null || !(mentionCandidates == null ? void 0 : mentionCandidates.length)) return [];
167
+ const q = mentionQuery.toLowerCase();
168
+ return mentionCandidates.filter((m) => !m.isYou).filter((m) => !q || m.name.toLowerCase().split(/\s+/).some((w) => w.startsWith(q))).slice(0, 8);
169
+ }, [mentionQuery, mentionCandidates]);
170
+ const mentionIdsFor = React.useCallback((text) => {
171
+ const ids = insertedMentions.current.filter((m) => text.includes(m.token)).map((m) => m.id);
172
+ return Array.from(new Set(ids));
173
+ }, []);
174
+ const activeMentionQuery = React.useCallback((text) => {
175
+ if (!(mentionCandidates == null ? void 0 : mentionCandidates.length)) return null;
176
+ const m = /(?:^|\s)@([\p{L}\p{N}._'’-]*)$/u.exec(text);
177
+ return m ? m[1] : null;
178
+ }, [mentionCandidates]);
147
179
  const [selected, setSelected] = React.useState(null);
148
180
  const selectedMessage = React.useMemo(() => {
149
181
  var _a2;
@@ -237,6 +269,18 @@ function ThreadScreen({
237
269
  }
238
270
  });
239
271
  }
272
+ if (selectedMessage && onKeep && !selectedMessage.isDeleted && (!selectedMessage.sendState || selectedMessage.sendState === "sent")) {
273
+ const target = selectedMessage;
274
+ list.push({
275
+ key: "keep",
276
+ label: target.kept ? "Unkeep" : "Keep",
277
+ icon: target.kept ? "bookmark-off-outline" : "bookmark-outline",
278
+ onPress: () => {
279
+ setSelected(null);
280
+ onKeep(target.id, !target.kept);
281
+ }
282
+ });
283
+ }
240
284
  if (selectedMessage && onEdit && canEditMessage(selectedMessage, at)) {
241
285
  const target = selectedMessage;
242
286
  list.push({
@@ -257,6 +301,18 @@ function ThreadScreen({
257
301
  }
258
302
  });
259
303
  }
304
+ if (selectedMessage && onReportMessage && !selectedMessage.isMine && !selectedMessage.isDeleted) {
305
+ const target = selectedMessage;
306
+ list.push({
307
+ key: "report",
308
+ label: "Report",
309
+ icon: "alert-octagon-outline",
310
+ onPress: () => {
311
+ setSelected(null);
312
+ onReportMessage(target);
313
+ }
314
+ });
315
+ }
260
316
  if (selectedMessage && (onDeleteForMe || onDeleteForEveryone)) {
261
317
  const target = selectedMessage;
262
318
  list.push({
@@ -270,7 +326,7 @@ function ThreadScreen({
270
326
  });
271
327
  }
272
328
  return list;
273
- }, [selectedMessage, onCopyText, onShareText, flushPendingShare, startReply, onForward, onStar, onEdit, onDeleteForMe, onDeleteForEveryone, onShowInfo, draft]);
329
+ }, [selectedMessage, onCopyText, onShareText, flushPendingShare, startReply, onForward, onStar, onEdit, onKeep, onReportMessage, onDeleteForMe, onDeleteForEveryone, onShowInfo, draft]);
274
330
  const deleteActions = React.useMemo(() => {
275
331
  const target = deleteTarget;
276
332
  if (!target) return [];
@@ -429,13 +485,35 @@ function ThreadScreen({
429
485
  return;
430
486
  }
431
487
  if (!text) return;
488
+ const mentions = mentionIdsFor(text);
432
489
  setDraft("");
433
490
  setReplyTo(null);
491
+ setMentionQuery(null);
492
+ insertedMentions.current = [];
434
493
  onDraftChange == null ? void 0 : onDraftChange("");
435
494
  haptics == null ? void 0 : haptics.impact("light");
436
- onSend(text, quote);
495
+ onSend(text, quote, mentions);
437
496
  jumpToLatest();
438
- }, [draft, staged, replyTo, editing, haptics, onSend, onSendAttachments, onEdit, onDraftChange, jumpToLatest]);
497
+ }, [draft, staged, replyTo, editing, haptics, onSend, onSendAttachments, onEdit, onDraftChange, jumpToLatest, mentionIdsFor]);
498
+ const insertMention = React.useCallback((member) => {
499
+ var _a2;
500
+ const handle = member.name.trim().split(/\s+/)[0] || "member";
501
+ const token = `@${handle}`;
502
+ setDraft((prev) => {
503
+ const next = prev.replace(/(?:^|\s)@([\p{L}\p{N}._'’-]*)$/u, (whole) => {
504
+ const lead = whole.startsWith("@") ? "" : whole[0];
505
+ return `${lead}${token} `;
506
+ });
507
+ onDraftChange == null ? void 0 : onDraftChange(next);
508
+ return next;
509
+ });
510
+ insertedMentions.current = [...insertedMentions.current, {
511
+ token,
512
+ id: member.id
513
+ }];
514
+ setMentionQuery(null);
515
+ (_a2 = inputRef.current) == null ? void 0 : _a2.focus();
516
+ }, [onDraftChange]);
439
517
  const pendingPick = React.useRef(null);
440
518
  const runPendingPick = React.useCallback(() => {
441
519
  const source = pendingPick.current;
@@ -461,6 +539,17 @@ function ThreadScreen({
461
539
  pendingPick.current = "camera";
462
540
  runPendingPick();
463
541
  }, [media, runPendingPick]);
542
+ const pendingPoll = React.useRef(false);
543
+ const runPendingPoll = React.useCallback(() => {
544
+ if (!pendingPoll.current) return;
545
+ pendingPoll.current = false;
546
+ setPollComposerOpen(true);
547
+ }, []);
548
+ const openPoll = React.useCallback(() => {
549
+ pendingPoll.current = true;
550
+ setAttachOpen(false);
551
+ if (Platform.OS !== "ios") setTimeout(runPendingPoll, 400);
552
+ }, [runPendingPoll]);
464
553
  const startRecording = React.useCallback(async () => {
465
554
  if (!audio) return;
466
555
  setComposerError(null);
@@ -501,7 +590,8 @@ function ThreadScreen({
501
590
  }, [audio]);
502
591
  const headerSubtitle = typingText || subtitle || "";
503
592
  return /* @__PURE__ */ jsxs(AppKeyboardAvoidingView, { style: [styles.fill, {
504
- backgroundColor: theme.threadBg
593
+ backgroundColor: theme.threadBg,
594
+ marginBottom: -insets.bottom
505
595
  }], behavior: Platform.OS === "ios" ? "padding" : void 0, keyboardVerticalOffset: 0, children: [
506
596
  /* @__PURE__ */ jsxs(SafeAreaView, { edges: ["top"], style: {
507
597
  backgroundColor: theme.headerBg
@@ -596,13 +686,13 @@ function ThreadScreen({
596
686
  const label = dayLabel(item.createdAt, now);
597
687
  const olderLabel = older ? dayLabel(older.createdAt, now) : null;
598
688
  const newDay = olderLabel !== label;
599
- const sameRun = (a, b) => Boolean(a && !a.isDeleted && !b.isDeleted && a.authorId && a.authorId === b.authorId);
689
+ const sameRun = (a, b) => Boolean(a && !a.isDeleted && !b.isDeleted && !a.system && !b.system && a.authorId && a.authorId === b.authorId);
600
690
  const startsBand = (unreadAnchor == null ? void 0 : unreadAnchor.id) === item.id;
601
691
  const groupWithPrevious = sameRun(older, item) && !newDay && !startsBand;
602
692
  return /* @__PURE__ */ jsxs(View, { children: [
603
693
  newDay ? /* @__PURE__ */ jsx(DaySeparator, { label, theme }) : null,
604
694
  startsBand && unreadAnchor ? /* @__PURE__ */ jsx(UnreadDivider, { count: unreadAnchor.count, theme }) : null,
605
- /* @__PURE__ */ jsx(SwipeToReply, { enabled: !item.isDeleted && (!item.sendState || item.sendState === "sent"), theme, onReply: () => startReply(item), children: /* @__PURE__ */ jsx(MessageBubble, { message: item, theme, status: item.isMine ? messageStatus(item, readWatermark != null ? readWatermark : 0, deliveredWatermark != null ? deliveredWatermark : 0) : void 0, viewerId, fileBaseUrl: media == null ? void 0 : media.fileBaseUrl, audio, onOpenImage: setViewerUrl, linkPreviews, onOpenLink, showAuthor: Boolean(isGroup) && !groupWithPrevious, groupWithPrevious, highlighted: highlightId === item.id, onPressQuote: jumpToMessage, onRetry: () => onRetry(item.id), onLongPress: (
695
+ item.system ? /* @__PURE__ */ jsx(SystemRow, { text: systemText(item.system, viewerId != null ? viewerId : "", resolveName), theme }) : /* @__PURE__ */ jsx(SwipeToReply, { enabled: !item.isDeleted && (!item.sendState || item.sendState === "sent"), theme, onReply: () => startReply(item), children: /* @__PURE__ */ jsx(MessageBubble, { message: item, theme, status: item.isMine ? messageStatus(item, readWatermark != null ? readWatermark : 0, deliveredWatermark != null ? deliveredWatermark : 0) : void 0, viewerId, fileBaseUrl: media == null ? void 0 : media.fileBaseUrl, audio, onOpenImage: setViewerUrl, linkPreviews, onOpenLink, onVote: item.poll && onVote ? (optionId) => onVote(item.id, optionId) : void 0, onViewVotes: item.poll && onViewVotes ? () => onViewVotes(item) : void 0, showAuthor: Boolean(isGroup) && !groupWithPrevious, groupWithPrevious, highlighted: highlightId === item.id, onPressQuote: jumpToMessage, onRetry: () => onRetry(item.id), onLongPress: (
606
696
  // Only confirmed messages can be reacted to or acted on.
607
697
  !item.isDeleted && (!item.sendState || item.sendState === "sent") ? (frame) => {
608
698
  haptics == null ? void 0 : haptics.impact("medium");
@@ -660,8 +750,45 @@ function ThreadScreen({
660
750
  }], children: /* @__PURE__ */ jsx(Text, { style: styles.jumpBadgeText, children: missed > 99 ? "99+" : missed }) }) : null
661
751
  ] }) : null
662
752
  ] }),
663
- /* @__PURE__ */ jsx(StagedAttachments, { files: staged, theme, onRemove: (index) => setStaged((prev) => prev.filter((_, i) => i !== index)) }),
664
- editing && !recordingSince ? /* @__PURE__ */ jsxs(View, { style: [styles.replyBar, {
753
+ !composerLock && !peerBlocked && mentionMatches.length ? /* @__PURE__ */ jsx(View, { style: [styles.mentionPanel, {
754
+ backgroundColor: theme.bg,
755
+ borderTopColor: theme.border
756
+ }], testID: "chat-mention-picker", children: mentionMatches.map((m) => /* @__PURE__ */ jsx(TapRow, { onPress: () => insertMention(m), accessibilityRole: "button", accessibilityLabel: `Mention ${m.name}`, pressedColor: theme.surface, children: /* @__PURE__ */ jsxs(View, { style: [styles.mentionRow, {
757
+ borderBottomColor: theme.border,
758
+ flexDirection: "row"
759
+ }], children: [
760
+ /* @__PURE__ */ jsx(Avatar, { name: m.name, uri: m.avatarUrl, size: 34, theme, variant: "initials" }),
761
+ /* @__PURE__ */ jsx(Text, { numberOfLines: 1, style: [styles.mentionName, {
762
+ color: theme.text
763
+ }], children: m.name }),
764
+ m.isAdmin ? /* @__PURE__ */ jsx(Text, { style: [styles.mentionAdmin, {
765
+ color: theme.textMuted
766
+ }], children: "Admin" }) : null
767
+ ] }) }, m.id)) }) : null,
768
+ composerLock ? /* @__PURE__ */ jsx(SafeAreaView, { edges: ["bottom"], style: {
769
+ backgroundColor: theme.composerBar
770
+ }, children: /* @__PURE__ */ jsxs(View, { style: [styles.composerLock, {
771
+ borderTopColor: theme.border
772
+ }], testID: "chat-composer-lock", children: [
773
+ /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: composerLock === "adminsOnly" ? "shield-account" : "account-off", size: 18, color: theme.textMuted, style: {
774
+ marginRight: 8
775
+ } }),
776
+ /* @__PURE__ */ jsx(Text, { style: [styles.composerLockText, {
777
+ color: theme.textMuted
778
+ }], children: composerLock === "adminsOnly" ? "Only admins can send messages" : "You can't send messages to this group because you're no longer a participant" })
779
+ ] }) }) : null,
780
+ !composerLock && peerBlocked ? /* @__PURE__ */ jsx(SafeAreaView, { edges: ["bottom"], style: {
781
+ backgroundColor: theme.composerBar
782
+ }, children: /* @__PURE__ */ jsx(Pressable, { onPress: onUnblockPeer, disabled: !onUnblockPeer, accessibilityRole: "button", accessibilityLabel: "Unblock to send a message", style: [styles.composerLock, {
783
+ borderTopColor: theme.border
784
+ }], testID: "chat-composer-blocked", children: /* @__PURE__ */ jsxs(Text, { style: [styles.composerLockText, {
785
+ color: theme.textMuted
786
+ }], children: [
787
+ "You blocked this contact.",
788
+ onUnblockPeer ? " Tap to unblock." : ""
789
+ ] }) }) }) : null,
790
+ !composerLock && !peerBlocked ? /* @__PURE__ */ jsx(StagedAttachments, { files: staged, theme, onRemove: (index) => setStaged((prev) => prev.filter((_, i) => i !== index)) }) : null,
791
+ !composerLock && !peerBlocked && editing && !recordingSince ? /* @__PURE__ */ jsxs(View, { style: [styles.replyBar, {
665
792
  backgroundColor: theme.composerBar,
666
793
  flexDirection: "row"
667
794
  }], testID: "chat-composer-editing", children: [
@@ -684,7 +811,7 @@ function ThreadScreen({
684
811
  setEditing(null);
685
812
  }, hitSlop: 10, accessibilityRole: "button", accessibilityLabel: "Cancel edit", style: styles.replyClose, children: /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: "close", size: 20, color: theme.textMuted }) })
686
813
  ] }) : null,
687
- replyTo && !recordingSince ? /* @__PURE__ */ jsxs(View, { style: [styles.replyBar, {
814
+ !composerLock && !peerBlocked && replyTo && !recordingSince ? /* @__PURE__ */ jsxs(View, { style: [styles.replyBar, {
688
815
  backgroundColor: theme.composerBar,
689
816
  flexDirection: "row"
690
817
  }], testID: "chat-composer-reply", children: [
@@ -693,7 +820,7 @@ function ThreadScreen({
693
820
  }, children: /* @__PURE__ */ jsx(ReplyQuote, { reply: replyTo, theme, viewerId, compact: true }) }),
694
821
  /* @__PURE__ */ jsx(Pressable, { onPress: () => setReplyTo(null), hitSlop: 10, accessibilityRole: "button", accessibilityLabel: "Cancel reply", style: styles.replyClose, children: /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: "close", size: 20, color: theme.textMuted }) })
695
822
  ] }) : null,
696
- recordingSince ? /* @__PURE__ */ jsx(RecordingBar, { theme, startedAt: recordingSince, onCancel: () => void finishRecording(false), onSend: () => void finishRecording(true) }) : /* @__PURE__ */ jsx(SafeAreaView, { edges: ["bottom"], style: {
823
+ composerLock || peerBlocked ? null : recordingSince ? /* @__PURE__ */ jsx(RecordingBar, { theme, startedAt: recordingSince, onCancel: () => void finishRecording(false), onSend: () => void finishRecording(true) }) : /* @__PURE__ */ jsx(SafeAreaView, { edges: ["bottom"], style: {
697
824
  backgroundColor: theme.composerBar
698
825
  }, children: /* @__PURE__ */ jsxs(View, { style: {
699
826
  flexDirection: "row",
@@ -717,6 +844,7 @@ function ThreadScreen({
717
844
  }, children: /* @__PURE__ */ jsx(TextInput, { ref: inputRef, value: draft, onChangeText: (text) => {
718
845
  setDraft(text);
719
846
  if (!editing) onDraftChange == null ? void 0 : onDraftChange(text);
847
+ setMentionQuery(editing ? null : activeMentionQuery(text));
720
848
  }, placeholder: "Message", placeholderTextColor: theme.textMuted, style: {
721
849
  flex: 1,
722
850
  color: theme.text,
@@ -734,7 +862,14 @@ function ThreadScreen({
734
862
  /* @__PURE__ */ jsx(Pressable, { onPress: () => void startRecording(), hitSlop: 6, accessibilityRole: "button", accessibilityLabel: "Record voice message", style: styles.composerIcon, testID: "chat-composer-mic", children: /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: "microphone-outline", size: 26, color: theme.accent }) })
735
863
  ] })
736
864
  ] }) }),
737
- /* @__PURE__ */ jsx(AttachSheet, { visible: attachOpen, theme, onClose: () => setAttachOpen(false), onPhotos: () => pick("library"), onCamera: () => pick("camera"), onDismissed: Platform.OS === "ios" ? runPendingPick : void 0 }),
865
+ /* @__PURE__ */ jsx(AttachSheet, { visible: attachOpen, theme, onClose: () => setAttachOpen(false), onPhotos: () => pick("library"), onCamera: () => pick("camera"), onPoll: onCreatePoll ? openPoll : void 0, onDismissed: Platform.OS === "ios" ? () => {
866
+ runPendingPick();
867
+ runPendingPoll();
868
+ } : void 0 }),
869
+ onCreatePoll ? /* @__PURE__ */ jsx(PollComposer, { visible: pollComposerOpen, theme, onCancel: () => setPollComposerOpen(false), onCreate: async (question, options, multiple) => {
870
+ await onCreatePoll(question, options, multiple);
871
+ setPollComposerOpen(false);
872
+ } }) : null,
738
873
  /* @__PURE__ */ jsx(ImageViewer, { url: viewerUrl, onClose: () => setViewerUrl(null) }),
739
874
  /* @__PURE__ */ jsx(MessageActionsOverlay, { message: selectedMessage, frame: (_a = selected == null ? void 0 : selected.frame) != null ? _a : null, theme, actions, onReact: (emoji) => {
740
875
  if (selectedMessage && onReact) {
@@ -816,6 +951,38 @@ const styles = StyleSheet.create({
816
951
  paddingRight: 4,
817
952
  paddingTop: 8
818
953
  },
954
+ mentionPanel: {
955
+ maxHeight: 220,
956
+ borderTopWidth: StyleSheet.hairlineWidth
957
+ },
958
+ mentionRow: {
959
+ alignItems: "center",
960
+ paddingHorizontal: 14,
961
+ paddingVertical: 8,
962
+ borderBottomWidth: StyleSheet.hairlineWidth
963
+ },
964
+ mentionName: {
965
+ flex: 1,
966
+ fontSize: 16,
967
+ marginLeft: 12
968
+ },
969
+ mentionAdmin: {
970
+ fontSize: 12,
971
+ marginLeft: 8
972
+ },
973
+ composerLock: {
974
+ flexDirection: "row",
975
+ alignItems: "center",
976
+ justifyContent: "center",
977
+ paddingHorizontal: 24,
978
+ paddingVertical: 14,
979
+ borderTopWidth: StyleSheet.hairlineWidth
980
+ },
981
+ composerLockText: {
982
+ fontSize: 13.5,
983
+ textAlign: "center",
984
+ flexShrink: 1
985
+ },
819
986
  replyClose: {
820
987
  width: 36,
821
988
  height: 36,