@planningcenter/chat-react-native 1.7.0-rc.0 → 2.0.0-rc.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 (56) hide show
  1. package/build/components/conversation/message.d.ts +9 -0
  2. package/build/components/conversation/message.d.ts.map +1 -0
  3. package/build/components/conversation/message.js +73 -0
  4. package/build/components/conversation/message.js.map +1 -0
  5. package/build/components/conversation/message_form.d.ts +20 -0
  6. package/build/components/conversation/message_form.d.ts.map +1 -0
  7. package/build/components/conversation/message_form.js +103 -0
  8. package/build/components/conversation/message_form.js.map +1 -0
  9. package/build/components/conversation/message_reaction.d.ts +23 -0
  10. package/build/components/conversation/message_reaction.d.ts.map +1 -1
  11. package/build/components/conversation/message_reaction.js +1 -4
  12. package/build/components/conversation/message_reaction.js.map +1 -1
  13. package/build/components/display/keyboard_view.d.ts +4 -0
  14. package/build/components/display/keyboard_view.d.ts.map +1 -0
  15. package/build/components/display/keyboard_view.js +46 -0
  16. package/build/components/display/keyboard_view.js.map +1 -0
  17. package/build/index.d.ts +4 -6
  18. package/build/index.d.ts.map +1 -1
  19. package/build/index.js +4 -6
  20. package/build/index.js.map +1 -1
  21. package/build/navigation/index.d.ts.map +1 -1
  22. package/build/screens/conversation_screen.d.ts.map +1 -1
  23. package/build/screens/conversation_screen.js +21 -84
  24. package/build/screens/conversation_screen.js.map +1 -1
  25. package/build/screens/message_actions_screen.d.ts.map +1 -1
  26. package/build/screens/message_actions_screen.js +69 -50
  27. package/build/screens/message_actions_screen.js.map +1 -1
  28. package/build/utils/index.d.ts +1 -0
  29. package/build/utils/index.d.ts.map +1 -1
  30. package/build/utils/index.js +1 -0
  31. package/build/utils/index.js.map +1 -1
  32. package/build/utils/native_adapters/clipboard.d.ts +6 -0
  33. package/build/utils/native_adapters/clipboard.d.ts.map +1 -0
  34. package/build/utils/native_adapters/clipboard.js +9 -0
  35. package/build/utils/native_adapters/clipboard.js.map +1 -0
  36. package/build/utils/native_adapters/configuration.d.ts +10 -0
  37. package/build/utils/native_adapters/configuration.d.ts.map +1 -0
  38. package/build/utils/native_adapters/configuration.js +18 -0
  39. package/build/utils/native_adapters/configuration.js.map +1 -0
  40. package/build/utils/native_adapters/index.d.ts +3 -0
  41. package/build/utils/native_adapters/index.d.ts.map +1 -0
  42. package/build/utils/native_adapters/index.js +3 -0
  43. package/build/utils/native_adapters/index.js.map +1 -0
  44. package/package.json +2 -2
  45. package/src/__tests__/utils/native_adapters/configuration.ts +21 -0
  46. package/src/components/conversation/message.tsx +87 -0
  47. package/src/components/conversation/message_form.tsx +159 -0
  48. package/src/components/conversation/message_reaction.tsx +1 -4
  49. package/src/components/display/keyboard_view.tsx +69 -0
  50. package/src/index.tsx +10 -6
  51. package/src/screens/conversation_screen.tsx +30 -115
  52. package/src/screens/message_actions_screen.tsx +114 -77
  53. package/src/utils/index.ts +1 -0
  54. package/src/utils/native_adapters/clipboard.ts +9 -0
  55. package/src/utils/native_adapters/configuration.ts +25 -0
  56. package/src/utils/native_adapters/index.ts +2 -0
@@ -1,15 +1,16 @@
1
+ import { PlatformPressable } from '@react-navigation/elements';
1
2
  import { useNavigation } from '@react-navigation/native';
2
3
  import { useMutation, useQueryClient } from '@tanstack/react-query';
3
- import colorFunction from 'color';
4
- import React, { useContext } from 'react';
5
- import { StyleSheet, View } from 'react-native';
6
- import { SafeAreaView } from 'react-native-safe-area-context';
7
- import { TextButton } from '../components';
4
+ import React, { useCallback, useContext } from 'react';
5
+ import { Alert, Platform, StyleSheet, useWindowDimensions, View } from 'react-native';
6
+ import { useSafeAreaInsets } from 'react-native-safe-area-context';
7
+ import { Text, TextButton } from '../components';
8
+ import { REACTION_EMOJIS, useReactionStyles } from '../components/conversation/message_reaction';
8
9
  import { ChatContext } from '../contexts';
9
10
  import { useTheme } from '../hooks';
10
11
  import { getMessagesRequestArgs, useConversationMessages } from '../hooks/use_conversation_messages';
11
12
  import { updateRecordInPagesData } from '../utils';
