@planningcenter/chat-react-native 3.38.0-rc.1 → 3.38.0-rc.10

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 (106) hide show
  1. package/build/components/conversation/message_list.d.ts +10 -0
  2. package/build/components/conversation/message_list.d.ts.map +1 -0
  3. package/build/components/conversation/message_list.js +13 -0
  4. package/build/components/conversation/message_list.js.map +1 -0
  5. package/build/components/display/conversation_avatar.d.ts +2 -1
  6. package/build/components/display/conversation_avatar.d.ts.map +1 -1
  7. package/build/components/display/conversation_avatar.js +6 -5
  8. package/build/components/display/conversation_avatar.js.map +1 -1
  9. package/build/components/display/emoji_avatar.d.ts +3 -1
  10. package/build/components/display/emoji_avatar.d.ts.map +1 -1
  11. package/build/components/display/emoji_avatar.js +2 -2
  12. package/build/components/display/emoji_avatar.js.map +1 -1
  13. package/build/components/display/icon_avatar.d.ts +3 -1
  14. package/build/components/display/icon_avatar.d.ts.map +1 -1
  15. package/build/components/display/icon_avatar.js +2 -2
  16. package/build/components/display/icon_avatar.js.map +1 -1
  17. package/build/hooks/groups/use_group_chat_conversation_payload.d.ts.map +1 -1
  18. package/build/hooks/groups/use_group_chat_conversation_payload.js +1 -0
  19. package/build/hooks/groups/use_group_chat_conversation_payload.js.map +1 -1
  20. package/build/hooks/index.d.ts +1 -0
  21. package/build/hooks/index.d.ts.map +1 -1
  22. package/build/hooks/index.js +1 -0
  23. package/build/hooks/index.js.map +1 -1
  24. package/build/hooks/use_preview_avatar_diameter.d.ts +2 -0
  25. package/build/hooks/use_preview_avatar_diameter.d.ts.map +1 -0
  26. package/build/hooks/use_preview_avatar_diameter.js +11 -0
  27. package/build/hooks/use_preview_avatar_diameter.js.map +1 -0
  28. package/build/jest.js +1 -1
  29. package/build/jest.js.map +1 -1
  30. package/build/screens/avatar_picker/avatar_picker_screen.d.ts.map +1 -1
  31. package/build/screens/avatar_picker/avatar_picker_screen.js +11 -9
  32. package/build/screens/avatar_picker/avatar_picker_screen.js.map +1 -1
  33. package/build/screens/avatar_picker/avatar_preview.d.ts.map +1 -1
  34. package/build/screens/avatar_picker/avatar_preview.js +13 -5
  35. package/build/screens/avatar_picker/avatar_preview.js.map +1 -1
  36. package/build/screens/avatar_picker/emoji_tab.d.ts.map +1 -1
  37. package/build/screens/avatar_picker/emoji_tab.js +3 -7
  38. package/build/screens/avatar_picker/emoji_tab.js.map +1 -1
  39. package/build/screens/avatar_picker/upload_tab.d.ts.map +1 -1
  40. package/build/screens/avatar_picker/upload_tab.js +2 -1
  41. package/build/screens/avatar_picker/upload_tab.js.map +1 -1
  42. package/build/screens/conversation_details_screen.d.ts.map +1 -1
  43. package/build/screens/conversation_details_screen.js +5 -2
  44. package/build/screens/conversation_details_screen.js.map +1 -1
  45. package/build/screens/conversation_filter_recipients/components/header_row.d.ts.map +1 -1
  46. package/build/screens/conversation_filter_recipients/components/header_row.js +3 -2
  47. package/build/screens/conversation_filter_recipients/components/header_row.js.map +1 -1
  48. package/build/screens/conversation_filter_recipients/hooks/use_flattened_array_of_service_types_with_teams.d.ts.map +1 -1
  49. package/build/screens/conversation_filter_recipients/hooks/use_flattened_array_of_service_types_with_teams.js +47 -18
  50. package/build/screens/conversation_filter_recipients/hooks/use_flattened_array_of_service_types_with_teams.js.map +1 -1
  51. package/build/screens/conversation_filter_recipients/hooks/use_service_types_with_teams.d.ts +2 -1
  52. package/build/screens/conversation_filter_recipients/hooks/use_service_types_with_teams.d.ts.map +1 -1
  53. package/build/screens/conversation_filter_recipients/hooks/use_service_types_with_teams.js +23 -26
  54. package/build/screens/conversation_filter_recipients/hooks/use_service_types_with_teams.js.map +1 -1
  55. package/build/screens/conversation_filter_recipients/types.d.ts +1 -1
  56. package/build/screens/conversation_filter_recipients/types.d.ts.map +1 -1
  57. package/build/screens/conversation_filter_recipients/types.js.map +1 -1
  58. package/build/screens/conversation_screen.d.ts.map +1 -1
  59. package/build/screens/conversation_screen.js +3 -7
  60. package/build/screens/conversation_screen.js.map +1 -1
  61. package/build/screens/conversation_select_recipients/components/recipient_link_row.d.ts +1 -1
  62. package/build/screens/conversation_select_recipients/components/recipient_link_row.d.ts.map +1 -1
  63. package/build/screens/conversation_select_recipients/components/recipient_link_row.js +3 -3
  64. package/build/screens/conversation_select_recipients/components/recipient_link_row.js.map +1 -1
  65. package/build/screens/conversation_select_recipients/components/team_recipient_row.d.ts.map +1 -1
  66. package/build/screens/conversation_select_recipients/components/team_recipient_row.js +1 -1
  67. package/build/screens/conversation_select_recipients/components/team_recipient_row.js.map +1 -1
  68. package/build/screens/team_conversation_screen.d.ts.map +1 -1
  69. package/build/screens/team_conversation_screen.js +24 -1
  70. package/build/screens/team_conversation_screen.js.map +1 -1
  71. package/build/utils/client/client.d.ts +1 -1
  72. package/build/utils/client/client.d.ts.map +1 -1
  73. package/build/utils/client/client.js +7 -6
  74. package/build/utils/client/client.js.map +1 -1
  75. package/build/utils/client/instrumented_fetch.js +3 -5
  76. package/build/utils/client/instrumented_fetch.js.map +1 -1
  77. package/package.json +4 -4
  78. package/src/__tests__/hooks/use_group_chat_conversation_payload.test.tsx +50 -0
  79. package/src/__tests__/jest.ts +1 -1
  80. package/src/__tests__/utils/client.ts +32 -0
  81. package/src/components/conversation/__tests__/message_list.test.tsx +14 -0
  82. package/src/components/conversation/message_list.tsx +42 -0
  83. package/src/components/display/conversation_avatar.tsx +7 -5
  84. package/src/components/display/emoji_avatar.tsx +10 -2
  85. package/src/components/display/icon_avatar.tsx +10 -2
  86. package/src/hooks/groups/use_group_chat_conversation_payload.ts +1 -0
  87. package/src/hooks/index.ts +1 -0
  88. package/src/hooks/use_preview_avatar_diameter.ts +12 -0
  89. package/src/jest.ts +1 -1
  90. package/src/screens/avatar_picker/avatar_picker_screen.tsx +25 -9
  91. package/src/screens/avatar_picker/avatar_preview.tsx +14 -5
  92. package/src/screens/avatar_picker/emoji_tab.tsx +3 -6
  93. package/src/screens/avatar_picker/upload_tab.tsx +2 -0
  94. package/src/screens/conversation_details_screen.tsx +10 -1
  95. package/src/screens/conversation_filter_recipients/components/header_row.tsx +3 -2
  96. package/src/screens/conversation_filter_recipients/hooks/__tests__/use_service_types_with_teams.test.ts +108 -0
  97. package/src/screens/conversation_filter_recipients/hooks/use_flattened_array_of_service_types_with_teams.tsx +46 -19
  98. package/src/screens/conversation_filter_recipients/hooks/use_service_types_with_teams.ts +31 -29
  99. package/src/screens/conversation_filter_recipients/types.tsx +1 -1
  100. package/src/screens/conversation_screen.tsx +5 -14
  101. package/src/screens/conversation_select_recipients/components/recipient_link_row.tsx +6 -4
  102. package/src/screens/conversation_select_recipients/components/team_recipient_row.tsx +2 -1
  103. package/src/screens/team_conversation_screen.tsx +33 -1
  104. package/src/utils/client/__tests__/instrumented_fetch.test.ts +9 -5
  105. package/src/utils/client/client.ts +9 -7
  106. package/src/utils/client/instrumented_fetch.ts +3 -6
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","sourceRoot":"","sources":["../../../src/utils/client/client.ts"],"names":[],"mappings":"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","sourcesContent":["import { ApiCollection, ApiResource, FailedResponse } from '../../types'\nimport {\n concatRecords,\n ensureNoQueryParamsInDev,\n makeRequest,\n MakeRequestArgs,\n throwErrorIfFieldsMissing,\n throwErrorIfQueryParams,\n} from './request_helpers'\nimport { DeleteRequest, GetRequest, PatchRequest, PostRequest, WalkRequest } from './types'\n\nexport type OnUnauthorizedResponse = (_response: FailedResponse) => void\n\ntype ClientArgs = {\n version: string\n defaultHeaders?: Record<string, string>\n onUnauthorizedResponse?: OnUnauthorizedResponse\n root?: string\n}\n\nexport class Client {\n version: string = ''\n defaultHeaders: Record<string, string> = {}\n onUnauthorizedResponse?: OnUnauthorizedResponse\n root?: string\n\n constructor({ version, defaultHeaders = {}, onUnauthorizedResponse, root }: ClientArgs) {\n this.version = version\n this.defaultHeaders = defaultHeaders\n this.onUnauthorizedResponse = onUnauthorizedResponse\n this.root = root\n }\n\n async get<T extends ApiCollection | ApiResource>(args: GetRequest): Promise<T> {\n const { walk, ...data } = args.data\n const isWalking = Boolean(walk)\n const headers = { ...this.headers, ...args.headers }\n const url = this.appUrl(args.url)\n\n await throwErrorIfQueryParams(url)\n\n const requestArgs: MakeRequestArgs = { data, url, action: 'GET', headers }\n\n const walkRequest = ({\n url: requestUrl,\n data: d = { fields: {} },\n acc = { data: [], included: [], meta: {}, links: {} },\n ...options\n }: WalkRequest): Promise<T> => {\n return makeRequest({\n action: 'GET',\n data: d,\n url: requestUrl,\n ...options,\n headers,\n }).then(({ links, ...rest }) => {\n const records = Array.isArray(rest.data) ? concatRecords(acc, rest) : rest\n\n // `next` will have our params in the link so we do not want to pass them back in\n if (links?.next) {\n return walkRequest({ ...options, data: d, url: links.next, acc: records })\n } else {\n return records\n }\n })\n }\n\n const handler = isWalking\n ? (a: MakeRequestArgs | WalkRequest) => walkRequest(a as WalkRequest)\n : (a: MakeRequestArgs | WalkRequest) => makeRequest(a as MakeRequestArgs)\n\n return throwErrorIfFieldsMissing(handler, requestArgs)\n .then(response => response as T)\n .catch(this.handleNotOk)\n }\n\n async patch<T extends ApiCollection | ApiResource>(args: PatchRequest): Promise<T> {\n const headers = { ...this.headers, ...args.headers }\n const url = this.appUrl(args.url)\n\n const requestArgs: MakeRequestArgs = { data: args.data, url, action: 'PATCH', headers }\n\n return ensureNoQueryParamsInDev(makeRequest, requestArgs)\n .then(response => response as T)\n .catch(this.handleNotOk)\n }\n\n async post<T extends ApiCollection | ApiResource>(args: PostRequest): Promise<T> {\n const headers = { ...this.headers, ...args.headers }\n const url = this.appUrl(args.url)\n\n const requestArgs: MakeRequestArgs = { ...args, data: args.data, url, action: 'POST', headers }\n\n return ensureNoQueryParamsInDev(makeRequest, requestArgs)\n .then(response => response as T)\n .catch(this.handleNotOk)\n }\n\n async delete(args: DeleteRequest) {\n const headers = { ...this.headers, ...args.headers }\n const url = this.appUrl(args.url)\n\n const requestArgs: MakeRequestArgs = { url, action: 'DELETE', headers }\n\n return makeRequest(requestArgs).catch(this.handleNotOk)\n }\n\n handleNotOk = async (response: Response) => {\n if (response.status === 401) {\n const errorData = await this.parseErrorResponse(response)\n this.onUnauthorizedResponse?.({\n ...response,\n errors: errorData.errors || [],\n } as FailedResponse)\n }\n\n return Promise.reject(response)\n }\n\n parseErrorResponse = async (response: Response): Promise<Partial<FailedResponse>> => {\n try {\n return (await response.clone().json()) as FailedResponse\n } catch {\n return {}\n }\n }\n\n appUrl(url: string) {\n return `${this.root}${url}`\n }\n\n get headers() {\n return {\n Accept: 'application/vnd.api+json',\n 'Content-Type': 'application/json',\n 'X-PCO-API-Version': this.version,\n ...this.defaultHeaders,\n }\n }\n}\n\nexport default Client\n"]}
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../../src/utils/client/client.ts"],"names":[],"mappings":"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","sourcesContent":["import { ApiCollection, ApiResource, FailedResponse } from '../../types'\nimport {\n concatRecords,\n ensureNoQueryParamsInDev,\n makeRequest,\n MakeRequestArgs,\n throwErrorIfFieldsMissing,\n throwErrorIfQueryParams,\n} from './request_helpers'\nimport { DeleteRequest, GetRequest, PatchRequest, PostRequest, WalkRequest } from './types'\n\nexport type OnUnauthorizedResponse = (_response: FailedResponse) => void\n\ntype ClientArgs = {\n version: string\n defaultHeaders?: Record<string, string>\n onUnauthorizedResponse?: OnUnauthorizedResponse\n root?: string\n}\n\nexport class Client {\n version: string = ''\n defaultHeaders: Record<string, string> = {}\n onUnauthorizedResponse?: OnUnauthorizedResponse\n root?: string\n\n constructor({ version, defaultHeaders = {}, onUnauthorizedResponse, root }: ClientArgs) {\n this.version = version\n this.defaultHeaders = defaultHeaders\n this.onUnauthorizedResponse = onUnauthorizedResponse\n this.root = root\n }\n\n async get<T extends ApiCollection | ApiResource>(args: GetRequest): Promise<T> {\n const { walk, ...data } = args.data\n const isWalking = Boolean(walk)\n const headers = { ...this.headers, ...args.headers }\n const url = this.appUrl(args.url)\n\n await throwErrorIfQueryParams(url)\n\n const requestArgs: MakeRequestArgs = { data, url, action: 'GET', headers }\n\n const walkRequest = ({\n url: requestUrl,\n data: d = { fields: {} },\n acc = { data: [], included: [], meta: {}, links: {} },\n ...options\n }: WalkRequest): Promise<T> => {\n return makeRequest({\n action: 'GET',\n data: d,\n url: requestUrl,\n ...options,\n headers,\n }).then(({ links, ...rest }) => {\n const records = Array.isArray(rest.data) ? concatRecords(acc, rest) : rest\n\n // `next` will have our params in the link so we do not want to pass them back in\n if (links?.next) {\n return walkRequest({ ...options, data: d, url: links.next, acc: records })\n } else {\n return records\n }\n })\n }\n\n const handler = isWalking\n ? (a: MakeRequestArgs | WalkRequest) => walkRequest(a as WalkRequest)\n : (a: MakeRequestArgs | WalkRequest) => makeRequest(a as MakeRequestArgs)\n\n return throwErrorIfFieldsMissing(handler, requestArgs)\n .then(response => response as T)\n .catch(this.handleNotOk)\n }\n\n async patch<T extends ApiCollection | ApiResource>(args: PatchRequest): Promise<T> {\n const headers = { ...this.headers, ...args.headers }\n const url = this.appUrl(args.url)\n\n const requestArgs: MakeRequestArgs = { data: args.data, url, action: 'PATCH', headers }\n\n return ensureNoQueryParamsInDev(makeRequest, requestArgs)\n .then(response => response as T)\n .catch(this.handleNotOk)\n }\n\n async post<T extends ApiCollection | ApiResource>(args: PostRequest): Promise<T> {\n const headers = { ...this.headers, ...args.headers }\n const url = this.appUrl(args.url)\n\n const requestArgs: MakeRequestArgs = { ...args, data: args.data, url, action: 'POST', headers }\n\n return ensureNoQueryParamsInDev(makeRequest, requestArgs)\n .then(response => response as T)\n .catch(this.handleNotOk)\n }\n\n async delete(args: DeleteRequest) {\n const headers = { ...this.headers, ...args.headers }\n const url = this.appUrl(args.url)\n\n const requestArgs: MakeRequestArgs = { url, action: 'DELETE', headers }\n\n return makeRequest(requestArgs).catch(this.handleNotOk)\n }\n\n handleNotOk = async (response: Response | Error) => {\n if (!(response instanceof Response)) return Promise.reject(response)\n\n const errorData = await this.parseErrorResponse(response)\n const notOkResponse = response.clone() as FailedResponse\n notOkResponse.errors = errorData.errors || []\n\n if (response.status === 401) {\n this.onUnauthorizedResponse?.(notOkResponse)\n }\n\n return Promise.reject(notOkResponse)\n }\n\n parseErrorResponse = async (response: Response): Promise<Partial<FailedResponse>> => {\n try {\n return (await response.clone().json()) as FailedResponse\n } catch {\n return {}\n }\n }\n\n appUrl(url: string) {\n return `${this.root}${url}`\n }\n\n get headers() {\n return {\n Accept: 'application/vnd.api+json',\n 'Content-Type': 'application/json',\n 'X-PCO-API-Version': this.version,\n ...this.defaultHeaders,\n }\n }\n}\n\nexport default Client\n"]}
@@ -13,7 +13,7 @@ export async function instrumentedFetch(url, init) {
13
13
  return response;
14
14
  }
15
15
  catch (networkError) {
16
- reportNetworkError(networkError, method, url);
16
+ warnNetworkError(networkError, method, url);
17
17
  throw networkError;
18
18
  }
19
19
  }
@@ -34,11 +34,9 @@ function reportHttpError(response, method, url) {
34
34
  },
35
35
  });
36
36
  }
37
- function reportNetworkError(networkError, method, url) {
37
+ function warnNetworkError(networkError, method, url) {
38
38
  const path = templatePath(url);
39
- const error = new Error(`Network failure ${method} ${path}: ${networkError.message}`);
40
- error.name = 'NetworkError';
41
- Log.reportError(error, {
39
+ console.warn(`Network failure ${method} ${path}: ${networkError.message}`, {
42
40
  scope: 'http',
43
41
  tags: {
44
42
  ...SHARED_TAGS,
@@ -1 +1 @@
1
- {"version":3,"file":"instrumented_fetch.js","sourceRoot":"","sources":["../../../src/utils/client/instrumented_fetch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,kCAAkC,CAAA;AAEtD,MAAM,WAAW,GAAG;IAClB,IAAI,EAAE,MAAM;IACZ,OAAO,EAAE,mBAAmB;CACpB,CAAA;AAEV,MAAM,gBAAgB,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAA;AAExC,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,GAAW,EAAE,IAAiB;IACpE,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,KAAK,CAAA;IACnC,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;QACvC,IAAI,CAAC,QAAQ,CAAC,EAAE;YAAE,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,CAAC,CAAA;QACxD,OAAO,QAAQ,CAAA;IACjB,CAAC;IAAC,OAAO,YAAY,EAAE,CAAC;QACtB,kBAAkB,CAAC,YAAqB,EAAE,MAAM,EAAE,GAAG,CAAC,CAAA;QACtD,MAAM,YAAY,CAAA;IACpB,CAAC;AACH,CAAC;AAED,SAAS,eAAe,CAAC,QAAkB,EAAE,MAAc,EAAE,GAAW;IACtE,MAAM,EAAE,MAAM,EAAE,GAAG,QAAQ,CAAA;IAC3B,IAAI,gBAAgB,CAAC,QAAQ,CAAC,MAAM,CAAC;QAAE,OAAM;IAE7C,MAAM,IAAI,GAAG,YAAY,CAAC,GAAG,CAAC,CAAA;IAC9B,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,QAAQ,MAAM,IAAI,MAAM,IAAI,IAAI,EAAE,CAAC,CAAA;IAC3D,KAAK,CAAC,IAAI,GAAG,YAAY,MAAM,EAAE,CAAA;IAEjC,GAAG,CAAC,WAAW,CAAC,KAAK,EAAE;QACrB,KAAK,EAAE,MAAM;QACb,IAAI,EAAE;YACJ,GAAG,WAAW;YACd,aAAa,EAAE,MAAM,CAAC,MAAM,CAAC;YAC7B,aAAa,EAAE,MAAM;YACrB,WAAW,EAAE,IAAI;SAClB;KACF,CAAC,CAAA;AACJ,CAAC;AAED,SAAS,kBAAkB,CAAC,YAAmB,EAAE,MAAc,EAAE,GAAW;IAC1E,MAAM,IAAI,GAAG,YAAY,CAAC,GAAG,CAAC,CAAA;IAC9B,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,mBAAmB,MAAM,IAAI,IAAI,KAAK,YAAY,CAAC,OAAO,EAAE,CAAC,CAAA;IACrF,KAAK,CAAC,IAAI,GAAG,cAAc,CAAA;IAE3B,GAAG,CAAC,WAAW,CAAC,KAAK,EAAE;QACrB,KAAK,EAAE,MAAM;QACb,IAAI,EAAE;YACJ,GAAG,WAAW;YACd,aAAa,EAAE,MAAM;YACrB,WAAW,EAAE,IAAI;YACjB,YAAY,EAAE,SAAS;SACxB;KACF,CAAC,CAAA;AACJ,CAAC;AAED,SAAS,YAAY,CAAC,GAAW;IAC/B,IAAI,IAAY,CAAA;IAChB,IAAI,CAAC;QACH,IAAI,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAA;IAC9B,CAAC;IAAC,MAAM,CAAC;QACP,IAAI,GAAG,GAAG,CAAA;IACZ,CAAC;IAED,OAAO,IAAI;SACR,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;SACzD,IAAI,CAAC,GAAG,CAAC,CAAA;AACd,CAAC","sourcesContent":["import { Log } from '../native_adapters/configuration'\n\nconst SHARED_TAGS = {\n team: 'chat',\n package: 'chat-react-native',\n} as const\n\nconst SKIPPED_STATUSES = [401, 403, 404]\n\nexport async function instrumentedFetch(url: string, init: RequestInit): Promise<Response> {\n const method = init.method ?? 'GET'\n try {\n const response = await fetch(url, init)\n if (!response.ok) reportHttpError(response, method, url)\n return response\n } catch (networkError) {\n reportNetworkError(networkError as Error, method, url)\n throw networkError\n }\n}\n\nfunction reportHttpError(response: Response, method: string, url: string) {\n const { status } = response\n if (SKIPPED_STATUSES.includes(status)) return\n\n const path = templatePath(url)\n const error = new Error(`HTTP ${status} ${method} ${path}`)\n error.name = `HTTPError${status}`\n\n Log.reportError(error, {\n scope: 'http',\n tags: {\n ...SHARED_TAGS,\n 'http.status': String(status),\n 'http.method': method,\n 'http.path': path,\n },\n })\n}\n\nfunction reportNetworkError(networkError: Error, method: string, url: string) {\n const path = templatePath(url)\n const error = new Error(`Network failure ${method} ${path}: ${networkError.message}`)\n error.name = 'NetworkError'\n\n Log.reportError(error, {\n scope: 'http',\n tags: {\n ...SHARED_TAGS,\n 'http.method': method,\n 'http.path': path,\n 'http.error': 'network',\n },\n })\n}\n\nfunction templatePath(url: string): string {\n let path: string\n try {\n path = new URL(url).pathname\n } catch {\n path = url\n }\n\n return path\n .split('/')\n .map(segment => (/^\\d+$/.test(segment) ? ':id' : segment))\n .join('/')\n}\n"]}
1
+ {"version":3,"file":"instrumented_fetch.js","sourceRoot":"","sources":["../../../src/utils/client/instrumented_fetch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,kCAAkC,CAAA;AAEtD,MAAM,WAAW,GAAG;IAClB,IAAI,EAAE,MAAM;IACZ,OAAO,EAAE,mBAAmB;CACpB,CAAA;AAEV,MAAM,gBAAgB,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAA;AAExC,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,GAAW,EAAE,IAAiB;IACpE,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,KAAK,CAAA;IACnC,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;QACvC,IAAI,CAAC,QAAQ,CAAC,EAAE;YAAE,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,CAAC,CAAA;QACxD,OAAO,QAAQ,CAAA;IACjB,CAAC;IAAC,OAAO,YAAY,EAAE,CAAC;QACtB,gBAAgB,CAAC,YAAqB,EAAE,MAAM,EAAE,GAAG,CAAC,CAAA;QACpD,MAAM,YAAY,CAAA;IACpB,CAAC;AACH,CAAC;AAED,SAAS,eAAe,CAAC,QAAkB,EAAE,MAAc,EAAE,GAAW;IACtE,MAAM,EAAE,MAAM,EAAE,GAAG,QAAQ,CAAA;IAC3B,IAAI,gBAAgB,CAAC,QAAQ,CAAC,MAAM,CAAC;QAAE,OAAM;IAE7C,MAAM,IAAI,GAAG,YAAY,CAAC,GAAG,CAAC,CAAA;IAC9B,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,QAAQ,MAAM,IAAI,MAAM,IAAI,IAAI,EAAE,CAAC,CAAA;IAC3D,KAAK,CAAC,IAAI,GAAG,YAAY,MAAM,EAAE,CAAA;IAEjC,GAAG,CAAC,WAAW,CAAC,KAAK,EAAE;QACrB,KAAK,EAAE,MAAM;QACb,IAAI,EAAE;YACJ,GAAG,WAAW;YACd,aAAa,EAAE,MAAM,CAAC,MAAM,CAAC;YAC7B,aAAa,EAAE,MAAM;YACrB,WAAW,EAAE,IAAI;SAClB;KACF,CAAC,CAAA;AACJ,CAAC;AAED,SAAS,gBAAgB,CAAC,YAAmB,EAAE,MAAc,EAAE,GAAW;IACxE,MAAM,IAAI,GAAG,YAAY,CAAC,GAAG,CAAC,CAAA;IAC9B,OAAO,CAAC,IAAI,CAAC,mBAAmB,MAAM,IAAI,IAAI,KAAK,YAAY,CAAC,OAAO,EAAE,EAAE;QACzE,KAAK,EAAE,MAAM;QACb,IAAI,EAAE;YACJ,GAAG,WAAW;YACd,aAAa,EAAE,MAAM;YACrB,WAAW,EAAE,IAAI;YACjB,YAAY,EAAE,SAAS;SACxB;KACF,CAAC,CAAA;AACJ,CAAC;AAED,SAAS,YAAY,CAAC,GAAW;IAC/B,IAAI,IAAY,CAAA;IAChB,IAAI,CAAC;QACH,IAAI,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAA;IAC9B,CAAC;IAAC,MAAM,CAAC;QACP,IAAI,GAAG,GAAG,CAAA;IACZ,CAAC;IAED,OAAO,IAAI;SACR,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;SACzD,IAAI,CAAC,GAAG,CAAC,CAAA;AACd,CAAC","sourcesContent":["import { Log } from '../native_adapters/configuration'\n\nconst SHARED_TAGS = {\n team: 'chat',\n package: 'chat-react-native',\n} as const\n\nconst SKIPPED_STATUSES = [401, 403, 404]\n\nexport async function instrumentedFetch(url: string, init: RequestInit): Promise<Response> {\n const method = init.method ?? 'GET'\n try {\n const response = await fetch(url, init)\n if (!response.ok) reportHttpError(response, method, url)\n return response\n } catch (networkError) {\n warnNetworkError(networkError as Error, method, url)\n throw networkError\n }\n}\n\nfunction reportHttpError(response: Response, method: string, url: string) {\n const { status } = response\n if (SKIPPED_STATUSES.includes(status)) return\n\n const path = templatePath(url)\n const error = new Error(`HTTP ${status} ${method} ${path}`)\n error.name = `HTTPError${status}`\n\n Log.reportError(error, {\n scope: 'http',\n tags: {\n ...SHARED_TAGS,\n 'http.status': String(status),\n 'http.method': method,\n 'http.path': path,\n },\n })\n}\n\nfunction warnNetworkError(networkError: Error, method: string, url: string) {\n const path = templatePath(url)\n console.warn(`Network failure ${method} ${path}: ${networkError.message}`, {\n scope: 'http',\n tags: {\n ...SHARED_TAGS,\n 'http.method': method,\n 'http.path': path,\n 'http.error': 'network',\n },\n })\n}\n\nfunction templatePath(url: string): string {\n let path: string\n try {\n path = new URL(url).pathname\n } catch {\n path = url\n }\n\n return path\n .split('/')\n .map(segment => (/^\\d+$/.test(segment) ? ':id' : segment))\n .join('/')\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@planningcenter/chat-react-native",
3
- "version": "3.38.0-rc.1",
3
+ "version": "3.38.0-rc.10",
4
4
  "description": "",
5
5
  "main": "build/index.js",
6
6
  "react-native": "./src/index.tsx",
@@ -26,9 +26,9 @@
26
26
  "dependencies": {
27
27
  "@fortawesome/fontawesome-svg-core": "^7.2.0",
28
28
  "@fortawesome/react-native-fontawesome": "^0.3.2",
29
+ "@planningcenter/emoji-keyboard": "3.38.0-rc.10",
29
30
  "lodash-inflection": "^1.5.0",
30
- "react-compiler-runtime": "^1.0.0",
31
- "rn-emoji-keyboard": "1.7.0"
31
+ "react-compiler-runtime": "^1.0.0"
32
32
  },
33
33
  "peerDependencies": {
34
34
  "@planningcenter/datetime-fmt": ">=2.0.0",
@@ -72,5 +72,5 @@
72
72
  "react-native-url-polyfill": "^2.0.0",
73
73
  "typescript": "~5.9.2"
74
74
  },
75
- "gitHead": "addadedc983bf11d717e5528d8d4d143a7acd4c9"
75
+ "gitHead": "2d9a404aadc8137412ca18ad9d5c7949097009ac"
76
76
  }
@@ -0,0 +1,50 @@
1
+ import { QueryClientProvider } from '@tanstack/react-query'
2
+ import { renderHook, waitFor } from '@testing-library/react-native'
3
+ import React from 'react'
4
+ import { buildTestQueryClient } from '../../__utils__/query_client'
5
+ import { useGroupChatConversationPayload } from '../../hooks/groups/use_group_chat_conversation_payload'
6
+ import * as useApiClientModule from '../../hooks/use_api_client'
7
+
8
+ const mockGroupsPost = (impl: jest.Mock) => {
9
+ jest.spyOn(useApiClientModule, 'useApiClient').mockReturnValue({
10
+ groups: { post: impl },
11
+ } as unknown as ReturnType<typeof useApiClientModule.useApiClient>)
12
+ return impl
13
+ }
14
+
15
+ const resolveWith = (value: string) =>
16
+ jest.fn().mockResolvedValue({
17
+ data: { type: 'GroupChatConversationPayload', id: '1', value },
18
+ links: {},
19
+ meta: {},
20
+ })
21
+
22
+ describe('useGroupChatConversationPayload', () => {
23
+ afterEach(() => {
24
+ jest.restoreAllMocks()
25
+ })
26
+
27
+ it('refetches on mount even when a fresh payload is already cached', async () => {
28
+ // Prime the cache as if a payload was fetched moments ago. The hook's 25-min staleTime
29
+ // means the default refetchOnMount would treat this as fresh and skip the request, so a
30
+ // group composition change on web would stay invisible until the cache is dropped (app
31
+ // restart). refetchOnMount: 'always' must override that and refetch on every mount.
32
+ const queryClient = buildTestQueryClient()
33
+ queryClient.setQueryData(['groups', '/me/groups/1/chat_conversation_payload'], {
34
+ data: { type: 'GroupChatConversationPayload', id: '1', value: 'stale' },
35
+ links: {},
36
+ meta: {},
37
+ })
38
+ const post = mockGroupsPost(resolveWith('fresh'))
39
+ const wrapper = ({ children }: { children: React.ReactNode }) => (
40
+ <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
41
+ )
42
+
43
+ const { result } = renderHook(() => useGroupChatConversationPayload({ groupId: 1 }), {
44
+ wrapper,
45
+ })
46
+
47
+ await waitFor(() => expect(post).toHaveBeenCalledTimes(1))
48
+ await waitFor(() => expect(result.current.payload).toBe('fresh'))
49
+ })
50
+ })
@@ -4,8 +4,8 @@ describe('jestTransformPackages', () => {
4
4
  it('exports an array of package patterns', () => {
5
5
  expect(jestTransformPackages).toEqual([
6
6
  '@planningcenter/chat-react-native',
7
+ '@planningcenter/emoji-keyboard',
7
8
  '@fortawesome',
8
- 'rn-emoji-keyboard',
9
9
  ])
10
10
  })
11
11
  })
@@ -369,4 +369,36 @@ describe('error handling', () => {
369
369
  ).rejects.toHaveProperty('status', 401)
370
370
  }
371
371
  })
372
+
373
+ describe('non-401 errors', () => {
374
+ it('attaches the parsed body so the message survives to the UI', async () => {
375
+ const url = '/records'
376
+
377
+ MockServer.post(
378
+ url,
379
+ {
380
+ errors: [
381
+ {
382
+ status: '422',
383
+ title: 'Unprocessable Entity',
384
+ detail: 'Conversations including a minor must have at least two confirmed adults.',
385
+ },
386
+ ],
387
+ },
388
+ 422,
389
+ { once: true }
390
+ )
391
+
392
+ const rejection = (await client
393
+ .post({ url, data: { data: { type: 'Record', attributes: {} } } })
394
+ .catch(e => e)) as FailedResponse
395
+
396
+ expect(rejection).toHaveProperty('status', 422)
397
+ expect(rejection.errors?.[0]?.detail).toEqual(
398
+ 'Conversations including a minor must have at least two confirmed adults.'
399
+ )
400
+ // A 422 must not be mistaken for an auth failure
401
+ expect(onUnauthorizedResponse).not.toHaveBeenCalled()
402
+ })
403
+ })
372
404
  })
@@ -0,0 +1,14 @@
1
+ import { render } from '@testing-library/react-native'
2
+ import { createRef } from 'react'
3
+ import { FlatList } from 'react-native-gesture-handler'
4
+ import { MessageList } from '../message_list'
5
+
6
+ describe('MessageList', () => {
7
+ it('renders with the gesture-handler FlatList so overscroll cannot drag an enclosing sheet', () => {
8
+ const listRef = createRef<FlatList>()
9
+
10
+ const screen = render(<MessageList listRef={listRef} data={[]} renderItem={() => null} />)
11
+
12
+ expect(screen.UNSAFE_getByType(FlatList)).toBeTruthy()
13
+ })
14
+ })
@@ -0,0 +1,42 @@
1
+ import { RefObject } from 'react'
2
+ import { StyleSheet, type FlatListProps } from 'react-native'
3
+ import { FlatList } from 'react-native-gesture-handler'
4
+ import type { EnrichedMessage } from '../../utils/group_messages'
5
+
6
+ const extractItemKey = (item: EnrichedMessage) => String(item.id)
7
+ const maintainVisibleContentPosition = { minIndexForVisible: 0 }
8
+
9
+ type MessageListProps = Pick<
10
+ FlatListProps<EnrichedMessage>,
11
+ | 'data'
12
+ | 'renderItem'
13
+ | 'onScroll'
14
+ | 'onScrollBeginDrag'
15
+ | 'viewabilityConfigCallbackPairs'
16
+ | 'onContentSizeChange'
17
+ | 'onScrollToIndexFailed'
18
+ | 'onEndReached'
19
+ | 'ListHeaderComponent'
20
+ > & {
21
+ listRef: RefObject<FlatList | null>
22
+ }
23
+
24
+ export function MessageList({ listRef, ...props }: MessageListProps) {
25
+ return (
26
+ <FlatList
27
+ inverted
28
+ ref={listRef}
29
+ contentContainerStyle={styles.listContainer}
30
+ maintainVisibleContentPosition={maintainVisibleContentPosition}
31
+ keyExtractor={extractItemKey}
32
+ scrollEventThrottle={64}
33
+ {...props}
34
+ />
35
+ )
36
+ }
37
+
38
+ const styles = StyleSheet.create({
39
+ listContainer: {
40
+ paddingVertical: 12,
41
+ },
42
+ })
@@ -52,6 +52,7 @@ interface ConversationAvatarProps {
52
52
  showFallback?: boolean
53
53
  fallbackIconName?: IconString
54
54
  style?: ViewStyle
55
+ maxFontSizeMultiplier?: AvatarRootProps['maxFontSizeMultiplier']
55
56
  }
56
57
 
57
58
  export function ConversationAvatar({
@@ -60,30 +61,31 @@ export function ConversationAvatar({
60
61
  showFallback = false,
61
62
  fallbackIconName = 'general.person',
62
63
  style,
64
+ maxFontSizeMultiplier,
63
65
  }: ConversationAvatarProps) {
64
66
  const avatar = resolveAvatar(conversation)
67
+ const sizeProps = { size, maxFontSizeMultiplier, style }
65
68
 
66
69
  switch (avatar.kind) {
67
70
  case 'image':
68
71
  return (
69
- <AvatarPrimitive.Root size={size} style={style}>
72
+ <AvatarPrimitive.Root {...sizeProps}>
70
73
  <AvatarPrimitive.Mask>
71
74
  <AvatarPrimitive.Image sourceUri={avatar.url} />
72
75
  </AvatarPrimitive.Mask>
73
76
  </AvatarPrimitive.Root>
74
77
  )
75
78
  case 'icon':
76
- return <IconAvatar iconKey={avatar.iconKey} color={avatar.color} size={size} />
79
+ return <IconAvatar iconKey={avatar.iconKey} color={avatar.color} {...sizeProps} />
77
80
  case 'emoji':
78
- return <EmojiAvatar emoji={avatar.emoji} color={avatar.color} size={size} />
81
+ return <EmojiAvatar emoji={avatar.emoji} color={avatar.color} {...sizeProps} />
79
82
  case 'group':
80
83
  return (
81
84
  <AvatarGroup
82
85
  sourceUris={avatar.sources || []}
83
- size={size}
84
86
  showFallback={showFallback || !avatar.sources || avatar.sources.length === 0}
85
87
  fallbackIconName={fallbackIconName}
86
- style={style}
88
+ {...sizeProps}
87
89
  />
88
90
  )
89
91
  }
@@ -20,13 +20,21 @@ interface EmojiAvatarProps {
20
20
  emoji: string
21
21
  color?: string | null
22
22
  size?: AvatarRootProps['size']
23
+ maxFontSizeMultiplier?: AvatarRootProps['maxFontSizeMultiplier']
24
+ style?: AvatarRootProps['style']
23
25
  }
24
26
 
25
- export function EmojiAvatar({ emoji, color, size = 'lg' }: EmojiAvatarProps) {
27
+ export function EmojiAvatar({
28
+ emoji,
29
+ color,
30
+ size = 'lg',
31
+ maxFontSizeMultiplier,
32
+ style,
33
+ }: EmojiAvatarProps) {
26
34
  const gradientProps = getAvatarGradientProps(color)
27
35
 
28
36
  return (
29
- <AvatarPrimitive.Root size={size}>
37
+ <AvatarPrimitive.Root size={size} maxFontSizeMultiplier={maxFontSizeMultiplier} style={style}>
30
38
  <AvatarPrimitive.Mask>
31
39
  <LinearGradient {...gradientProps} style={styles.gradientFill}>
32
40
  <View style={styles.contentContainer}>
@@ -26,13 +26,21 @@ interface IconAvatarProps {
26
26
  iconKey: string
27
27
  color?: string | null
28
28
  size?: AvatarRootProps['size']
29
+ maxFontSizeMultiplier?: AvatarRootProps['maxFontSizeMultiplier']
30
+ style?: AvatarRootProps['style']
29
31
  }
30
32
 
31
33
  function findIcon(iconKey: string): IconDefinition | undefined {
32
34
  return findIconDefinition({ prefix: 'fas', iconName: iconKey as IconName })
33
35
  }
34
36
 
35
- export function IconAvatar({ iconKey, color, size = 'lg' }: IconAvatarProps) {
37
+ export function IconAvatar({
38
+ iconKey,
39
+ color,
40
+ size = 'lg',
41
+ maxFontSizeMultiplier,
42
+ style,
43
+ }: IconAvatarProps) {
36
44
  const gradientProps = getAvatarGradientProps(color)
37
45
  const iconDef = findIcon(iconKey)
38
46
 
@@ -41,7 +49,7 @@ export function IconAvatar({ iconKey, color, size = 'lg' }: IconAvatarProps) {
41
49
  }
42
50
 
43
51
  return (
44
- <AvatarPrimitive.Root size={size}>
52
+ <AvatarPrimitive.Root size={size} maxFontSizeMultiplier={maxFontSizeMultiplier} style={style}>
45
53
  <AvatarPrimitive.Mask>
46
54
  <LinearGradient {...gradientProps} style={styles.gradientFill}>
47
55
  <View style={styles.contentContainer}>
@@ -26,6 +26,7 @@ export function useGroupChatConversationPayload({ groupId, enabled = true }: Pro
26
26
  },
27
27
  }),
28
28
  staleTime: STALE_TIME_MS,
29
+ refetchOnMount: 'always',
29
30
  enabled,
30
31
  })
31
32
 
@@ -16,6 +16,7 @@ export * from './use_message_reaction_toggle'
16
16
  export * from './use_new_conversation_entry'
17
17
  export * from './use_organization'
18
18
  export * from './use_people_person'
19
+ export * from './use_preview_avatar_diameter'
19
20
  export * from './use_product_analytics'
20
21
  export * from './use_qualified_by_age'
21
22
  export * from './use_scalable_number_of_lines'
@@ -0,0 +1,12 @@
1
+ import { useWindowDimensions } from 'react-native'
2
+ import { MAX_FONT_SIZE_MULTIPLIER_LANDMARK } from '../utils'
3
+ import { useFontScale } from './use_font_scale'
4
+
5
+ const VIEWPORT_FRACTION = 0.22
6
+ const BASE_DIAMETER = 80
7
+
8
+ export function usePreviewAvatarDiameter() {
9
+ const { width } = useWindowDimensions()
10
+ const fontScale = useFontScale({ maxFontSizeMultiplier: MAX_FONT_SIZE_MULTIPLIER_LANDMARK })
11
+ return Math.min(BASE_DIAMETER * fontScale, width * VIEWPORT_FRACTION)
12
+ }
package/src/jest.ts CHANGED
@@ -16,6 +16,6 @@
16
16
  */
17
17
  export const jestTransformPackages = [
18
18
  '@planningcenter/chat-react-native',
19
+ '@planningcenter/emoji-keyboard',
19
20
  '@fortawesome',
20
- 'rn-emoji-keyboard',
21
21
  ]
@@ -13,6 +13,8 @@ import {
13
13
  type AvatarUpdatePayload,
14
14
  } from '../../hooks/use_conversation_avatar_update'
15
15
  import { useFontScale } from '../../hooks/use_font_scale'
16
+ import { usePreviewAvatarDiameter } from '../../hooks/use_preview_avatar_diameter'
17
+ import { MAX_FONT_SIZE_MULTIPLIER_LANDMARK } from '../../utils'
16
18
  import type { ImagePickerAsset } from '../../utils/native_adapters/image_picker'
17
19
  import {
18
20
  avatarPickerReducer,
@@ -71,6 +73,7 @@ function EditModeContent({ conversationId }: { conversationId: number }) {
71
73
  const navigation = useNavigation()
72
74
  const { data: conversation } = useConversation({ conversation_id: conversationId })
73
75
  const mutation = useConversationAvatarUpdate({ conversationId })
76
+ const previewDiameter = usePreviewAvatarDiameter()
74
77
 
75
78
  const [state, dispatch] = useReducer(avatarPickerReducer, conversation, initAvatarPickerState)
76
79
 
@@ -110,7 +113,14 @@ function EditModeContent({ conversationId }: { conversationId: number }) {
110
113
  </FormSheet.HeaderTextButton>
111
114
  </>
112
115
  }
113
- fallbackPreview={<ConversationAvatar conversation={conversation} size="2xl" />}
116
+ fallbackPreview={
117
+ <ConversationAvatar
118
+ conversation={conversation}
119
+ size="2xl"
120
+ maxFontSizeMultiplier={MAX_FONT_SIZE_MULTIPLIER_LANDMARK}
121
+ style={{ width: previewDiameter, height: previewDiameter }}
122
+ />
123
+ }
114
124
  />
115
125
  )
116
126
  }
@@ -187,7 +197,10 @@ function AvatarPickerFormSheet({
187
197
  activeTab={state.activeTab}
188
198
  onTabPress={tab => dispatch({ type: 'SELECT_TAB', payload: tab })}
189
199
  renderItem={({ item }) => (
190
- <Text style={[styles.tabLabel, item === state.activeTab && styles.tabLabelActive]}>
200
+ <Text
201
+ style={[styles.tabLabel, item === state.activeTab && styles.tabLabelActive]}
202
+ maxFontSizeMultiplier={MAX_FONT_SIZE_MULTIPLIER_LANDMARK}
203
+ >
191
204
  {TAB_LABELS[item]}
192
205
  </Text>
193
206
  )}
@@ -226,7 +239,15 @@ function AvatarPickerFormSheet({
226
239
 
227
240
  function EmptyAvatarPlaceholder() {
228
241
  const styles = useStyles()
229
- return <View style={styles.emptyAvatarPlaceholder} />
242
+ const diameter = usePreviewAvatarDiameter()
243
+ return (
244
+ <View
245
+ style={[
246
+ styles.emptyAvatarPlaceholder,
247
+ { width: diameter, height: diameter, borderRadius: diameter / 2 },
248
+ ]}
249
+ />
250
+ )
230
251
  }
231
252
 
232
253
  type ValidAvatarPickerState = AvatarPickerState &
@@ -264,9 +285,7 @@ function buildPayload(state: AvatarPickerState): AvatarUpdatePayload | null {
264
285
 
265
286
  const useStyles = () => {
266
287
  const { colors } = useTheme()
267
- const fontScale = useFontScale({ maxFontSizeMultiplier: 1.3 })
268
- const uncappedFontScale = useFontScale()
269
- const emptyAvatarDiameter = 80 * uncappedFontScale
288
+ const fontScale = useFontScale({ maxFontSizeMultiplier: MAX_FONT_SIZE_MULTIPLIER_LANDMARK })
270
289
 
271
290
  return StyleSheet.create({
272
291
  formSheetRoot: {
@@ -301,9 +320,6 @@ const useStyles = () => {
301
320
  borderTopColor: colors.borderColorDefaultBase,
302
321
  },
303
322
  emptyAvatarPlaceholder: {
304
- width: emptyAvatarDiameter,
305
- height: emptyAvatarDiameter,
306
- borderRadius: emptyAvatarDiameter / 2,
307
323
  borderWidth: 2,
308
324
  borderStyle: 'dashed',
309
325
  borderColor: colors.borderColorDefaultBase,
@@ -3,6 +3,8 @@ import { StyleSheet, View } from 'react-native'
3
3
  import { EmojiAvatar } from '../../components/display/emoji_avatar'
4
4
  import { IconAvatar } from '../../components/display/icon_avatar'
5
5
  import AvatarPrimitive from '../../components/primitive/avatar_primitive'
6
+ import { usePreviewAvatarDiameter } from '../../hooks/use_preview_avatar_diameter'
7
+ import { MAX_FONT_SIZE_MULTIPLIER_LANDMARK } from '../../utils'
6
8
  import type { AvatarPickerState } from './avatar_picker_state'
7
9
 
8
10
  interface AvatarPreviewProps {
@@ -10,18 +12,24 @@ interface AvatarPreviewProps {
10
12
  fallback: React.ReactNode
11
13
  }
12
14
 
13
- function getPreviewNode(state: AvatarPickerState): React.ReactNode {
15
+ function getPreviewNode(state: AvatarPickerState, diameter: number): React.ReactNode {
16
+ const sizeProps = {
17
+ size: '2xl',
18
+ maxFontSizeMultiplier: MAX_FONT_SIZE_MULTIPLIER_LANDMARK,
19
+ style: { width: diameter, height: diameter },
20
+ } as const
21
+
14
22
  switch (state.selectedType) {
15
23
  case 'icon':
16
24
  if (!state.selectedKey) return null
17
- return <IconAvatar iconKey={state.selectedKey} color={state.selectedColor} size="2xl" />
25
+ return <IconAvatar iconKey={state.selectedKey} color={state.selectedColor} {...sizeProps} />
18
26
  case 'emoji':
19
27
  if (!state.selectedKey) return null
20
- return <EmojiAvatar emoji={state.selectedKey} color={state.selectedColor} size="2xl" />
28
+ return <EmojiAvatar emoji={state.selectedKey} color={state.selectedColor} {...sizeProps} />
21
29
  case 'image':
22
30
  if (!state.imagePreviewUri) return null
23
31
  return (
24
- <AvatarPrimitive.Root size="2xl">
32
+ <AvatarPrimitive.Root {...sizeProps}>
25
33
  <AvatarPrimitive.Mask>
26
34
  <AvatarPrimitive.Image sourceUri={state.imagePreviewUri} />
27
35
  </AvatarPrimitive.Mask>
@@ -33,7 +41,8 @@ function getPreviewNode(state: AvatarPickerState): React.ReactNode {
33
41
  }
34
42
 
35
43
  export function AvatarPreview({ state, fallback }: AvatarPreviewProps) {
36
- const preview = getPreviewNode(state)
44
+ const diameter = usePreviewAvatarDiameter()
45
+ const preview = getPreviewNode(state, diameter)
37
46
  return <View style={styles.container}>{preview ?? fallback}</View>
38
47
  }
39
48
 
@@ -1,15 +1,11 @@
1
+ import { EmojiKeyboard, emojisByCategory, type EmojiType } from '@planningcenter/emoji-keyboard'
1
2
  import React, { useCallback } from 'react'
2
3
  import { StyleSheet, View } from 'react-native'
3
- import { EmojiKeyboard, type EmojiType, type EmojisByCategory } from 'rn-emoji-keyboard'
4
- // rn-emoji-keyboard exposes no public exclusion API, so we reach into its
5
- // internal src/ tree for the emoji JSON. Version is pinned in package.json
6
- // — verify this path still resolves before bumping rn-emoji-keyboard.
7
- import emojiData from 'rn-emoji-keyboard/src/assets/emojis.json'
8
4
  import { useTheme } from '../../hooks'
9
5
 
10
6
  const BLOCKED_EMOJIS = new Set(['🖕'])
11
7
 
12
- const filteredEmojis = (emojiData as EmojisByCategory[]).map(category => ({
8
+ const filteredEmojis = emojisByCategory.map(category => ({
13
9
  ...category,
14
10
  data: category.data.filter(e => !BLOCKED_EMOJIS.has(e.emoji)),
15
11
  }))
@@ -57,6 +53,7 @@ export function EmojiTab({ onEmojiSelect }: EmojiTabProps) {
57
53
  categoryPosition="top"
58
54
  emojisByCategory={filteredEmojis}
59
55
  onEmojiSelected={handleEmojiSelected}
56
+ hideHeader
60
57
  enableSearchBar
61
58
  enableRecentlyUsed
62
59
  theme={emojiTheme}
@@ -2,6 +2,7 @@ import React, { useCallback } from 'react'
2
2
  import { Alert, StyleSheet, View } from 'react-native'
3
3
  import { Button } from '../../components'
4
4
  import { useTheme } from '../../hooks'
5
+ import { MAX_FONT_SIZE_MULTIPLIER_LANDMARK } from '../../utils'
5
6
  import { ImagePicker } from '../../utils/native_adapters'
6
7
  import type { ImagePickerAsset } from '../../utils/native_adapters/image_picker'
7
8
 
@@ -43,6 +44,7 @@ export function UploadTab({ imagePreviewUri, onImageSelect }: UploadTabProps) {
43
44
  variant="outline"
44
45
  appearance="interaction"
45
46
  size="md"
47
+ maxFontSizeMultiplier={MAX_FONT_SIZE_MULTIPLIER_LANDMARK}
46
48
  />
47
49
  </View>
48
50
  )
@@ -49,8 +49,10 @@ import {
49
49
  useConversationUpdate,
50
50
  } from '../hooks/use_conversation'
51
51
  import { availableFeatures, useFeatures } from '../hooks/use_features'
52
+ import { usePreviewAvatarDiameter } from '../hooks/use_preview_avatar_diameter'
52
53
  import { type ConversationResource, MemberResource, isDefined } from '../types'
53
54
  import { GroupResource } from '../types/resources/group_resource'
55
+ import { MAX_FONT_SIZE_MULTIPLIER_LANDMARK } from '../utils'
54
56
  import { genderDisplayLabel } from '../utils/gender_display_label'
55
57
  import { tokens } from '../vendor/tapestry/tokens'
56
58
  // =========================================
@@ -634,10 +636,16 @@ function AvatarCard({
634
636
  onPress: () => void
635
637
  }) {
636
638
  const styles = useStyles()
639
+ const diameter = usePreviewAvatarDiameter()
637
640
 
638
641
  return (
639
642
  <View style={styles.avatarCard}>
640
- <ConversationAvatar conversation={conversation} size="2xl" />
643
+ <ConversationAvatar
644
+ conversation={conversation}
645
+ size="2xl"
646
+ maxFontSizeMultiplier={MAX_FONT_SIZE_MULTIPLIER_LANDMARK}
647
+ style={{ width: diameter, height: diameter }}
648
+ />
641
649
  <Button
642
650
  title="Update avatar"
643
651
  iconNameLeft="general.pencil"
@@ -645,6 +653,7 @@ function AvatarCard({
645
653
  variant="outline"
646
654
  appearance="interaction"
647
655
  size="sm"
656
+ maxFontSizeMultiplier={MAX_FONT_SIZE_MULTIPLIER_LANDMARK}
648
657
  />
649
658
  </View>
650
659
  )