@planningcenter/chat-react-native 2.0.0 → 2.1.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.
- package/build/components/conversation/message.d.ts.map +1 -1
- package/build/components/conversation/message.js +7 -2
- package/build/components/conversation/message.js.map +1 -1
- package/build/components/conversation/message_reaction.d.ts +1 -1
- package/build/components/conversation/message_reaction.d.ts.map +1 -1
- package/build/components/conversation/message_reaction.js +1 -1
- package/build/components/conversation/message_reaction.js.map +1 -1
- package/build/components/conversations.d.ts.map +1 -1
- package/build/components/conversations.js +76 -30
- package/build/components/conversations.js.map +1 -1
- package/build/components/display/badge.d.ts +2 -6
- package/build/components/display/badge.d.ts.map +1 -1
- package/build/components/display/badge.js +1 -5
- package/build/components/display/badge.js.map +1 -1
- package/build/components/display/tabs.d.ts +17 -0
- package/build/components/display/tabs.d.ts.map +1 -0
- package/build/components/display/tabs.js +97 -0
- package/build/components/display/tabs.js.map +1 -0
- package/build/contexts/api_provider.js +2 -2
- package/build/contexts/api_provider.js.map +1 -1
- package/build/hooks/use_conversation_jolt_events.d.ts +2 -0
- package/build/hooks/use_conversation_jolt_events.d.ts.map +1 -0
- package/build/hooks/use_conversation_jolt_events.js +47 -0
- package/build/hooks/use_conversation_jolt_events.js.map +1 -0
- package/build/hooks/use_conversation_messages.d.ts +2 -18
- package/build/hooks/use_conversation_messages.d.ts.map +1 -1
- package/build/hooks/use_conversation_messages.js +2 -2
- package/build/hooks/use_conversation_messages.js.map +1 -1
- package/build/hooks/use_conversations.d.ts +37 -0
- package/build/hooks/use_conversations.d.ts.map +1 -0
- package/build/hooks/use_conversations.js +48 -0
- package/build/hooks/use_conversations.js.map +1 -0
- package/build/hooks/use_jolt.d.ts +9 -0
- package/build/hooks/use_jolt.d.ts.map +1 -0
- package/build/hooks/use_jolt.js +71 -0
- package/build/hooks/use_jolt.js.map +1 -0
- package/build/hooks/use_suspense_api.d.ts +7 -2
- package/build/hooks/use_suspense_api.d.ts.map +1 -1
- package/build/hooks/use_suspense_api.js +7 -2
- package/build/hooks/use_suspense_api.js.map +1 -1
- package/build/navigation/index.d.ts +5 -0
- package/build/navigation/index.d.ts.map +1 -1
- package/build/navigation/index.js +7 -2
- package/build/navigation/index.js.map +1 -1
- package/build/screens/message_actions_screen.d.ts +1 -1
- package/build/screens/message_actions_screen.d.ts.map +1 -1
- package/build/screens/message_actions_screen.js +1 -1
- package/build/screens/message_actions_screen.js.map +1 -1
- package/build/screens/reactions_screen.d.ts +11 -0
- package/build/screens/reactions_screen.d.ts.map +1 -0
- package/build/screens/reactions_screen.js +83 -0
- package/build/screens/reactions_screen.js.map +1 -0
- package/build/types/resources/app_name.d.ts +2 -0
- package/build/types/resources/app_name.d.ts.map +1 -0
- package/build/types/resources/app_name.js +2 -0
- package/build/types/resources/app_name.js.map +1 -0
- package/build/types/resources/conversation.d.ts +18 -10
- package/build/types/resources/conversation.d.ts.map +1 -1
- package/build/types/resources/conversation.js.map +1 -1
- package/build/types/resources/conversation_badge.d.ts +12 -0
- package/build/types/resources/conversation_badge.d.ts.map +1 -0
- package/build/types/resources/conversation_badge.js +2 -0
- package/build/types/resources/conversation_badge.js.map +1 -0
- package/build/types/resources/group_resource.d.ts +12 -0
- package/build/types/resources/group_resource.d.ts.map +1 -0
- package/build/types/resources/group_resource.js +2 -0
- package/build/types/resources/group_resource.js.map +1 -0
- package/build/types/resources/index.d.ts +2 -1
- package/build/types/resources/index.d.ts.map +1 -1
- package/build/types/resources/index.js +2 -1
- package/build/types/resources/index.js.map +1 -1
- package/build/types/resources/member.d.ts +23 -0
- package/build/types/resources/member.d.ts.map +1 -0
- package/build/types/resources/member.js +2 -0
- package/build/types/resources/member.js.map +1 -0
- package/build/types/resources/member_ability.d.ts +6 -0
- package/build/types/resources/member_ability.d.ts.map +1 -0
- package/build/types/resources/member_ability.js +2 -0
- package/build/types/resources/member_ability.js.map +1 -0
- package/build/types/resources/reaction.d.ts +1 -1
- package/build/types/resources/reaction.js.map +1 -1
- package/build/utils/cache/page_mutations.d.ts +19 -2
- package/build/utils/cache/page_mutations.d.ts.map +1 -1
- package/build/utils/cache/page_mutations.js +21 -7
- package/build/utils/cache/page_mutations.js.map +1 -1
- package/build/utils/client/client.d.ts +1 -1
- package/build/utils/client/client.d.ts.map +1 -1
- package/build/utils/client/client.js +1 -1
- package/build/utils/client/client.js.map +1 -1
- package/build/utils/date.d.ts +4 -0
- package/build/utils/date.d.ts.map +1 -0
- package/build/utils/date.js +23 -0
- package/build/utils/date.js.map +1 -0
- package/build/utils/session.d.ts +0 -6
- package/build/utils/session.d.ts.map +1 -1
- package/build/utils/session.js +0 -6
- package/build/utils/session.js.map +1 -1
- package/build/utils/uri.d.ts +1 -1
- package/build/utils/uri.d.ts.map +1 -1
- package/build/utils/uri.js +1 -1
- package/build/utils/uri.js.map +1 -1
- package/package.json +7 -3
- package/src/__tests__/utils/cache/page_mutations.ts +7 -46
- package/src/components/conversation/message.tsx +8 -3
- package/src/components/conversation/message_reaction.tsx +6 -2
- package/src/components/conversations.tsx +95 -32
- package/src/components/display/badge.tsx +3 -8
- package/src/components/display/tabs.tsx +142 -0
- package/src/contexts/api_provider.tsx +3 -3
- package/src/hooks/use_conversation_jolt_events.ts +67 -0
- package/src/hooks/use_conversation_messages.ts +6 -2
- package/src/hooks/use_conversations.ts +53 -0
- package/src/hooks/use_jolt.ts +101 -0
- package/src/hooks/use_suspense_api.ts +10 -3
- package/src/navigation/index.tsx +10 -2
- package/src/screens/message_actions_screen.tsx +1 -1
- package/src/screens/reactions_screen.tsx +131 -0
- package/src/types/resources/app_name.ts +1 -0
- package/src/types/resources/conversation.ts +18 -10
- package/src/types/resources/conversation_badge.ts +10 -0
- package/src/types/resources/group_resource.ts +10 -0
- package/src/types/resources/index.ts +2 -1
- package/src/types/resources/member.ts +24 -0
- package/src/types/resources/member_ability.ts +5 -0
- package/src/types/resources/reaction.ts +1 -1
- package/src/utils/cache/page_mutations.ts +32 -9
- package/src/utils/client/client.ts +1 -1
- package/src/utils/date.ts +25 -0
- package/src/utils/session.ts +0 -7
- package/src/utils/uri.ts +1 -1
package/build/utils/uri.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"uri.d.ts","sourceRoot":"","sources":["../../src/utils/uri.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAQnC,
|
|
1
|
+
{"version":3,"file":"uri.d.ts","sourceRoot":"","sources":["../../src/utils/uri.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAQnC,qBAAa,GAAG;IACd,OAAO,EAAE,OAAO,CAAA;IAChB,GAAG,CAAC,EAAE,MAAM,CAAA;gBAEA,EAAE,OAAO,EAAE,GAAG,EAAE,EAAE;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE;IAKhE,IAAI,MAAM,qBAMT;IAED,IAAI,IAAI,6FAWP;IAED,IAAI,GAAG,4BAEN;IAED,IAAI,OAAO,WAEV;IAED,IAAI,SAAS,WAEZ;IAED,IAAI,OAAO;;;MAKV;IAED,MAAM,qBAIL;IAED,GAAG,qBAIF;CACF"}
|
package/build/utils/uri.js
CHANGED
|
@@ -5,7 +5,7 @@ const systemName = DeviceInfo.getSystemName();
|
|
|
5
5
|
const systemVersion = DeviceInfo.getSystemVersion();
|
|
6
6
|
const readableVersion = DeviceInfo.getReadableVersion();
|
|
7
7
|
const appName = DeviceInfo.getApplicationName();
|
|
8
|
-
export
|
|
8
|
+
export class Uri {
|
|
9
9
|
session;
|
|
10
10
|
app;
|
|
11
11
|
constructor({ session, app }) {
|
package/build/utils/uri.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"uri.js","sourceRoot":"","sources":["../../src/utils/uri.ts"],"names":[],"mappings":"AAAA,OAAO,UAAU,MAAM,0BAA0B,CAAA;AAEjD,MAAM,KAAK,GAAG,UAAU,CAAC,QAAQ,EAAE,CAAA;AACnC,MAAM,KAAK,GAAG,UAAU,CAAC,QAAQ,EAAE,CAAA;AACnC,MAAM,UAAU,GAAG,UAAU,CAAC,aAAa,EAAE,CAAA;AAC7C,MAAM,aAAa,GAAG,UAAU,CAAC,gBAAgB,EAAE,CAAA;AACnD,MAAM,eAAe,GAAG,UAAU,CAAC,kBAAkB,EAAE,CAAA;AACvD,MAAM,OAAO,GAAG,UAAU,CAAC,kBAAkB,EAAE,CAAA;AAE/C,MAAM,
|
|
1
|
+
{"version":3,"file":"uri.js","sourceRoot":"","sources":["../../src/utils/uri.ts"],"names":[],"mappings":"AAAA,OAAO,UAAU,MAAM,0BAA0B,CAAA;AAEjD,MAAM,KAAK,GAAG,UAAU,CAAC,QAAQ,EAAE,CAAA;AACnC,MAAM,KAAK,GAAG,UAAU,CAAC,QAAQ,EAAE,CAAA;AACnC,MAAM,UAAU,GAAG,UAAU,CAAC,aAAa,EAAE,CAAA;AAC7C,MAAM,aAAa,GAAG,UAAU,CAAC,gBAAgB,EAAE,CAAA;AACnD,MAAM,eAAe,GAAG,UAAU,CAAC,kBAAkB,EAAE,CAAA;AACvD,MAAM,OAAO,GAAG,UAAU,CAAC,kBAAkB,EAAE,CAAA;AAE/C,MAAM,OAAO,GAAG;IACd,OAAO,CAAS;IAChB,GAAG,CAAS;IAEZ,YAAY,EAAE,OAAO,EAAE,GAAG,EAAsC;QAC9D,IAAI,CAAC,OAAO,GAAG,OAAO,CAAA;QACtB,IAAI,CAAC,GAAG,GAAG,GAAG,CAAA;IAChB,CAAC;IAED,IAAI,MAAM;QACR,IAAI,IAAI,CAAC,GAAG,KAAK,aAAa,EAAE,CAAC;YAC/B,OAAO,MAAM,CAAA;QACf,CAAC;aAAM,CAAC;YACN,OAAO,OAAO,CAAA;QAChB,CAAC;IACH,CAAC;IAED,IAAI,IAAI;QACN,QAAQ,IAAI,CAAC,GAAG,EAAE,CAAC;YACjB,KAAK,YAAY;gBACf,OAAO,8BAA8B,CAAA;YACvC,KAAK,SAAS;gBACZ,OAAO,sCAAsC,CAAA;YAC/C,KAAK,aAAa;gBAChB,OAAO,cAAc,CAAA;YACvB;gBACE,OAAO,8BAA8B,CAAA;QACzC,CAAC;IACH,CAAC;IAED,IAAI,GAAG;QACL,OAAO,IAAI,CAAC,OAAO,EAAE,GAAG,IAAI,YAAY,CAAA;IAC1C,CAAC;IAED,IAAI,OAAO;QACT,OAAO,GAAG,IAAI,CAAC,MAAM,MAAM,IAAI,CAAC,IAAI,EAAE,CAAA;IACxC,CAAC;IAED,IAAI,SAAS;QACX,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,EAAE,CAAA;IAC1C,CAAC;IAED,IAAI,OAAO;QACT,OAAO;YACL,YAAY,EAAE,GAAG,OAAO,IAAI,eAAe,KAAK,KAAK,KAAK,KAAK,KAAK,UAAU,KAAK,aAAa,GAAG;YACnG,aAAa,EAAE,UAAU,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,YAAY,EAAE;SAC5D,CAAA;IACH,CAAC;IAED,MAAM,GAAG,IAAI,CAAC,EAAE;QACd,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAA;QAErD,OAAO,GAAG,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,SAAS,GAAG,IAAI,IAAI,GAAG,EAAE,CAAA;IACzD,CAAC,CAAA;IAED,GAAG,GAAG,IAAI,CAAC,EAAE;QACX,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAA;QAErD,OAAO,GAAG,IAAI,CAAC,OAAO,GAAG,IAAI,IAAI,GAAG,EAAE,CAAA;IACxC,CAAC,CAAA;CACF","sourcesContent":["import DeviceInfo from 'react-native-device-info'\nimport { Session } from './session'\nconst brand = DeviceInfo.getBrand()\nconst model = DeviceInfo.getModel()\nconst systemName = DeviceInfo.getSystemName()\nconst systemVersion = DeviceInfo.getSystemVersion()\nconst readableVersion = DeviceInfo.getReadableVersion()\nconst appName = DeviceInfo.getApplicationName()\n\nexport class Uri {\n session: Session\n app?: string\n\n constructor({ session, app }: { session: Session; app?: string }) {\n this.session = session\n this.app = app\n }\n\n get schema() {\n if (this.env === 'development') {\n return 'http'\n } else {\n return 'https'\n }\n }\n\n get host() {\n switch (this.env) {\n case 'production':\n return 'api.planningcenteronline.com'\n case 'staging':\n return 'api-staging.planningcenteronline.com'\n case 'development':\n return 'api.pco.test'\n default:\n return 'api.planningcenteronline.com'\n }\n }\n\n get env() {\n return this.session?.env || 'production'\n }\n\n get baseUrl() {\n return `${this.schema}://${this.host}`\n }\n\n get directory() {\n return this.app ? `/${this.app}/v2` : ''\n }\n\n get headers() {\n return {\n 'User-Agent': `${appName}/${readableVersion} (${brand}, ${model}, ${systemName}, ${systemVersion})`,\n Authorization: `Bearer ${this.session.token?.access_token}`,\n }\n }\n\n appUrl = path => {\n if (path.startsWith(`${this.schema}://`)) return path\n\n return `${this.baseUrl}${this.directory}${path || '/'}`\n }\n\n api = path => {\n if (path.startsWith(`${this.schema}://`)) return path\n\n return `${this.baseUrl}${path || '/'}`\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@planningcenter/chat-react-native",
|
|
3
|
-
"version": "2.0.0",
|
|
3
|
+
"version": "2.1.0-rc.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -24,13 +24,17 @@
|
|
|
24
24
|
"react-native-url-polyfill": "^2.0.0"
|
|
25
25
|
},
|
|
26
26
|
"peerDependencies": {
|
|
27
|
-
"@planningcenter/
|
|
27
|
+
"@planningcenter/datetime-fmt": ">=1.0.0",
|
|
28
|
+
"@planningcenter/icons": ">=15.0.0",
|
|
29
|
+
"@planningcenter/jolt-client": ">=2.0.0",
|
|
28
30
|
"@react-navigation/elements": "*",
|
|
29
31
|
"@react-navigation/native": ">=7.0.0",
|
|
30
32
|
"@react-navigation/native-stack": ">=7.0.0",
|
|
31
33
|
"@tanstack/react-query": "^5.0.0",
|
|
32
34
|
"color": "^3.1.2",
|
|
33
35
|
"lodash": "*",
|
|
36
|
+
"moment": ">=2.0.0",
|
|
37
|
+
"moment-timezone": ">=2.0.0",
|
|
34
38
|
"react": "*",
|
|
35
39
|
"react-native": "*",
|
|
36
40
|
"react-native-device-info": "*",
|
|
@@ -47,5 +51,5 @@
|
|
|
47
51
|
"prettier": "^3.4.2",
|
|
48
52
|
"typescript": "<5.6.0"
|
|
49
53
|
},
|
|
50
|
-
"gitHead": "
|
|
54
|
+
"gitHead": "9ada391b04ffc168aa7ffb4baaa746429ee7e093"
|
|
51
55
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { deleteRecordInPagesData, updateRecordInPagesData } from '../../../utils/'
|
|
2
2
|
|
|
3
3
|
const data = {
|
|
4
4
|
pageParams: {},
|
|
@@ -105,6 +105,7 @@ describe('updateRecordInPagesData', () => {
|
|
|
105
105
|
pages: [
|
|
106
106
|
{
|
|
107
107
|
data: [
|
|
108
|
+
{ id, type: 'Message', text: `updated example ${id}` },
|
|
108
109
|
{ id: '1', type: 'Message', text: 'message 1' },
|
|
109
110
|
{ id: '2', type: 'Message', text: 'message 2' },
|
|
110
111
|
],
|
|
@@ -116,7 +117,6 @@ describe('updateRecordInPagesData', () => {
|
|
|
116
117
|
data: [
|
|
117
118
|
{ id: '3', type: 'Message', text: 'message 3' },
|
|
118
119
|
{ id: '4', type: 'Message', text: 'message 4' },
|
|
119
|
-
{ id, type: 'Message', text: `updated example ${id}` },
|
|
120
120
|
],
|
|
121
121
|
included: [],
|
|
122
122
|
links: {},
|
|
@@ -127,49 +127,14 @@ describe('updateRecordInPagesData', () => {
|
|
|
127
127
|
})
|
|
128
128
|
})
|
|
129
129
|
|
|
130
|
-
describe('
|
|
131
|
-
it('should
|
|
132
|
-
const id = '
|
|
130
|
+
describe('deleteRecordInPagesData', () => {
|
|
131
|
+
it('should delete records from page', () => {
|
|
132
|
+
const id = '4'
|
|
133
133
|
const record = createRecord({ id })
|
|
134
134
|
|
|
135
|
-
const result =
|
|
136
|
-
data,
|
|
137
|
-
record,
|
|
138
|
-
})
|
|
139
|
-
|
|
140
|
-
expect(result).toEqual({
|
|
141
|
-
pageParams: {},
|
|
142
|
-
pages: [
|
|
143
|
-
{
|
|
144
|
-
data: [
|
|
145
|
-
{ id: '1', type: 'Message', text: 'message 1' },
|
|
146
|
-
{ id: '2', type: 'Message', text: 'message 2' },
|
|
147
|
-
],
|
|
148
|
-
included: [],
|
|
149
|
-
links: {},
|
|
150
|
-
meta: { count: 2, totalCount: 2 },
|
|
151
|
-
},
|
|
152
|
-
{
|
|
153
|
-
data: [
|
|
154
|
-
{ id: '3', type: 'Message', text: 'message 3' },
|
|
155
|
-
{ id: '4', type: 'Message', text: 'message 4' },
|
|
156
|
-
record,
|
|
157
|
-
],
|
|
158
|
-
included: [],
|
|
159
|
-
links: {},
|
|
160
|
-
meta: { count: 2, totalCount: 2 },
|
|
161
|
-
},
|
|
162
|
-
],
|
|
163
|
-
})
|
|
164
|
-
})
|
|
165
|
-
|
|
166
|
-
it('should add the record in the pages data and update with a custom processRecord function', () => {
|
|
167
|
-
const id = '8'
|
|
168
|
-
const record = createRecord({ id })
|
|
169
|
-
const result = addRecordInPagesData<typeof record>({
|
|
135
|
+
const result = deleteRecordInPagesData<typeof record>({
|
|
170
136
|
data,
|
|
171
137
|
record,
|
|
172
|
-
processRecord: r => ({ ...r, text: 'updated ' + r.text }),
|
|
173
138
|
})
|
|
174
139
|
|
|
175
140
|
expect(result).toEqual({
|
|
@@ -185,11 +150,7 @@ describe('addRecordInPagesData', () => {
|
|
|
185
150
|
meta: { count: 2, totalCount: 2 },
|
|
186
151
|
},
|
|
187
152
|
{
|
|
188
|
-
data: [
|
|
189
|
-
{ id: '3', type: 'Message', text: 'message 3' },
|
|
190
|
-
{ id: '4', type: 'Message', text: 'message 4' },
|
|
191
|
-
{ ...record, text: `updated example ${id}` },
|
|
192
|
-
],
|
|
153
|
+
data: [{ id: '3', type: 'Message', text: 'message 3' }],
|
|
193
154
|
included: [],
|
|
194
155
|
links: {},
|
|
195
156
|
meta: { count: 2, totalCount: 2 },
|
|
@@ -7,6 +7,7 @@ import { MessageReaction } from '../../components/conversation/message_reaction'
|
|
|
7
7
|
import { Avatar, Text } from '../../components/display'
|
|
8
8
|
import { useTheme } from '../../hooks'
|
|
9
9
|
import { MessageResource } from '../../types'
|
|
10
|
+
import { ReactionCountResource } from '../../types/resources/reaction'
|
|
10
11
|
|
|
11
12
|
/** Message
|
|
12
13
|
* Component for display of a message within a conversation list
|
|
@@ -21,9 +22,13 @@ export function Message(props: MessageResource & { conversation_id: string }) {
|
|
|
21
22
|
conversation_id,
|
|
22
23
|
})
|
|
23
24
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
const handleReactionPress = (reaction: ReactionCountResource) => {
|
|
26
|
+
navigation.navigate('Reactions', {
|
|
27
|
+
message_id: props.id,
|
|
28
|
+
conversation_id,
|
|
29
|
+
reaction_value: reaction.value,
|
|
30
|
+
})
|
|
31
|
+
}
|
|
27
32
|
if (!text) return null
|
|
28
33
|
|
|
29
34
|
return (
|
|
@@ -19,12 +19,16 @@ export function MessageReaction({
|
|
|
19
19
|
onPress,
|
|
20
20
|
}: {
|
|
21
21
|
reaction: ReactionCountResource
|
|
22
|
-
onPress: () => void
|
|
22
|
+
onPress: (_reaction: ReactionCountResource) => void
|
|
23
23
|
}) {
|
|
24
24
|
const styles = useReactionStyles(reaction)
|
|
25
25
|
|
|
26
26
|
return (
|
|
27
|
-
<PlatformPressable
|
|
27
|
+
<PlatformPressable
|
|
28
|
+
key={reaction.value}
|
|
29
|
+
style={styles.reaction}
|
|
30
|
+
onPress={() => onPress(reaction)}
|
|
31
|
+
>
|
|
28
32
|
<Text style={styles.reactionEmoji}>{REACTION_EMOJIS[reaction.value]}</Text>
|
|
29
33
|
<Text style={styles.reactionText}>{reaction.count}</Text>
|
|
30
34
|
</PlatformPressable>
|
|
@@ -1,45 +1,42 @@
|
|
|
1
1
|
import { useNavigation } from '@react-navigation/native'
|
|
2
2
|
import React from 'react'
|
|
3
|
-
import { FlatList, Pressable, StyleSheet } from 'react-native'
|
|
3
|
+
import { FlatList, Pressable, StyleSheet, View } from 'react-native'
|
|
4
4
|
import { useTheme } from '../hooks'
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { Heading, Text } from './display'
|
|
5
|
+
import { useConversationsJoltEvents } from '../hooks/use_conversation_jolt_events'
|
|
6
|
+
import { useConversations } from '../hooks/use_conversations'
|
|
7
|
+
import { formatDatePreview } from '../utils/date'
|
|
8
|
+
import { AvatarGroup, Badge, Heading, Text, TextButton } from './display'
|
|
9
9
|
|
|
10
10
|
export const Conversations = () => {
|
|
11
11
|
const styles = useStyles()
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
data: {
|
|
15
|
-
perPage: 20,
|
|
16
|
-
order: '-last_message',
|
|
17
|
-
fields: {
|
|
18
|
-
Conversation: [
|
|
19
|
-
'title',
|
|
20
|
-
'last_message_created_at',
|
|
21
|
-
'last_message_author_name',
|
|
22
|
-
'last_message_text_preview',
|
|
23
|
-
'unread_count',
|
|
24
|
-
],
|
|
25
|
-
},
|
|
26
|
-
},
|
|
27
|
-
}
|
|
28
|
-
const { data: conversations, fetchNextPage } = useSuspensePaginator<ConversationResource>(request)
|
|
12
|
+
|
|
13
|
+
const { conversations, fetchNextPage, refetch, isRefetching } = useConversations()
|
|
29
14
|
|
|
30
15
|
// TODO: Filter using the API
|
|
31
16
|
const nonEmptyConversations = conversations.filter(c => c.lastMessageTextPreview) || []
|
|
32
|
-
|
|
33
17
|
const navigation = useNavigation()
|
|
34
18
|
|
|
19
|
+
useConversationsJoltEvents()
|
|
20
|
+
|
|
35
21
|
return (
|
|
36
22
|
<FlatList
|
|
37
23
|
data={nonEmptyConversations}
|
|
38
24
|
contentContainerStyle={styles.container}
|
|
39
25
|
style={styles.scrollView}
|
|
26
|
+
onRefresh={refetch}
|
|
27
|
+
refreshing={isRefetching}
|
|
28
|
+
ListHeaderComponent={
|
|
29
|
+
<View style={styles.header}>
|
|
30
|
+
<Heading numberOfLines={1} variant="h2">
|
|
31
|
+
Conversations
|
|
32
|
+
</Heading>
|
|
33
|
+
<TextButton>Mark all read</TextButton>
|
|
34
|
+
</View>
|
|
35
|
+
}
|
|
40
36
|
ListEmptyComponent={<Text>No conversations found</Text>}
|
|
41
37
|
renderItem={({ item }) => (
|
|
42
38
|
<Pressable
|
|
39
|
+
style={styles.conversation}
|
|
43
40
|
onPress={() =>
|
|
44
41
|
navigation.navigate('Conversation', {
|
|
45
42
|
conversation_id: item.id,
|
|
@@ -47,12 +44,30 @@ export const Conversations = () => {
|
|
|
47
44
|
})
|
|
48
45
|
}
|
|
49
46
|
>
|
|
50
|
-
<
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
47
|
+
<AvatarGroup size="lg" sourceUris={item.previewAvatarUrls || []} />
|
|
48
|
+
<View style={styles.conversationBody}>
|
|
49
|
+
<Heading numberOfLines={1} variant="h3" style={styles.conversationTitle}>
|
|
50
|
+
{item.title}
|
|
51
|
+
</Heading>
|
|
52
|
+
<Text style={styles.listItem} numberOfLines={2}>
|
|
53
|
+
{item.lastMessageAuthorName}: {item.lastMessageTextPreview}
|
|
54
|
+
</Text>
|
|
55
|
+
<View style={styles.conversationBadges}>
|
|
56
|
+
{item.badges?.map(badge => (
|
|
57
|
+
<Badge
|
|
58
|
+
key={badge.text}
|
|
59
|
+
variant="meta"
|
|
60
|
+
productLogoName={badge.appName}
|
|
61
|
+
label={badge.pcoResourceType}
|
|
62
|
+
metaLabel={badge.text || ''}
|
|
63
|
+
/>
|
|
64
|
+
))}
|
|
65
|
+
</View>
|
|
66
|
+
</View>
|
|
67
|
+
<View style={styles.conversationExtra}>
|
|
68
|
+
<Text variant="secondary">{formatDatePreview(item.lastMessageCreatedAt)}</Text>
|
|
69
|
+
<UnreadCountBadge count={item.unreadCount} />
|
|
70
|
+
</View>
|
|
56
71
|
</Pressable>
|
|
57
72
|
)}
|
|
58
73
|
onEndReached={() => fetchNextPage()}
|
|
@@ -60,13 +75,61 @@ export const Conversations = () => {
|
|
|
60
75
|
)
|
|
61
76
|
}
|
|
62
77
|
|
|
78
|
+
const UnreadCountBadge = ({ count }: { count: number }) => {
|
|
79
|
+
const styles = useStyles()
|
|
80
|
+
const displayCount = count > 99 ? '99+' : count
|
|
81
|
+
|
|
82
|
+
if (count === 0) return null
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<Text variant="tertiary" style={styles.unreadCountBadge}>
|
|
86
|
+
{displayCount}
|
|
87
|
+
</Text>
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
|
|
63
91
|
const useStyles = () => {
|
|
64
92
|
const { colors } = useTheme()
|
|
65
93
|
|
|
66
94
|
return StyleSheet.create({
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
95
|
+
header: {
|
|
96
|
+
flexDirection: 'row',
|
|
97
|
+
justifyContent: 'space-between',
|
|
98
|
+
paddingTop: 8,
|
|
99
|
+
paddingBottom: 8,
|
|
100
|
+
paddingHorizontal: 16,
|
|
101
|
+
},
|
|
102
|
+
scrollView: { flex: 1 },
|
|
103
|
+
container: { gap: 8, paddingVertical: 16 },
|
|
70
104
|
listItem: { color: colors.fillColorNeutral020 },
|
|
105
|
+
conversation: {
|
|
106
|
+
flexDirection: 'row',
|
|
107
|
+
gap: 8,
|
|
108
|
+
borderBottomWidth: 1,
|
|
109
|
+
borderBottomColor: colors.fillColorNeutral060,
|
|
110
|
+
paddingTop: 4,
|
|
111
|
+
paddingBottom: 12,
|
|
112
|
+
paddingHorizontal: 16,
|
|
113
|
+
},
|
|
114
|
+
conversationTitle: {},
|
|
115
|
+
conversationBody: {
|
|
116
|
+
flex: 1,
|
|
117
|
+
rowGap: 2,
|
|
118
|
+
},
|
|
119
|
+
conversationExtra: {
|
|
120
|
+
rowGap: 2,
|
|
121
|
+
},
|
|
122
|
+
conversationBadges: {
|
|
123
|
+
marginTop: 4,
|
|
124
|
+
alignItems: 'flex-start',
|
|
125
|
+
},
|
|
126
|
+
unreadCountBadge: {
|
|
127
|
+
alignSelf: 'flex-end',
|
|
128
|
+
backgroundColor: colors.interaction,
|
|
129
|
+
paddingVertical: 0,
|
|
130
|
+
paddingHorizontal: 10,
|
|
131
|
+
borderRadius: 24,
|
|
132
|
+
color: 'white',
|
|
133
|
+
},
|
|
71
134
|
})
|
|
72
135
|
}
|
|
@@ -54,12 +54,7 @@ type VariantStyles = Record<
|
|
|
54
54
|
}
|
|
55
55
|
>
|
|
56
56
|
|
|
57
|
-
|
|
58
|
-
groups: 'groups',
|
|
59
|
-
services: 'services',
|
|
60
|
-
} as const
|
|
61
|
-
|
|
62
|
-
type PoductLogoNameUnion = (typeof LOGO_NAMES)[keyof typeof LOGO_NAMES]
|
|
57
|
+
type ProductLogoName = 'Groups' | 'Services' | 'groups' | 'services'
|
|
63
58
|
|
|
64
59
|
const PRODUCT_LOGO_COMPONENT_MAP = {
|
|
65
60
|
groups: GroupsLogo,
|
|
@@ -90,7 +85,7 @@ interface BadgeProps {
|
|
|
90
85
|
/**
|
|
91
86
|
* Adds a product logo to the left of the text.
|
|
92
87
|
*/
|
|
93
|
-
productLogoName?:
|
|
88
|
+
productLogoName?: ProductLogoName
|
|
94
89
|
/**
|
|
95
90
|
* Shows an icon of the user choice to the left of the text.
|
|
96
91
|
*/
|
|
@@ -123,7 +118,7 @@ export function Badge({
|
|
|
123
118
|
const hasMetaLabel = Boolean(metaLabel)
|
|
124
119
|
|
|
125
120
|
const showLogo = showBadgeLogo && productLogoName && isMeta
|
|
126
|
-
const ProductLogoSvg = showLogo && PRODUCT_LOGO_COMPONENT_MAP[productLogoName]
|
|
121
|
+
const ProductLogoSvg = showLogo && PRODUCT_LOGO_COMPONENT_MAP[productLogoName?.toLowerCase()]
|
|
127
122
|
const badgeLabel = isMetaSubtle && hasMetaLabel ? `${label}:` : label
|
|
128
123
|
|
|
129
124
|
return (
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { PlatformPressable } from '@react-navigation/elements'
|
|
2
|
+
import React, { useEffect, useRef, useState } from 'react'
|
|
3
|
+
import { Animated, Easing, StyleSheet, View, ViewStyle } from 'react-native'
|
|
4
|
+
import { useTheme } from '../../hooks'
|
|
5
|
+
|
|
6
|
+
// =================================
|
|
7
|
+
// ====== Component ================
|
|
8
|
+
// =================================
|
|
9
|
+
|
|
10
|
+
interface TabsProps<ItemT> {
|
|
11
|
+
data: ArrayLike<ItemT> | null | undefined
|
|
12
|
+
activeTab?: ItemT
|
|
13
|
+
keyExtractor?: (_item: ItemT, _index?: number) => string
|
|
14
|
+
onTabPress?: (_item: ItemT) => void
|
|
15
|
+
renderItem: (_: { item: ItemT; index: number }) => React.ReactNode
|
|
16
|
+
style?: ViewStyle
|
|
17
|
+
contentContainerStyle?: ViewStyle
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const defaultKeyExtractor = (item: any, index?: number) => {
|
|
21
|
+
if (typeof item === 'string') return item
|
|
22
|
+
|
|
23
|
+
return item.id || index?.toString() || item.toString()
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function Tabs<ItemT>({
|
|
27
|
+
activeTab = { id: '' } as ItemT,
|
|
28
|
+
contentContainerStyle,
|
|
29
|
+
data,
|
|
30
|
+
keyExtractor = defaultKeyExtractor,
|
|
31
|
+
onTabPress,
|
|
32
|
+
renderItem,
|
|
33
|
+
style,
|
|
34
|
+
}: TabsProps<ItemT>) {
|
|
35
|
+
const [tabDimSet, setTabDimensions] = useState<Set<{ index: number; width: number }>>(new Set())
|
|
36
|
+
const tabDimensions = Array.from(tabDimSet)
|
|
37
|
+
const [tabHeight, setTabHeight] = useState(0)
|
|
38
|
+
const styles = useStyles()
|
|
39
|
+
const opacity = useRef(new Animated.Value(0)).current
|
|
40
|
+
const tabCursorPosition = useRef(new Animated.Value(0)).current
|
|
41
|
+
const dataArray = Array.from(data || [])
|
|
42
|
+
const activeTabIndex = dataArray.findIndex(
|
|
43
|
+
(item, index) => keyExtractor(item, index) === keyExtractor(activeTab)
|
|
44
|
+
)
|
|
45
|
+
const gap = 8
|
|
46
|
+
const tabCursorSpacing = tabDimensions
|
|
47
|
+
.slice(0, activeTabIndex)
|
|
48
|
+
.reduce((acc, { width }) => acc + width + gap, 0)
|
|
49
|
+
|
|
50
|
+
Animated.timing(tabCursorPosition, {
|
|
51
|
+
toValue: tabCursorSpacing,
|
|
52
|
+
easing: Easing.inOut(Easing.ease),
|
|
53
|
+
duration: 100,
|
|
54
|
+
useNativeDriver: true,
|
|
55
|
+
}).start()
|
|
56
|
+
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
if (activeTabIndex === -1) return
|
|
59
|
+
|
|
60
|
+
Animated.timing(opacity, {
|
|
61
|
+
toValue: 1,
|
|
62
|
+
easing: Easing.inOut(Easing.ease),
|
|
63
|
+
duration: 500,
|
|
64
|
+
useNativeDriver: true,
|
|
65
|
+
}).start()
|
|
66
|
+
}, [opacity, activeTabIndex])
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<View style={[styles.container, style]}>
|
|
70
|
+
<Animated.View style={[styles.contentContainer, contentContainerStyle]}>
|
|
71
|
+
<View
|
|
72
|
+
style={styles.tabsContainer}
|
|
73
|
+
onLayout={event => {
|
|
74
|
+
const { height } = event.nativeEvent.layout
|
|
75
|
+
setTabHeight(height)
|
|
76
|
+
}}
|
|
77
|
+
>
|
|
78
|
+
{dataArray.map((item, index) => {
|
|
79
|
+
return (
|
|
80
|
+
<PlatformPressable
|
|
81
|
+
key={index}
|
|
82
|
+
style={styles.tab}
|
|
83
|
+
onPress={() => {
|
|
84
|
+
onTabPress?.(item)
|
|
85
|
+
}}
|
|
86
|
+
>
|
|
87
|
+
<View
|
|
88
|
+
onLayout={event => {
|
|
89
|
+
const { width } = event.nativeEvent.layout
|
|
90
|
+
setTabDimensions(dimensions => dimensions.add({ index, width }))
|
|
91
|
+
}}
|
|
92
|
+
>
|
|
93
|
+
{renderItem({ item, index })}
|
|
94
|
+
</View>
|
|
95
|
+
</PlatformPressable>
|
|
96
|
+
)
|
|
97
|
+
})}
|
|
98
|
+
</View>
|
|
99
|
+
<Animated.View
|
|
100
|
+
style={[
|
|
101
|
+
styles.cursor,
|
|
102
|
+
{
|
|
103
|
+
opacity,
|
|
104
|
+
top: tabHeight - 5,
|
|
105
|
+
width: tabDimensions[activeTabIndex]?.width || 0,
|
|
106
|
+
transform: [{ translateX: tabCursorPosition }],
|
|
107
|
+
},
|
|
108
|
+
]}
|
|
109
|
+
/>
|
|
110
|
+
</Animated.View>
|
|
111
|
+
</View>
|
|
112
|
+
)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// =================================
|
|
116
|
+
// ====== Styles ===================
|
|
117
|
+
// =================================
|
|
118
|
+
|
|
119
|
+
const useStyles = () => {
|
|
120
|
+
const theme = useTheme()
|
|
121
|
+
return StyleSheet.create({
|
|
122
|
+
container: {
|
|
123
|
+
flexDirection: 'row',
|
|
124
|
+
justifyContent: 'center',
|
|
125
|
+
},
|
|
126
|
+
contentContainer: {},
|
|
127
|
+
cursor: {
|
|
128
|
+
borderBottomWidth: 3,
|
|
129
|
+
borderBottomColor: theme.colors.interaction,
|
|
130
|
+
height: 5,
|
|
131
|
+
flex: 1,
|
|
132
|
+
position: 'absolute',
|
|
133
|
+
zIndex: 5,
|
|
134
|
+
},
|
|
135
|
+
tab: {},
|
|
136
|
+
tabsContainer: {
|
|
137
|
+
flex: 1,
|
|
138
|
+
flexDirection: 'row',
|
|
139
|
+
gap: 8,
|
|
140
|
+
},
|
|
141
|
+
})
|
|
142
|
+
}
|
|
@@ -2,8 +2,8 @@ import { QueryClient, QueryClientProvider, QueryKey } from '@tanstack/react-quer
|
|
|
2
2
|
import React, { useContext, useEffect, useRef } from 'react'
|
|
3
3
|
import { ViewProps } from 'react-native'
|
|
4
4
|
import { Client } from '../utils'
|
|
5
|
-
import { GetRequest } from '../utils/client/types'
|
|
6
5
|
import { ChatContext, ChatContextValue } from './chat_context'
|
|
6
|
+
import { RequestQueryKey } from '../hooks'
|
|
7
7
|
|
|
8
8
|
let apiClient: Client | undefined
|
|
9
9
|
|
|
@@ -12,9 +12,9 @@ const defaultQueryFn = ({ queryKey }: { queryKey: QueryKey }) => {
|
|
|
12
12
|
throw new Error('No token present')
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
const data = queryKey
|
|
15
|
+
const [url, data, headers] = queryKey as RequestQueryKey
|
|
16
16
|
|
|
17
|
-
return apiClient.get(data)
|
|
17
|
+
return apiClient.get({ url, data, headers })
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
export const queryClient = new QueryClient({
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { CustomMessage } from '@planningcenter/jolt-client/dist/types/JoltConnection'
|
|
2
|
+
import { InfiniteData, useQueryClient } from '@tanstack/react-query'
|
|
3
|
+
import { useContext } from 'react'
|
|
4
|
+
import { ChatContext } from '../contexts'
|
|
5
|
+
import { ApiCollection, ApiResource, ConversationResource } from '../types'
|
|
6
|
+
import { deleteRecordInPagesData, updateRecordInPagesData } from '../utils'
|
|
7
|
+
import { getConversationsRequestArgs } from './use_conversations'
|
|
8
|
+
import { useCurrentPerson } from './use_current_person'
|
|
9
|
+
import { useJoltChannel, useJoltEvent } from './use_jolt'
|
|
10
|
+
import { getRequestQueryKey } from './use_suspense_api'
|
|
11
|
+
|
|
12
|
+
type QueryData = InfiniteData<ApiCollection<ConversationResource>>
|
|
13
|
+
interface JoltConversationsEvent extends CustomMessage {
|
|
14
|
+
data: {
|
|
15
|
+
data: ConversationResource
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function useConversationsJoltEvents() {
|
|
20
|
+
const { client } = useContext(ChatContext)
|
|
21
|
+
const queryClient = useQueryClient()
|
|
22
|
+
const currentPerson = useCurrentPerson()
|
|
23
|
+
const joltChannel = useJoltChannel(`chat.people.${currentPerson.id}`)
|
|
24
|
+
|
|
25
|
+
const conversationsRequestArgs = getConversationsRequestArgs()
|
|
26
|
+
const conversationQueryKey = getRequestQueryKey(conversationsRequestArgs)
|
|
27
|
+
|
|
28
|
+
const fetchConversation = async ({ id }: ConversationResource) => {
|
|
29
|
+
const { data: argsData } = conversationsRequestArgs
|
|
30
|
+
const { data } = await client.get<ApiResource<ConversationResource>>({
|
|
31
|
+
url: `/me/conversations/${id}`,
|
|
32
|
+
data: {
|
|
33
|
+
fields: argsData.fields,
|
|
34
|
+
include: argsData.include,
|
|
35
|
+
},
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
return data
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const handleConversationUpdateOrCreate = async (e: JoltConversationsEvent) => {
|
|
42
|
+
const conversation = await fetchConversation(e.data.data).catch(c => c)
|
|
43
|
+
|
|
44
|
+
queryClient.setQueryData<QueryData>(conversationQueryKey, prev =>
|
|
45
|
+
updateRecordInPagesData({
|
|
46
|
+
data: prev,
|
|
47
|
+
record: conversation,
|
|
48
|
+
processRecord: (record, current) => {
|
|
49
|
+
return { ...current, ...record }
|
|
50
|
+
},
|
|
51
|
+
})
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const handleConversationDestroy = (e: JoltConversationsEvent) => {
|
|
56
|
+
queryClient.setQueryData<QueryData>(conversationQueryKey, prev =>
|
|
57
|
+
deleteRecordInPagesData({
|
|
58
|
+
data: prev,
|
|
59
|
+
record: e.data.data,
|
|
60
|
+
})
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
useJoltEvent(joltChannel, 'conversation.updated', handleConversationUpdateOrCreate)
|
|
65
|
+
useJoltEvent(joltChannel, 'conversation.created', handleConversationUpdateOrCreate)
|
|
66
|
+
useJoltEvent(joltChannel, 'conversation.destroyed', handleConversationDestroy)
|
|
67
|
+
}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { MessageResource } from '../types'
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
getRequestQueryKey,
|
|
4
|
+
SuspensePaginatorOptions,
|
|
5
|
+
useSuspensePaginator,
|
|
6
|
+
} from './use_suspense_api'
|
|
3
7
|
|
|
4
8
|
export const useConversationMessages = (
|
|
5
9
|
{ conversation_id }: { conversation_id: string },
|
|
@@ -39,5 +43,5 @@ export const getMessagesRequestArgs = ({ conversation_id }: { conversation_id: s
|
|
|
39
43
|
|
|
40
44
|
export const getMessagesQueryKey = ({ conversation_id }: { conversation_id: string }) => {
|
|
41
45
|
const requestArgs = getMessagesRequestArgs({ conversation_id })
|
|
42
|
-
return
|
|
46
|
+
return getRequestQueryKey(requestArgs)
|
|
43
47
|
}
|