12
- import { REACTION_EMOJIS } from '../components/conversation/message_reaction';
13
+ import { Clipboard } from '../utils/native_adapters';
13
14
  export const ReactScreenOptions = {
14
15
  presentation: 'formSheet',
15
16
  headerShown: false,
@@ -22,14 +23,26 @@ export function MessageActionsScreen({ route }) {
22
23
  const { client } = useContext(ChatContext);
23
24
  const queryClient = useQueryClient();
24
25
  const styles = useStyles();
25
- const { messages, queryKey } = useConversationMessages({ conversation_id }, { refetchOnMount: false });
26
+ const { messages, queryKey, refetch } = useConversationMessages({ conversation_id }, { refetchOnMount: false });
26
27
  const message = messages.find(m => m.id === message_id);
27
- const reactionValues = message?.reactionCounts.filter(reaction => reaction.mine).map(reaction => reaction.value) || [];
28
- const handleReactionPress = (value) => {
29
- const present = reactionValues.includes(value);
28
+ const myReactions = message?.reactionCounts
29
+ .filter(reaction => reaction.mine)
30
+ .map(reaction => reaction.value);
31
+ const availableReactions = Object.entries(REACTION_EMOJIS).map(([value, emoji]) => {
32
+ return {
33
+ value: value,
34
+ emoji,
35
+ mine: myReactions?.includes(value),
36
+ };
37
+ });
38
+ const handleCopyPress = (text) => {
39
+ Clipboard.setStringAsync(text || '');
40
+ navigation.goBack();
41
+ };
42
+ const handleReactionPress = useCallback(({ value, mine }) => {
30
43
  const requestParams = getMessagesRequestArgs({ conversation_id });
31
44
  // Value has already been updated
32
- const endpoint = !present ? 'react' : 'unreact';
45
+ const endpoint = !mine ? 'react' : 'unreact';
33
46
  const url = `/me/conversations/${conversation_id}/messages/${message_id}/${endpoint}`;
34
47
  const fieldsWithValueJoined = Object.fromEntries(Object.entries(requestParams.data.fields).map(([k, v]) => [k, v.join(',')]));
35
48
  return client.post({
@@ -43,8 +56,12 @@ export function MessageActionsScreen({ route }) {
43
56
  fields: fieldsWithValueJoined,
44
57
  },
45
58
  });
46
- };
47
- const { mutate, isPending } = useMutation({
59
+ }, [client, conversation_id, message_id]);
60
+ const deleteMessage = useCallback(() => {
61
+ const url = `/me/conversations/${conversation_id}/messages/${message_id}/`;
62
+ return client.delete({ url });
63
+ }, [client, conversation_id, message_id]);
64
+ const { mutate: handleReaction, isPending } = useMutation({
48
65
  mutationFn: handleReactionPress,
49
66
  onSuccess: (result) => {
50
67
  const updatedMessage = result.data;
@@ -54,63 +71,66 @@ export function MessageActionsScreen({ route }) {
54
71
  }));
55
72
  navigation.goBack();
56
73
  },
74
+ onError: () => {
75
+ Alert.alert('Oops', 'We were unable to react to this message. Please try again.');
76
+ },
57
77
  });
58
- const myReactions = message?.reactionCounts
59
- .filter(reaction => reaction.mine)
60
- .map(reaction => reaction.value);
61
- const availableReactions = Object.entries(REACTION_EMOJIS).map(([value, emoji]) => {
62
- return {
63
- value: value,
64
- emoji,
65
- mine: myReactions?.includes(value),
66
- };
78
+ const { mutate: handleDeleteMessage } = useMutation({
79
+ mutationFn: deleteMessage,
80
+ onSuccess: () => {
81
+ refetch();
82
+ navigation.goBack();
83
+ },
84
+ onError: () => {
85
+ Alert.alert('Oops', 'We were unable to delete this message. Please try again.');
86
+ },
67
87
  });
68
- return (<SafeAreaView style={styles.container} edges={['bottom']}>
88
+ return (<View style={styles.container}>
69
89
  <View style={styles.reactionList}>
70
- {availableReactions.map(({ value, emoji, mine }, index) => (<TextButton key={index} onPress={() => mutate(value)} style={({ pressed }) => [
71
- styles.reaction,
72
- mine && styles.reactionActive,
73
- pressed && styles.pressed,
74
- ]} variant="plain" disabled={isPending}>
75
- {emoji}
76
- </TextButton>))}
90
+ {availableReactions.map((reaction, index) => (<Reaction key={index} reaction={reaction} onPress={() => handleReaction(reaction)}/>))}
77
91
  </View>
78
92
  <View style={styles.actions}>
79
93
  <View style={styles.actionButton}>
80
- <TextButton onPress={() => navigation.goBack()} disabled={isPending}>
81
- Copy
82
- </TextButton>
83
- <TextButton appearance="danger" onPress={() => navigation.goBack()} disabled={isPending}>
94
+ <TextButton onPress={() => handleCopyPress(message?.text)}>Copy</TextButton>
95
+ <TextButton appearance="danger" onPress={() => handleDeleteMessage()} disabled={isPending}>
84
96
  Delete
85
97
  </TextButton>
86
98
  </View>
87
99
  </View>
88
- </SafeAreaView>);
100
+ </View>);
89
101
  }
102
+ const Reaction = ({ reaction, onPress, }) => {
103
+ const styles = useStyles();
104
+ const reactionStyles = useReactionStyles({ mine: reaction.mine ? 1 : 0 });
105
+ return (<PlatformPressable key={reaction.value} style={[reactionStyles.reaction, styles.reaction]} onPress={onPress}>
106
+ <Text style={reactionStyles.reactionEmoji}>{REACTION_EMOJIS[reaction.value]}</Text>
107
+ </PlatformPressable>);
108
+ };
90
109
  const useStyles = () => {
91
110
  const theme = useTheme();
111
+ const { height } = useWindowDimensions();
112
+ const { bottom } = useSafeAreaInsets();
113
+ const btnBorderWidth = 1;
114
+ const baseSize = 44;
115
+ const reactionBtnSize = Platform.select({
116
+ ios: baseSize,
117
+ android: baseSize + btnBorderWidth * 2,
118
+ });
92
119
  return StyleSheet.create({
93
120
  container: {
94
121
  justifyContent: 'flex-start',
95
122
  paddingTop: 12,
123
+ paddingBottom: bottom,
96
124
  width: '100%',
97
125
  backgroundColor: theme.colors.fillColorNeutral100Inverted,
126
+ height,
98
127
  },
99
128
  reaction: {
100
- padding: 12,
101
- borderRadius: 24,
102
- includeFontPadding: false,
103
- textAlignVertical: 'center',
104
- fontSize: 28,
105
- color: '#ffffff',
106
- backgroundColor: theme.colors.fillColorNeutral050Base,
107
- },
108
- reactionActive: {
109
- borderColor: theme.colors.interaction,
110
- backgroundColor: colorFunction(theme.colors.interaction).alpha(0.2).string(),
111
- },
112
- pressed: {
113
- opacity: 0.5,
129
+ height: reactionBtnSize,
130
+ width: reactionBtnSize,
131
+ borderWidth: btnBorderWidth,
132
+ borderRadius: 32,
133
+ justifyContent: 'center',
114
134
  },
115
135
  reactionList: {
116
136
  justifyContent: 'center',
@@ -119,7 +139,6 @@ const useStyles = () => {
119
139
  flexDirection: 'row',
120
140
  borderBottomColor: theme.colors.fillColorNeutral040,
121
141
  borderBottomWidth: 1,
122
- flex: 1,
123
142
  },
124
143
  actions: { flex: 1 },
125
144
  actionButton: { padding: 12, paddingBottom: 100, gap: 12 },
@@ -1 +1 @@
1
- {"version":3,"file":"message_actions_screen.js","sourceRoot":"","sources":["../../src/screens/message_actions_screen.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAqB,aAAa,EAAE,MAAM,0BAA0B,CAAA;AAE3E,OAAO,EAAgB,WAAW,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AACjF,OAAO,aAAa,MAAM,OAAO,CAAA;AACjC,OAAO,KAAK,EAAE,EAAE,UAAU,EAAE,MAAM,OAAO,CAAA;AACzC,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,cAAc,CAAA;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAA;AAC7D,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AACnC,OAAO,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,MAAM,oCAAoC,CAAA;AAGpG,OAAO,EAAE,uBAAuB,EAAE,MAAM,UAAU,CAAA;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,6CAA6C,CAAA;AAE7E,MAAM,CAAC,MAAM,kBAAkB,GAAiC;IAC9D,YAAY,EAAE,WAAW;IACzB,WAAW,EAAE,KAAK;IAClB,mBAAmB,EAAE,CAAC,IAAI,CAAC;IAC3B,mBAAmB,EAAE,IAAI;CAC1B,CAAA;AAOD,MAAM,UAAU,oBAAoB,CAAC,EAAE,KAAK,EAAuB;IACjE,MAAM,UAAU,GAAG,aAAa,EAAE,CAAA;IAClC,MAAM,EAAE,eAAe,EAAE,UAAU,EAAE,GAAG,KAAK,CAAC,MAAM,CAAA;IAEpD,MAAM,EAAE,MAAM,EAAE,GAAG,UAAU,CAAC,WAAW,CAAC,CAAA;IAC1C,MAAM,WAAW,GAAG,cAAc,EAAE,CAAA;IACpC,MAAM,MAAM,GAAG,SAAS,EAAE,CAAA;IAE1B,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,uBAAuB,CACpD,EAAE,eAAe,EAAE,EACnB,EAAE,cAAc,EAAE,KAAK,EAAE,CAC1B,CAAA;IACD,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,UAAU,CAAC,CAAA;IACvD,MAAM,cAAc,GAClB,OAAO,EAAE,cAAc,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,CAAA;IAEjG,MAAM,mBAAmB,GAAG,CAAC,KAAmC,EAAE,EAAE;QAClE,MAAM,OAAO,GAAG,cAAc,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;QAC9C,MAAM,aAAa,GAAG,sBAAsB,CAAC,EAAE,eAAe,EAAE,CAAC,CAAA;QAEjE,iCAAiC;QACjC,MAAM,QAAQ,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAA;QAC/C,MAAM,GAAG,GAAG,qBAAqB,eAAe,aAAa,UAAU,IAAI,QAAQ,EAAE,CAAA;QACrF,MAAM,qBAAqB,GAAG,MAAM,CAAC,WAAW,CAC9C,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAC5E,CAAA;QAED,OAAO,MAAM,CAAC,IAAI,CAAC;YACjB,GAAG;YACH,IAAI,EAAE;gBACJ,GAAG,aAAa,CAAC,IAAI;gBACrB,IAAI,EAAE;oBACJ,IAAI,EAAE,SAAS;oBACf,UAAU,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE;iBAC7B;gBACD,MAAM,EAAE,qBAAqB;aAC9B;SACF,CAAC,CAAA;IACJ,CAAC,CAAA;IAED,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,WAAW,CAAC;QACxC,UAAU,EAAE,mBAAmB;QAC/B,SAAS,EAAE,CAAC,MAAoC,EAAE,EAAE;YAClD,MAAM,cAAc,GAAG,MAAM,CAAC,IAAI,CAAA;YAGlC,WAAW,CAAC,YAAY,CAAY,QAAQ,EAAE,IAAI,CAAC,EAAE,CACnD,uBAAuB,CAAC;gBACtB,IAAI;gBACJ,MAAM,EAAE,cAAc;aACvB,CAAC,CACH,CAAA;YACD,UAAU,CAAC,MAAM,EAAE,CAAA;QACrB,CAAC;KACF,CAAC,CAAA;IAEF,MAAM,WAAW,GAAG,OAAO,EAAE,cAAc;SACxC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC;SACjC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;IAElC,MAAM,kBAAkB,GAAG,MAAM,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,EAAE;QAChF,OAAO;YACL,KAAK,EAAE,KAAuC;YAC9C,KAAK;YACL,IAAI,EAAE,WAAW,EAAE,QAAQ,CAAC,KAAuC,CAAC;SACrE,CAAA;IACH,CAAC,CAAC,CAAA;IAEF,OAAO,CACL,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CACvD;MAAA,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC,CAC/B;QAAA,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,EAAE,CAAC,CACzD,CAAC,UAAU,CACT,GAAG,CAAC,CAAC,KAAK,CAAC,CACX,OAAO,CAAC,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAC7B,KAAK,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;gBACtB,MAAM,CAAC,QAAQ;gBACf,IAAI,IAAI,MAAM,CAAC,cAAc;gBAC7B,OAAO,IAAI,MAAM,CAAC,OAAO;aAC1B,CAAC,CACF,OAAO,CAAC,OAAO,CACf,QAAQ,CAAC,CAAC,SAAS,CAAC,CAEpB;YAAA,CAAC,KAAK,CACR;UAAA,EAAE,UAAU,CAAC,CACd,CAAC,CACJ;MAAA,EAAE,IAAI,CACN;MAAA,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAC1B;QAAA,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC,CAC/B;UAAA,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,SAAS,CAAC,CAClE;;UACF,EAAE,UAAU,CACZ;UAAA,CAAC,UAAU,CAAC,UAAU,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,SAAS,CAAC,CACtF;;UACF,EAAE,UAAU,CACd;QAAA,EAAE,IAAI,CACR;MAAA,EAAE,IAAI,CACR;IAAA,EAAE,YAAY,CAAC,CAChB,CAAA;AACH,CAAC;AAED,MAAM,SAAS,GAAG,GAAG,EAAE;IACrB,MAAM,KAAK,GAAG,QAAQ,EAAE,CAAA;IACxB,OAAO,UAAU,CAAC,MAAM,CAAC;QACvB,SAAS,EAAE;YACT,cAAc,EAAE,YAAY;YAC5B,UAAU,EAAE,EAAE;YACd,KAAK,EAAE,MAAM;YACb,eAAe,EAAE,KAAK,CAAC,MAAM,CAAC,2BAA2B;SAC1D;QACD,QAAQ,EAAE;YACR,OAAO,EAAE,EAAE;YACX,YAAY,EAAE,EAAE;YAChB,kBAAkB,EAAE,KAAK;YACzB,iBAAiB,EAAE,QAAQ;YAC3B,QAAQ,EAAE,EAAE;YACZ,KAAK,EAAE,SAAS;YAChB,eAAe,EAAE,KAAK,CAAC,MAAM,CAAC,uBAAuB;SACtD;QACD,cAAc,EAAE;YACd,WAAW,EAAE,KAAK,CAAC,MAAM,CAAC,WAAW;YACrC,eAAe,EAAE,aAAa,CAAC,KAAK,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE;SAC7E;QACD,OAAO,EAAE;YACP,OAAO,EAAE,GAAG;SACb;QACD,YAAY,EAAE;YACZ,cAAc,EAAE,QAAQ;YACxB,GAAG,EAAE,EAAE;YACP,eAAe,EAAE,EAAE;YACnB,aAAa,EAAE,KAAK;YACpB,iBAAiB,EAAE,KAAK,CAAC,MAAM,CAAC,mBAAmB;YACnD,iBAAiB,EAAE,CAAC;YACpB,IAAI,EAAE,CAAC;SACR;QACD,OAAO,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE;QACpB,YAAY,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,aAAa,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE;KAC3D,CAAC,CAAA;AACJ,CAAC,CAAA","sourcesContent":["import { StaticScreenProps, useNavigation } from '@react-navigation/native'\nimport { NativeStackNavigationOptions } from '@react-navigation/native-stack'\nimport { InfiniteData, useMutation, useQueryClient } from '@tanstack/react-query'\nimport colorFunction from 'color'\nimport React, { useContext } from 'react'\nimport { StyleSheet, View } from 'react-native'\nimport { SafeAreaView } from 'react-native-safe-area-context'\nimport { TextButton } from '../components'\nimport { ChatContext } from '../contexts'\nimport { useTheme } from '../hooks'\nimport { getMessagesRequestArgs, useConversationMessages } from '../hooks/use_conversation_messages'\nimport { ApiCollection, ApiResource, MessageResource } from '../types'\nimport { ReactionCountResource } from '../types/resources/reaction'\nimport { updateRecordInPagesData } from '../utils'\nimport { REACTION_EMOJIS } from '../components/conversation/message_reaction'\n\nexport const ReactScreenOptions: NativeStackNavigationOptions = {\n presentation: 'formSheet',\n headerShown: false,\n sheetAllowedDetents: [0.25],\n sheetGrabberVisible: true,\n}\n\nexport type ReactionScreenProps = StaticScreenProps<{\n message_id: string\n conversation_id: string\n}>\n\nexport function MessageActionsScreen({ route }: ReactionScreenProps) {\n const navigation = useNavigation()\n const { conversation_id, message_id } = route.params\n\n const { client } = useContext(ChatContext)\n const queryClient = useQueryClient()\n const styles = useStyles()\n\n const { messages, queryKey } = useConversationMessages(\n { conversation_id },\n { refetchOnMount: false }\n )\n const message = messages.find(m => m.id === message_id)\n const reactionValues =\n message?.reactionCounts.filter(reaction => reaction.mine).map(reaction => reaction.value) || []\n\n const handleReactionPress = (value: keyof typeof REACTION_EMOJIS) => {\n const present = reactionValues.includes(value)\n const requestParams = getMessagesRequestArgs({ conversation_id })\n\n // Value has already been updated\n const endpoint = !present ? 'react' : 'unreact'\n const url = `/me/conversations/${conversation_id}/messages/${message_id}/${endpoint}`\n const fieldsWithValueJoined = Object.fromEntries(\n Object.entries(requestParams.data.fields).map(([k, v]) => [k, v.join(',')])\n )\n\n return client.post({\n url,\n data: {\n ...requestParams.data,\n data: {\n type: 'Message',\n attributes: { value: value },\n },\n fields: fieldsWithValueJoined,\n },\n })\n }\n\n const { mutate, isPending } = useMutation({\n mutationFn: handleReactionPress,\n onSuccess: (result: ApiResource<MessageResource>) => {\n const updatedMessage = result.data\n type QueryData = InfiniteData<ApiCollection<MessageResource>>\n\n queryClient.setQueryData<QueryData>(queryKey, data =>\n updateRecordInPagesData({\n data,\n record: updatedMessage,\n })\n )\n navigation.goBack()\n },\n })\n\n const myReactions = message?.reactionCounts\n .filter(reaction => reaction.mine)\n .map(reaction => reaction.value)\n\n const availableReactions = Object.entries(REACTION_EMOJIS).map(([value, emoji]) => {\n return {\n value: value as ReactionCountResource['value'],\n emoji,\n mine: myReactions?.includes(value as ReactionCountResource['value']),\n }\n })\n\n return (\n <SafeAreaView style={styles.container} edges={['bottom']}>\n <View style={styles.reactionList}>\n {availableReactions.map(({ value, emoji, mine }, index) => (\n <TextButton\n key={index}\n onPress={() => mutate(value)}\n style={({ pressed }) => [\n styles.reaction,\n mine && styles.reactionActive,\n pressed && styles.pressed,\n ]}\n variant=\"plain\"\n disabled={isPending}\n >\n {emoji}\n </TextButton>\n ))}\n </View>\n <View style={styles.actions}>\n <View style={styles.actionButton}>\n <TextButton onPress={() => navigation.goBack()} disabled={isPending}>\n Copy\n </TextButton>\n <TextButton appearance=\"danger\" onPress={() => navigation.goBack()} disabled={isPending}>\n Delete\n </TextButton>\n </View>\n </View>\n </SafeAreaView>\n )\n}\n\nconst useStyles = () => {\n const theme = useTheme()\n return StyleSheet.create({\n container: {\n justifyContent: 'flex-start',\n paddingTop: 12,\n width: '100%',\n backgroundColor: theme.colors.fillColorNeutral100Inverted,\n },\n reaction: {\n padding: 12,\n borderRadius: 24,\n includeFontPadding: false,\n textAlignVertical: 'center',\n fontSize: 28,\n color: '#ffffff',\n backgroundColor: theme.colors.fillColorNeutral050Base,\n },\n reactionActive: {\n borderColor: theme.colors.interaction,\n backgroundColor: colorFunction(theme.colors.interaction).alpha(0.2).string(),\n },\n pressed: {\n opacity: 0.5,\n },\n reactionList: {\n justifyContent: 'center',\n gap: 24,\n paddingVertical: 12,\n flexDirection: 'row',\n borderBottomColor: theme.colors.fillColorNeutral040,\n borderBottomWidth: 1,\n flex: 1,\n },\n actions: { flex: 1 },\n actionButton: { padding: 12, paddingBottom: 100, gap: 12 },\n })\n}\n"]}
1
+ {"version":3,"file":"message_actions_screen.js","sourceRoot":"","sources":["../../src/screens/message_actions_screen.tsx"],"names":[],"mappings":"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","sourcesContent":["import { PlatformPressable } from '@react-navigation/elements'\nimport { StaticScreenProps, useNavigation } from '@react-navigation/native'\nimport { NativeStackNavigationOptions } from '@react-navigation/native-stack'\nimport { InfiniteData, useMutation, useQueryClient } from '@tanstack/react-query'\nimport React, { useCallback, useContext } from 'react'\nimport { Alert, Platform, StyleSheet, useWindowDimensions, View } from 'react-native'\nimport { useSafeAreaInsets } from 'react-native-safe-area-context'\nimport { Text, TextButton } from '../components'\nimport { REACTION_EMOJIS, useReactionStyles } from '../components/conversation/message_reaction'\nimport { ChatContext } from '../contexts'\nimport { useTheme } from '../hooks'\nimport { getMessagesRequestArgs, useConversationMessages } from '../hooks/use_conversation_messages'\nimport { ApiCollection, ApiResource, MessageResource } from '../types'\nimport { ReactionCountResource } from '../types/resources/reaction'\nimport { updateRecordInPagesData } from '../utils'\nimport { Clipboard } from '../utils/native_adapters'\n\nexport const ReactScreenOptions: NativeStackNavigationOptions = {\n presentation: 'formSheet',\n headerShown: false,\n sheetAllowedDetents: [0.25],\n sheetGrabberVisible: true,\n}\n\nexport type ReactionScreenProps = StaticScreenProps<{\n message_id: string\n conversation_id: string\n}>\n\nexport function MessageActionsScreen({ route }: ReactionScreenProps) {\n const navigation = useNavigation()\n const { conversation_id, message_id } = route.params\n\n const { client } = useContext(ChatContext)\n const queryClient = useQueryClient()\n const styles = useStyles()\n\n const { messages, queryKey, refetch } = useConversationMessages(\n { conversation_id },\n { refetchOnMount: false }\n )\n const message = messages.find(m => m.id === message_id)\n const myReactions = message?.reactionCounts\n .filter(reaction => reaction.mine)\n .map(reaction => reaction.value)\n\n const availableReactions = Object.entries(REACTION_EMOJIS).map(([value, emoji]) => {\n return {\n value: value as ReactionCountResource['value'],\n emoji,\n mine: myReactions?.includes(value as ReactionCountResource['value']),\n }\n })\n\n const handleCopyPress = (text?: string) => {\n Clipboard.setStringAsync(text || '')\n navigation.goBack()\n }\n\n const handleReactionPress = useCallback(\n ({ value, mine }: { value: keyof typeof REACTION_EMOJIS; mine?: boolean }) => {\n const requestParams = getMessagesRequestArgs({ conversation_id })\n\n // Value has already been updated\n const endpoint = !mine ? 'react' : 'unreact'\n const url = `/me/conversations/${conversation_id}/messages/${message_id}/${endpoint}`\n const fieldsWithValueJoined = Object.fromEntries(\n Object.entries(requestParams.data.fields).map(([k, v]) => [k, v.join(',')])\n )\n\n return client.post({\n url,\n data: {\n ...requestParams.data,\n data: {\n type: 'Message',\n attributes: { value: value },\n },\n fields: fieldsWithValueJoined,\n },\n })\n },\n [client, conversation_id, message_id]\n )\n\n const deleteMessage = useCallback(() => {\n const url = `/me/conversations/${conversation_id}/messages/${message_id}/`\n\n return client.delete({ url })\n }, [client, conversation_id, message_id])\n\n const { mutate: handleReaction, isPending } = useMutation({\n mutationFn: handleReactionPress,\n onSuccess: (result: ApiResource<MessageResource>) => {\n const updatedMessage = result.data\n type QueryData = InfiniteData<ApiCollection<MessageResource>>\n\n queryClient.setQueryData<QueryData>(queryKey, data =>\n updateRecordInPagesData({\n data,\n record: updatedMessage,\n })\n )\n navigation.goBack()\n },\n onError: () => {\n Alert.alert('Oops', 'We were unable to react to this message. Please try again.')\n },\n })\n\n const { mutate: handleDeleteMessage } = useMutation({\n mutationFn: deleteMessage,\n onSuccess: () => {\n refetch()\n navigation.goBack()\n },\n onError: () => {\n Alert.alert('Oops', 'We were unable to delete this message. Please try again.')\n },\n })\n\n return (\n <View style={styles.container}>\n <View style={styles.reactionList}>\n {availableReactions.map((reaction, index) => (\n <Reaction key={index} reaction={reaction} onPress={() => handleReaction(reaction)} />\n ))}\n </View>\n <View style={styles.actions}>\n <View style={styles.actionButton}>\n <TextButton onPress={() => handleCopyPress(message?.text)}>Copy</TextButton>\n <TextButton\n appearance=\"danger\"\n onPress={() => handleDeleteMessage()}\n disabled={isPending}\n >\n Delete\n </TextButton>\n </View>\n </View>\n </View>\n )\n}\n\nconst Reaction = ({\n reaction,\n onPress,\n}: {\n reaction: { value: ReactionCountResource['value']; emoji: string; mine: boolean | undefined }\n onPress: () => void\n}) => {\n const styles = useStyles()\n const reactionStyles = useReactionStyles({ mine: reaction.mine ? 1 : 0 })\n\n return (\n <PlatformPressable\n key={reaction.value}\n style={[reactionStyles.reaction, styles.reaction]}\n onPress={onPress}\n >\n <Text style={reactionStyles.reactionEmoji}>{REACTION_EMOJIS[reaction.value]}</Text>\n </PlatformPressable>\n )\n}\n\nconst useStyles = () => {\n const theme = useTheme()\n const { height } = useWindowDimensions()\n const { bottom } = useSafeAreaInsets()\n const btnBorderWidth = 1\n const baseSize = 44\n const reactionBtnSize = Platform.select({\n ios: baseSize,\n android: baseSize + btnBorderWidth * 2,\n })\n\n return StyleSheet.create({\n container: {\n justifyContent: 'flex-start',\n paddingTop: 12,\n paddingBottom: bottom,\n width: '100%',\n backgroundColor: theme.colors.fillColorNeutral100Inverted,\n height,\n },\n reaction: {\n height: reactionBtnSize,\n width: reactionBtnSize,\n borderWidth: btnBorderWidth,\n borderRadius: 32,\n justifyContent: 'center',\n },\n reactionList: {\n justifyContent: 'center',\n gap: 24,\n paddingVertical: 12,\n flexDirection: 'row',\n borderBottomColor: theme.colors.fillColorNeutral040,\n borderBottomWidth: 1,\n },\n actions: { flex: 1 },\n actionButton: { padding: 12, paddingBottom: 100, gap: 12 },\n })\n}\n"]}
@@ -5,4 +5,5 @@ export * from './space';
5
5
  export * from './client';
6
6
  export * from './uri';
7
7
  export * from './cache';
8
+ export * from './native_adapters';
8
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAA;AACzB,cAAc,SAAS,CAAA;AACvB,cAAc,UAAU,CAAA;AACxB,cAAc,SAAS,CAAA;AACvB,cAAc,UAAU,CAAA;AACxB,cAAc,OAAO,CAAA;AACrB,cAAc,SAAS,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAA;AACzB,cAAc,SAAS,CAAA;AACvB,cAAc,UAAU,CAAA;AACxB,cAAc,SAAS,CAAA;AACvB,cAAc,UAAU,CAAA;AACxB,cAAc,OAAO,CAAA;AACrB,cAAc,SAAS,CAAA;AACvB,cAAc,mBAAmB,CAAA"}
@@ -5,4 +5,5 @@ export * from './space';
5
5
  export * from './client';
6
6
  export * from './uri';
7
7
  export * from './cache';
8
+ export * from './native_adapters';
8
9
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAA;AACzB,cAAc,SAAS,CAAA;AACvB,cAAc,UAAU,CAAA;AACxB,cAAc,SAAS,CAAA;AACvB,cAAc,UAAU,CAAA;AACxB,cAAc,OAAO,CAAA;AACrB,cAAc,SAAS,CAAA","sourcesContent":["export * from './session'\nexport * from './theme'\nexport * from './styles'\nexport * from './space'\nexport * from './client'\nexport * from './uri'\nexport * from './cache'\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAA;AACzB,cAAc,SAAS,CAAA;AACvB,cAAc,UAAU,CAAA;AACxB,cAAc,SAAS,CAAA;AACvB,cAAc,UAAU,CAAA;AACxB,cAAc,OAAO,CAAA;AACrB,cAAc,SAAS,CAAA;AACvB,cAAc,mBAAmB,CAAA","sourcesContent":["export * from './session'\nexport * from './theme'\nexport * from './styles'\nexport * from './space'\nexport * from './client'\nexport * from './uri'\nexport * from './cache'\nexport * from './native_adapters'\n"]}
@@ -0,0 +1,6 @@
1
+ export declare class ClipboardAdapter {
2
+ getStringAsync: () => Promise<string>;
3
+ setStringAsync: (_: string) => Promise<void>;
4
+ constructor(methods: ClipboardAdapter);
5
+ }
6
+ //# sourceMappingURL=clipboard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clipboard.d.ts","sourceRoot":"","sources":["../../../src/utils/native_adapters/clipboard.ts"],"names":[],"mappings":"AAAA,qBAAa,gBAAgB;IAC3B,cAAc,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAA;IACrC,cAAc,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;gBAEhC,OAAO,EAAE,gBAAgB;CAItC"}
@@ -0,0 +1,9 @@
1
+ export class ClipboardAdapter {
2
+ getStringAsync;
3
+ setStringAsync;
4
+ constructor(methods) {
5
+ this.getStringAsync = methods.getStringAsync;
6
+ this.setStringAsync = methods.setStringAsync;
7
+ }
8
+ }
9
+ //# sourceMappingURL=clipboard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clipboard.js","sourceRoot":"","sources":["../../../src/utils/native_adapters/clipboard.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,gBAAgB;IAC3B,cAAc,CAAuB;IACrC,cAAc,CAA8B;IAE5C,YAAY,OAAyB;QACnC,IAAI,CAAC,cAAc,GAAG,OAAO,CAAC,cAAc,CAAA;QAC5C,IAAI,CAAC,cAAc,GAAG,OAAO,CAAC,cAAc,CAAA;IAC9C,CAAC;CACF","sourcesContent":["export class ClipboardAdapter {\n getStringAsync: () => Promise<string>\n setStringAsync: (_: string) => Promise<void>\n\n constructor(methods: ClipboardAdapter) {\n this.getStringAsync = methods.getStringAsync\n this.setStringAsync = methods.setStringAsync\n }\n}\n"]}
@@ -0,0 +1,10 @@
1
+ import { ClipboardAdapter } from './clipboard';
2
+ type ChatConfigurations = {
3
+ clipboard: ClipboardAdapter;
4
+ };
5
+ export declare class ChatAdapters {
6
+ static configure(configurations: ChatConfigurations): void;
7
+ }
8
+ declare let Clipboard: ClipboardAdapter;
9
+ export { Clipboard };
10
+ //# sourceMappingURL=configuration.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"configuration.d.ts","sourceRoot":"","sources":["../../../src/utils/native_adapters/configuration.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAE9C,KAAK,kBAAkB,GAAG;IACxB,SAAS,EAAE,gBAAgB,CAAA;CAC5B,CAAA;AAED,qBAAa,YAAY;IACvB,MAAM,CAAC,SAAS,CAAC,cAAc,EAAE,kBAAkB;CAGpD;AAMD,QAAA,IAAI,SAAS,EAAE,gBAMb,CAAA;AAEF,OAAO,EAAE,SAAS,EAAE,CAAA"}
@@ -0,0 +1,18 @@
1
+ import { ClipboardAdapter } from './clipboard';
2
+ export class ChatAdapters {
3
+ static configure(configurations) {
4
+ Clipboard = configurations.clipboard;
5
+ }
6
+ }
7
+ const methodMissing = () => {
8
+ console.warn('ChatAdapters.configure() must be called before using any adapters');
9
+ };
10
+ let Clipboard = new ClipboardAdapter({
11
+ getStringAsync: async () => {
12
+ methodMissing();
13
+ return '';
14
+ },
15
+ setStringAsync: async (_) => methodMissing(),
16
+ });
17
+ export { Clipboard };
18
+ //# sourceMappingURL=configuration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"configuration.js","sourceRoot":"","sources":["../../../src/utils/native_adapters/configuration.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAM9C,MAAM,OAAO,YAAY;IACvB,MAAM,CAAC,SAAS,CAAC,cAAkC;QACjD,SAAS,GAAG,cAAc,CAAC,SAAS,CAAA;IACtC,CAAC;CACF;AAED,MAAM,aAAa,GAAG,GAAG,EAAE;IACzB,OAAO,CAAC,IAAI,CAAC,mEAAmE,CAAC,CAAA;AACnF,CAAC,CAAA;AAED,IAAI,SAAS,GAAqB,IAAI,gBAAgB,CAAC;IACrD,cAAc,EAAE,KAAK,IAAI,EAAE;QACzB,aAAa,EAAE,CAAA;QACf,OAAO,EAAE,CAAA;IACX,CAAC;IACD,cAAc,EAAE,KAAK,EAAE,CAAS,EAAE,EAAE,CAAC,aAAa,EAAE;CACrD,CAAC,CAAA;AAEF,OAAO,EAAE,SAAS,EAAE,CAAA","sourcesContent":["import { ClipboardAdapter } from './clipboard'\n\ntype ChatConfigurations = {\n clipboard: ClipboardAdapter\n}\n\nexport class ChatAdapters {\n static configure(configurations: ChatConfigurations) {\n Clipboard = configurations.clipboard\n }\n}\n\nconst methodMissing = () => {\n console.warn('ChatAdapters.configure() must be called before using any adapters')\n}\n\nlet Clipboard: ClipboardAdapter = new ClipboardAdapter({\n getStringAsync: async () => {\n methodMissing()\n return ''\n },\n setStringAsync: async (_: string) => methodMissing(),\n})\n\nexport { Clipboard }\n"]}
@@ -0,0 +1,3 @@
1
+ export * from './clipboard';
2
+ export * from './configuration';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utils/native_adapters/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,cAAc,iBAAiB,CAAA"}
@@ -0,0 +1,3 @@
1
+ export * from './clipboard';
2
+ export * from './configuration';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/utils/native_adapters/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,cAAc,iBAAiB,CAAA","sourcesContent":["export * from './clipboard'\nexport * from './configuration'\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@planningcenter/chat-react-native",
3
- "version": "1.7.0-rc.0",
3
+ "version": "2.0.0-rc.0",
4
4
  "description": "",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -47,5 +47,5 @@
47
47
  "prettier": "^3.4.2",
48
48
  "typescript": "<5.6.0"
49
49
  },
50
- "gitHead": "45c925959fee223156aa61022952aaea6437c09a"
50
+ "gitHead": "c661342d905577c0c47d4a094bdf00ee2372471c"
51
51
  }
@@ -0,0 +1,21 @@
1
+ import { ChatAdapters } from '../../../utils/native_adapters/configuration'
2
+ import { Clipboard, ClipboardAdapter } from '../../../utils/native_adapters'
3
+
4
+ describe('ChatAdapters', () => {
5
+ const getStringAsync = jest.fn()
6
+ const setStringAsync = jest.fn()
7
+ const clipboard = new ClipboardAdapter({
8
+ getStringAsync,
9
+ setStringAsync,
10
+ })
11
+
12
+ it('should be defined', () => {
13
+ expect(ChatAdapters).toBeDefined()
14
+ })
15
+
16
+ it('should configure the clipboard', () => {
17
+ ChatAdapters.configure({ clipboard })
18
+
19
+ expect(Clipboard).toEqual(clipboard)
20
+ })
21
+ })
@@ -0,0 +1,87 @@
1
+ import { PlatformPressable } from '@react-navigation/elements'
2
+ import { useNavigation } from '@react-navigation/native'
3
+ import colorFunction from 'color'
4
+ import React from 'react'
5
+ import { StyleSheet, View } from 'react-native'
6
+ import { MessageReaction } from '../../components/conversation/message_reaction'
7
+ import { Avatar, Text } from '../../components/display'
8
+ import { useTheme } from '../../hooks'
9
+ import { MessageResource } from '../../types'
10
+
11
+ /** Message
12
+ * Component for display of a message within a conversation list
13
+ */
14
+ export function Message(props: MessageResource & { conversation_id: string }) {
15
+ const { text, conversation_id, reactionCounts } = props
16
+ const styles = useMessageStyles(props)
17
+ const navigation = useNavigation()
18
+ const handleMessagePress = () => {
19
+ navigation.navigate('MessageActions', {
20
+ message_id: props.id,
21
+ conversation_id,
22
+ })
23
+ }
24
+ // TODO: open the reaction screen to show who reacted
25
+ const handleReactionPress = handleMessagePress
26
+
27
+ if (!text) return null
28
+
29
+ return (
30
+ <View style={styles.message}>
31
+ {!props.mine && (
32
+ <View style={styles.messageAuthor}>
33
+ <Avatar size={'md'} sourceUri={props.author.avatar} />
34
+ </View>
35
+ )}
36
+ <View style={styles.messageContent}>
37
+ {!props.mine && <Text variant="tertiary">{props.author.name}</Text>}
38
+ <PlatformPressable style={styles.messageBubble} onLongPress={handleMessagePress}>
39
+ <Text style={styles.messageText}>{text}</Text>
40
+ </PlatformPressable>
41
+ <View style={styles.messageReactions}>
42
+ {reactionCounts.map(reaction => (
43
+ <MessageReaction
44
+ key={reaction.value}
45
+ reaction={reaction}
46
+ onPress={handleReactionPress}
47
+ />
48
+ ))}
49
+ </View>
50
+ </View>
51
+ </View>
52
+ )
53
+ }
54
+
55
+ const useMessageStyles = ({ mine }: MessageResource) => {
56
+ const { colors } = useTheme()
57
+ const activeColor = colorFunction(colors.interaction).alpha(0.2).string()
58
+
59
+ return StyleSheet.create({
60
+ message: {
61
+ gap: 8,
62
+ flexDirection: mine ? 'row-reverse' : 'row',
63
+ },
64
+ messageContent: {
65
+ gap: 8,
66
+ flexShrink: 1,
67
+ },
68
+ messageAuthor: {
69
+ flexDirection: 'row',
70
+ gap: 8,
71
+ },
72
+ messageBubble: {
73
+ backgroundColor: mine ? activeColor : colors.fillColorNeutral070,
74
+ borderRadius: 12,
75
+ paddingVertical: 6,
76
+ paddingHorizontal: 8,
77
+ },
78
+ messageText: {
79
+ color: colors.textColorDefaultPrimary,
80
+ },
81
+ messageReactions: {
82
+ flexDirection: 'row',
83
+ gap: 4,
84
+ justifyContent: mine ? 'flex-end' : 'flex-start',
85
+ },
86
+ })
87
+ }
@@ -0,0 +1,159 @@
1
+ import { useTheme as useNavigationTheme } from '@react-navigation/native'
2
+ import { InfiniteData, useMutation, useQueryClient } from '@tanstack/react-query'
3
+ import React, { useContext } from 'react'
4
+ import { StyleSheet, TextInput, View, ViewProps } from 'react-native'
5
+ import { IconButton } from '../../components'
6
+ import { ChatContext } from '../../contexts'
7
+ import { useTheme } from '../../hooks'
8
+ import { getMessagesQueryKey, getMessagesRequestArgs } from '../../hooks/use_conversation_messages'
9
+ import { ApiCollection, ApiResource, ConversationResource, MessageResource } from '../../types'
10
+ import { updateRecordInPagesData } from '../../utils'
11
+
12
+ export const MessageForm = {
13
+ Root: MessageFormRoot,
14
+ TextInput: MessageFormInput,
15
+ SubmitButton: MessageFormSubmitBtn,
16
+ AttachmentPicker: MessageFormAttachmentPicker,
17
+ Commands: MessageFormCommands,
18
+ }
19
+
20
+ interface MessagesFormRootProps extends ViewProps {
21
+ conversation: ConversationResource
22
+ }
23
+
24
+ const MessageFormContext = React.createContext({
25
+ text: '',
26
+ setText: (_text: string) => {},
27
+ onSubmit: () => {},
28
+ disabled: false,
29
+ })
30
+
31
+ function MessageFormRoot({ conversation, children }: MessagesFormRootProps) {
32
+ const styles = useMessageFormStyles()
33
+ const [text, setText] = React.useState('')
34
+ const { client } = useContext(ChatContext)
35
+ const queryClient = useQueryClient()
36
+
37
+ const { mutate, isPending } = useMutation({
38
+ mutationFn: async () => {
39
+ const requestParams = getMessagesRequestArgs({ conversation_id: conversation.id })
40
+ const fieldsWithValueJoined = Object.fromEntries(
41
+ Object.entries(requestParams.data.fields).map(([k, v]) => [k, v.join(',')])
42
+ )
43
+
44
+ return client.post({
45
+ url: `/me/conversations/${conversation.id}/messages`,
46
+ data: {
47
+ ...requestParams.data,
48
+ data: {
49
+ type: 'Message',
50
+ attributes: { text },
51
+ },
52
+ fields: fieldsWithValueJoined,
53
+ },
54
+ })
55
+ },
56
+ onSuccess: (result: ApiResource<MessageResource>) => {
57
+ const updatedMessage = result.data
58
+ type QueryData = InfiniteData<ApiCollection<MessageResource>>
59
+ const queryKey = getMessagesQueryKey({ conversation_id: conversation.id })
60
+
61
+ setText('')
62
+ queryClient.setQueryData<QueryData>(queryKey, data =>
63
+ updateRecordInPagesData({
64
+ data,
65
+ record: updatedMessage,
66
+ })
67
+ )
68
+ },
69
+ })
70
+
71
+ return (
72
+ <MessageFormContext.Provider
73
+ value={{ text, setText, onSubmit: () => mutate(), disabled: isPending }}
74
+ >
75
+ <View style={styles.textInputContainer}>{children}</View>
76
+ </MessageFormContext.Provider>
77
+ )
78
+ }
79
+
80
+ function MessageFormInput() {
81
+ const styles = useMessageFormStyles()
82
+ const { text, setText, onSubmit } = React.useContext(MessageFormContext)
83
+
84
+ return (
85
+ <TextInput
86
+ aria-disabled={true}
87
+ placeholder="Send a message"
88
+ onChangeText={setText}
89
+ value={text}
90
+ style={styles.textInput}
91
+ onSubmitEditing={onSubmit}
92
+ />
93
+ )
94
+ }
95
+
96
+ function MessageFormSubmitBtn() {
97
+ const styles = useMessageFormStyles()
98
+ const { onSubmit, disabled } = React.useContext(MessageFormContext)
99
+
100
+ return (
101
+ <IconButton
102
+ disabled={disabled}
103
+ accessibilityLabel="Send message"
104
+ size="md"
105
+ appearance="neutral"
106
+ style={styles.textInputSend}
107
+ name={'general.upArrow'}
108
+ onPress={onSubmit}
109
+ />
110
+ )
111
+ }
112
+
113
+ function MessageFormAttachmentPicker() {
114
+ return (
115
+ <IconButton
116
+ accessibilityLabel="Shazam"
117
+ size="md"
118
+ appearance="neutral"
119
+ name={'general.paperclip'}
120
+ />
121
+ )
122
+ }
123
+
124
+ function MessageFormCommands() {
125
+ return (
126
+ <IconButton accessibilityLabel="Shazam" size="md" appearance="neutral" name={'general.bolt'} />
127
+ )
128
+ }
129
+
130
+ const useMessageFormStyles = () => {
131
+ const theme = useTheme()
132
+ const navigationTheme = useNavigationTheme()
133
+
134
+ return StyleSheet.create({
135
+ textInputContainer: {
136
+ borderColor: theme.colors.fillColorNeutral050Base,
137
+ borderTopWidth: 1,
138
+ padding: 12,
139
+ backgroundColor: navigationTheme.colors.card,
140
+ flexDirection: 'row',
141
+ alignItems: 'center',
142
+ gap: 12,
143
+ },
144
+ textInput: {
145
+ borderRadius: 24,
146
+ borderWidth: 1,
147
+ padding: 12,
148
+ paddingHorizontal: 20,
149
+ borderColor: theme.colors.fillColorNeutral050Base,
150
+ flex: 1,
151
+ },
152
+ textInputSend: {
153
+ borderRadius: 24,
154
+ height: 36,
155
+ width: 36,
156
+ backgroundColor: theme.colors.fillColorNeutral040,
157
+ },
158
+ })
159
+ }
@@ -31,7 +31,7 @@ export function MessageReaction({
31
31
  )
32
32
  }
33
33
 
34
- const useReactionStyles = ({ mine }: { mine: number }) => {
34
+ export const useReactionStyles = ({ mine }: { mine: number }) => {
35
35
  const { colors } = useTheme()
36
36
  const activeBorderColor = colorFunction(colors.interaction).alpha(0.8).string()
37
37
  const activeColor = colorFunction(colors.interaction).alpha(0.2).string()
@@ -50,8 +50,5 @@ const useReactionStyles = ({ mine }: { mine: number }) => {
50
50
  },
51
51
  reactionEmoji: { fontSize: 12, paddingTop: 0 },
52
52
  reactionText: { fontSize: 12, color: colors.textColorDefaultPrimary },
53
- pressed: {
54
- opacity: 0.5,
55
- },
56
53
  })
57
54
  }