@amityco/react-native-social-uikit 4.1.1-fe2678f6.0 → 4.2.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 (205) hide show
  1. package/README.md +7 -0
  2. package/lib/commonjs/core/assets/icons/post.js +1 -1
  3. package/lib/commonjs/core/assets/icons/post.js.map +1 -1
  4. package/lib/commonjs/core/assets/icons/story.js +1 -1
  5. package/lib/commonjs/core/assets/icons/story.js.map +1 -1
  6. package/lib/commonjs/core/assets/icons/xml.js +16 -2
  7. package/lib/commonjs/core/assets/icons/xml.js.map +1 -1
  8. package/lib/commonjs/core/legacy/file.js +145 -62
  9. package/lib/commonjs/core/legacy/file.js.map +1 -1
  10. package/lib/commonjs/core/stores/slices/feedSlice.js +3 -0
  11. package/lib/commonjs/core/stores/slices/feedSlice.js.map +1 -1
  12. package/lib/commonjs/core/stores/slices/globalfeedSlice.js +5 -0
  13. package/lib/commonjs/core/stores/slices/globalfeedSlice.js.map +1 -1
  14. package/lib/commonjs/core/stores/slices/localVideoThumbnailSlice.js +46 -0
  15. package/lib/commonjs/core/stores/slices/localVideoThumbnailSlice.js.map +1 -0
  16. package/lib/commonjs/core/stores/slices/uiSlice.js +5 -1
  17. package/lib/commonjs/core/stores/slices/uiSlice.js.map +1 -1
  18. package/lib/commonjs/core/stores/store/index.js +3 -1
  19. package/lib/commonjs/core/stores/store/index.js.map +1 -1
  20. package/lib/commonjs/social/components/LoadingImage/index.js +73 -12
  21. package/lib/commonjs/social/components/LoadingImage/index.js.map +1 -1
  22. package/lib/commonjs/social/components/LoadingImage/styles.js +8 -3
  23. package/lib/commonjs/social/components/LoadingImage/styles.js.map +1 -1
  24. package/lib/commonjs/social/components/LoadingVideo/index.js +90 -16
  25. package/lib/commonjs/social/components/LoadingVideo/index.js.map +1 -1
  26. package/lib/commonjs/social/components/LoadingVideo/styles.js +14 -5
  27. package/lib/commonjs/social/components/LoadingVideo/styles.js.map +1 -1
  28. package/lib/commonjs/social/components/MediaViewer/MediaViewer.js +13 -13
  29. package/lib/commonjs/social/components/MediaViewer/MediaViewer.js.map +1 -1
  30. package/lib/commonjs/social/components/MediaViewer/styles.js +11 -1
  31. package/lib/commonjs/social/components/MediaViewer/styles.js.map +1 -1
  32. package/lib/commonjs/social/components/PostContent/index.js +8 -29
  33. package/lib/commonjs/social/components/PostContent/index.js.map +1 -1
  34. package/lib/commonjs/social/components/SelectedMediaComponent/SelectedMediaComponent.js +109 -17
  35. package/lib/commonjs/social/components/SelectedMediaComponent/SelectedMediaComponent.js.map +1 -1
  36. package/lib/commonjs/social/components/legacy/LoadingVideo/index.js +17 -6
  37. package/lib/commonjs/social/components/legacy/LoadingVideo/index.js.map +1 -1
  38. package/lib/commonjs/social/elements/PostMediaElement/PostMediaElement.js +121 -23
  39. package/lib/commonjs/social/elements/PostMediaElement/PostMediaElement.js.map +1 -1
  40. package/lib/commonjs/social/elements/PostMediaElement/styles.js +7 -0
  41. package/lib/commonjs/social/elements/PostMediaElement/styles.js.map +1 -1
  42. package/lib/commonjs/social/features/community/PendingRequest/components/PendingPostList/PendingPostList.js +11 -5
  43. package/lib/commonjs/social/features/community/PendingRequest/components/PendingPostList/PendingPostList.js.map +1 -1
  44. package/lib/commonjs/social/features/feed/components/NewsFeed/NewsFeed.js +14 -0
  45. package/lib/commonjs/social/features/feed/components/NewsFeed/NewsFeed.js.map +1 -1
  46. package/lib/commonjs/social/features/post/Composer/PostComposer.js +234 -86
  47. package/lib/commonjs/social/features/post/Composer/PostComposer.js.map +1 -1
  48. package/lib/commonjs/social/hooks/index.js +11 -0
  49. package/lib/commonjs/social/hooks/index.js.map +1 -1
  50. package/lib/commonjs/social/hooks/useCommunities.js +5 -3
  51. package/lib/commonjs/social/hooks/useCommunities.js.map +1 -1
  52. package/lib/commonjs/social/hooks/useCommunityPermission.js +43 -0
  53. package/lib/commonjs/social/hooks/useCommunityPermission.js.map +1 -0
  54. package/lib/commonjs/social/hooks/useCustomRankingGlobalFeed.js +10 -1
  55. package/lib/commonjs/social/hooks/useCustomRankingGlobalFeed.js.map +1 -1
  56. package/lib/commonjs/social/hooks/useFile.js +11 -0
  57. package/lib/commonjs/social/hooks/useFile.js.map +1 -1
  58. package/lib/commonjs/social/screens/CommunityProfile/components/Header/Header.js +18 -7
  59. package/lib/commonjs/social/screens/CommunityProfile/components/Header/Header.js.map +1 -1
  60. package/lib/commonjs/social/utils/mediaAttachments.js +150 -0
  61. package/lib/commonjs/social/utils/mediaAttachments.js.map +1 -0
  62. package/lib/commonjs/social/utils/postChildren.js +72 -0
  63. package/lib/commonjs/social/utils/postChildren.js.map +1 -0
  64. package/lib/module/core/assets/icons/post.js +1 -1
  65. package/lib/module/core/assets/icons/post.js.map +1 -1
  66. package/lib/module/core/assets/icons/story.js +1 -1
  67. package/lib/module/core/assets/icons/story.js.map +1 -1
  68. package/lib/module/core/assets/icons/xml.js +13 -0
  69. package/lib/module/core/assets/icons/xml.js.map +1 -1
  70. package/lib/module/core/legacy/file.js +145 -62
  71. package/lib/module/core/legacy/file.js.map +1 -1
  72. package/lib/module/core/stores/slices/feedSlice.js +3 -0
  73. package/lib/module/core/stores/slices/feedSlice.js.map +1 -1
  74. package/lib/module/core/stores/slices/globalfeedSlice.js +5 -0
  75. package/lib/module/core/stores/slices/globalfeedSlice.js.map +1 -1
  76. package/lib/module/core/stores/slices/localVideoThumbnailSlice.js +41 -0
  77. package/lib/module/core/stores/slices/localVideoThumbnailSlice.js.map +1 -0
  78. package/lib/module/core/stores/slices/uiSlice.js +5 -1
  79. package/lib/module/core/stores/slices/uiSlice.js.map +1 -1
  80. package/lib/module/core/stores/store/index.js +3 -1
  81. package/lib/module/core/stores/store/index.js.map +1 -1
  82. package/lib/module/social/components/LoadingImage/index.js +74 -14
  83. package/lib/module/social/components/LoadingImage/index.js.map +1 -1
  84. package/lib/module/social/components/LoadingImage/styles.js +8 -3
  85. package/lib/module/social/components/LoadingImage/styles.js.map +1 -1
  86. package/lib/module/social/components/LoadingVideo/index.js +91 -17
  87. package/lib/module/social/components/LoadingVideo/index.js.map +1 -1
  88. package/lib/module/social/components/LoadingVideo/styles.js +14 -5
  89. package/lib/module/social/components/LoadingVideo/styles.js.map +1 -1
  90. package/lib/module/social/components/MediaViewer/MediaViewer.js +13 -13
  91. package/lib/module/social/components/MediaViewer/MediaViewer.js.map +1 -1
  92. package/lib/module/social/components/MediaViewer/styles.js +11 -1
  93. package/lib/module/social/components/MediaViewer/styles.js.map +1 -1
  94. package/lib/module/social/components/PostContent/index.js +8 -29
  95. package/lib/module/social/components/PostContent/index.js.map +1 -1
  96. package/lib/module/social/components/SelectedMediaComponent/SelectedMediaComponent.js +110 -18
  97. package/lib/module/social/components/SelectedMediaComponent/SelectedMediaComponent.js.map +1 -1
  98. package/lib/module/social/components/legacy/LoadingVideo/index.js +17 -6
  99. package/lib/module/social/components/legacy/LoadingVideo/index.js.map +1 -1
  100. package/lib/module/social/elements/PostMediaElement/PostMediaElement.js +123 -25
  101. package/lib/module/social/elements/PostMediaElement/PostMediaElement.js.map +1 -1
  102. package/lib/module/social/elements/PostMediaElement/styles.js +7 -0
  103. package/lib/module/social/elements/PostMediaElement/styles.js.map +1 -1
  104. package/lib/module/social/features/community/PendingRequest/components/PendingPostList/PendingPostList.js +12 -6
  105. package/lib/module/social/features/community/PendingRequest/components/PendingPostList/PendingPostList.js.map +1 -1
  106. package/lib/module/social/features/feed/components/NewsFeed/NewsFeed.js +15 -1
  107. package/lib/module/social/features/feed/components/NewsFeed/NewsFeed.js.map +1 -1
  108. package/lib/module/social/features/post/Composer/PostComposer.js +236 -89
  109. package/lib/module/social/features/post/Composer/PostComposer.js.map +1 -1
  110. package/lib/module/social/hooks/index.js +1 -0
  111. package/lib/module/social/hooks/index.js.map +1 -1
  112. package/lib/module/social/hooks/useCommunities.js +5 -3
  113. package/lib/module/social/hooks/useCommunities.js.map +1 -1
  114. package/lib/module/social/hooks/useCommunityPermission.js +37 -0
  115. package/lib/module/social/hooks/useCommunityPermission.js.map +1 -0
  116. package/lib/module/social/hooks/useCustomRankingGlobalFeed.js +10 -1
  117. package/lib/module/social/hooks/useCustomRankingGlobalFeed.js.map +1 -1
  118. package/lib/module/social/hooks/useFile.js +11 -0
  119. package/lib/module/social/hooks/useFile.js.map +1 -1
  120. package/lib/module/social/screens/CommunityProfile/components/Header/Header.js +19 -8
  121. package/lib/module/social/screens/CommunityProfile/components/Header/Header.js.map +1 -1
  122. package/lib/module/social/utils/mediaAttachments.js +139 -0
  123. package/lib/module/social/utils/mediaAttachments.js.map +1 -0
  124. package/lib/module/social/utils/postChildren.js +66 -0
  125. package/lib/module/social/utils/postChildren.js.map +1 -0
  126. package/lib/typescript/core/assets/icons/xml.d.ts +1 -0
  127. package/lib/typescript/core/assets/icons/xml.d.ts.map +1 -1
  128. package/lib/typescript/core/legacy/file.d.ts +32 -0
  129. package/lib/typescript/core/legacy/file.d.ts.map +1 -1
  130. package/lib/typescript/core/stores/slices/feedSlice.d.ts.map +1 -1
  131. package/lib/typescript/core/stores/slices/globalfeedSlice.d.ts.map +1 -1
  132. package/lib/typescript/core/stores/slices/localVideoThumbnailSlice.d.ts +35 -0
  133. package/lib/typescript/core/stores/slices/localVideoThumbnailSlice.d.ts.map +1 -0
  134. package/lib/typescript/core/stores/slices/uiSlice.d.ts +5 -0
  135. package/lib/typescript/core/stores/slices/uiSlice.d.ts.map +1 -1
  136. package/lib/typescript/core/stores/store/index.d.ts.map +1 -1
  137. package/lib/typescript/core/types/mention.d.ts +34 -0
  138. package/lib/typescript/core/types/mention.d.ts.map +1 -1
  139. package/lib/typescript/social/components/LoadingImage/index.d.ts +8 -2
  140. package/lib/typescript/social/components/LoadingImage/index.d.ts.map +1 -1
  141. package/lib/typescript/social/components/LoadingImage/styles.d.ts.map +1 -1
  142. package/lib/typescript/social/components/LoadingVideo/index.d.ts +9 -2
  143. package/lib/typescript/social/components/LoadingVideo/index.d.ts.map +1 -1
  144. package/lib/typescript/social/components/LoadingVideo/styles.d.ts +1 -0
  145. package/lib/typescript/social/components/LoadingVideo/styles.d.ts.map +1 -1
  146. package/lib/typescript/social/components/MediaViewer/MediaViewer.d.ts.map +1 -1
  147. package/lib/typescript/social/components/MediaViewer/styles.d.ts +1 -0
  148. package/lib/typescript/social/components/MediaViewer/styles.d.ts.map +1 -1
  149. package/lib/typescript/social/components/PostContent/index.d.ts.map +1 -1
  150. package/lib/typescript/social/components/SelectedMediaComponent/SelectedMediaComponent.d.ts +2 -2
  151. package/lib/typescript/social/components/SelectedMediaComponent/SelectedMediaComponent.d.ts.map +1 -1
  152. package/lib/typescript/social/components/legacy/LoadingVideo/index.d.ts.map +1 -1
  153. package/lib/typescript/social/elements/PostMediaElement/PostMediaElement.d.ts.map +1 -1
  154. package/lib/typescript/social/elements/PostMediaElement/styles.d.ts +4 -0
  155. package/lib/typescript/social/elements/PostMediaElement/styles.d.ts.map +1 -1
  156. package/lib/typescript/social/features/community/PendingRequest/components/PendingPostList/PendingPostList.d.ts.map +1 -1
  157. package/lib/typescript/social/features/feed/components/NewsFeed/NewsFeed.d.ts.map +1 -1
  158. package/lib/typescript/social/features/post/Composer/PostComposer.d.ts.map +1 -1
  159. package/lib/typescript/social/hooks/index.d.ts +1 -0
  160. package/lib/typescript/social/hooks/index.d.ts.map +1 -1
  161. package/lib/typescript/social/hooks/useCommunities.d.ts +2 -1
  162. package/lib/typescript/social/hooks/useCommunities.d.ts.map +1 -1
  163. package/lib/typescript/social/hooks/useCommunityPermission.d.ts +14 -0
  164. package/lib/typescript/social/hooks/useCommunityPermission.d.ts.map +1 -0
  165. package/lib/typescript/social/hooks/useCustomRankingGlobalFeed.d.ts.map +1 -1
  166. package/lib/typescript/social/hooks/useFile.d.ts +11 -0
  167. package/lib/typescript/social/hooks/useFile.d.ts.map +1 -1
  168. package/lib/typescript/social/screens/CommunityProfile/components/Header/Header.d.ts.map +1 -1
  169. package/lib/typescript/social/utils/mediaAttachments.d.ts +91 -0
  170. package/lib/typescript/social/utils/mediaAttachments.d.ts.map +1 -0
  171. package/lib/typescript/social/utils/postChildren.d.ts +38 -0
  172. package/lib/typescript/social/utils/postChildren.d.ts.map +1 -0
  173. package/package.json +3 -3
  174. package/src/core/assets/icons/post.tsx +1 -1
  175. package/src/core/assets/icons/story.tsx +1 -1
  176. package/src/core/assets/icons/xml.ts +15 -0
  177. package/src/core/legacy/file.tsx +169 -84
  178. package/src/core/stores/slices/feedSlice.ts +3 -0
  179. package/src/core/stores/slices/globalfeedSlice.ts +5 -0
  180. package/src/core/stores/slices/localVideoThumbnailSlice.ts +58 -0
  181. package/src/core/stores/slices/uiSlice.ts +8 -0
  182. package/src/core/stores/store/index.ts +2 -0
  183. package/src/core/types/mention.ts +34 -0
  184. package/src/social/components/LoadingImage/index.tsx +81 -12
  185. package/src/social/components/LoadingImage/styles.ts +8 -3
  186. package/src/social/components/LoadingVideo/index.tsx +111 -21
  187. package/src/social/components/LoadingVideo/styles.ts +13 -4
  188. package/src/social/components/MediaViewer/MediaViewer.tsx +11 -12
  189. package/src/social/components/MediaViewer/styles.ts +9 -1
  190. package/src/social/components/PostContent/index.tsx +3 -35
  191. package/src/social/components/SelectedMediaComponent/SelectedMediaComponent.tsx +103 -13
  192. package/src/social/components/legacy/LoadingVideo/index.tsx +26 -15
  193. package/src/social/elements/PostMediaElement/PostMediaElement.tsx +139 -29
  194. package/src/social/elements/PostMediaElement/styles.ts +7 -0
  195. package/src/social/features/community/PendingRequest/components/PendingPostList/PendingPostList.tsx +11 -6
  196. package/src/social/features/feed/components/NewsFeed/NewsFeed.tsx +16 -1
  197. package/src/social/features/post/Composer/PostComposer.tsx +304 -91
  198. package/src/social/hooks/index.ts +1 -0
  199. package/src/social/hooks/useCommunities.ts +4 -2
  200. package/src/social/hooks/useCommunityPermission.ts +40 -0
  201. package/src/social/hooks/useCustomRankingGlobalFeed.ts +8 -1
  202. package/src/social/hooks/useFile.ts +11 -0
  203. package/src/social/screens/CommunityProfile/components/Header/Header.tsx +19 -10
  204. package/src/social/utils/mediaAttachments.ts +158 -0
  205. package/src/social/utils/postChildren.ts +85 -0
@@ -0,0 +1,91 @@
1
+ import type { IDisplayImage } from '../../core/types';
2
+ /**
3
+ * The composer caps a post at 10 attachments of a single media type.
4
+ */
5
+ export declare const MAX_MEDIA_ATTACHMENTS = 10;
6
+ /**
7
+ * The identity fields a staged attachment must carry for the helpers below to
8
+ * recognise it across its own upload. See `IDisplayImage.localId`.
9
+ */
10
+ export type MediaPickCandidate = IDisplayImage & {
11
+ localId: string;
12
+ };
13
+ /**
14
+ * Drop the candidates that are already staged, and any repeated inside the
15
+ * batch itself, preserving the order of the ones that survive.
16
+ *
17
+ * De-duplication runs against the LOCAL identity, never `fileName`: the moment
18
+ * an item finishes uploading the composer rewrites its `fileName` to the
19
+ * server's `attributes.name`, and edit-mode entries are hydrated with the
20
+ * fileId, so a set built from `fileName` compares server-side names against
21
+ * picker names and never matches. `localId` / `localFileName` are assigned once
22
+ * at pick time and survive the upload, so they still line up here.
23
+ *
24
+ * `localFileName` is a second key next to `localId` because the picker's
25
+ * `asset.id` is not populated by every configuration — the name is what catches
26
+ * a re-pick when no id came back. Camera captures deliberately carry no
27
+ * `localFileName`, so two shots never de-duplicate against each other.
28
+ *
29
+ * The drop is silent by design (no toast, no alert) and leaves the already
30
+ * staged copy exactly where it is, which is what keeps a re-pick of A followed
31
+ * by B and C rendering as A, B, C.
32
+ */
33
+ export declare function dedupeMediaPicks<T extends MediaPickCandidate>(staged: IDisplayImage[], candidates: T[]): T[];
34
+ /**
35
+ * Append `additions` to `staged`, holding the 10-attachment cap.
36
+ */
37
+ export declare function appendWithinCap(staged: IDisplayImage[], additions: IDisplayImage[]): IDisplayImage[];
38
+ /**
39
+ * Route a finished upload back to the entry it actually belongs to, matched by
40
+ * the local path the child was mounted with.
41
+ *
42
+ * The child reports both a positional `index` and the `originalPath`. The index
43
+ * is the array position the frame was RENDERED at when the upload started —
44
+ * LoadingVideo goes further and memoises its uploader on `[source]` alone, so
45
+ * it freezes the index of the render where the source last changed. Writing
46
+ * `data[index]` therefore lands on whatever now sits at that position: a frame
47
+ * removed, an upload retried after the array grew, or a slow upload finishing
48
+ * after its neighbours moved all put one file's remote url onto another file's
49
+ * entry — which is what QA saw as an already-loaded image repainting with a
50
+ * different picture once the network came back. Matching on `originalPath` is
51
+ * exact: a not-yet-uploaded entry's `url` IS the local path its child was
52
+ * handed, and pick de-duplication guarantees those are unique across the array.
53
+ *
54
+ * The patch is MERGED rather than replacing the entry: `localId` /
55
+ * `localFileName` are what the next pick de-duplicates against, and
56
+ * what the carousel keys its frames by, and the picker's width/height still
57
+ * classify the frame ratio — a wholesale replacement dropped all of them.
58
+ *
59
+ * No match means the entry is gone (closed mid-upload) or already finished, so
60
+ * the array is returned untouched rather than resurrecting or double-writing.
61
+ */
62
+ export declare function applyFinishedUpload(staged: IDisplayImage[], originalPath: string, patch: Partial<IDisplayImage>): IDisplayImage[];
63
+ /**
64
+ * Add or remove a media item's `source` from the set of in-flight uploads.
65
+ *
66
+ * The composer gates Post/Save on this set being empty. It replaced a single
67
+ * shared boolean that whichever upload finished FIRST flipped back to false on
68
+ * behalf of every other frame, unlocking Post while the rest were still
69
+ * uploading.
70
+ *
71
+ * Returns the same set instance when nothing changes, so a redundant report —
72
+ * a child clearing on unmount right after `handleLoadEnd` already cleared —
73
+ * does not trigger a re-render.
74
+ */
75
+ export declare function toggleUploadingSource(uploading: Set<string>, source: string, isUploading: boolean): Set<string>;
76
+ /**
77
+ * The entries the feed's local-thumbnail bridge should hold for a just-created
78
+ * video post.
79
+ *
80
+ * A video's server thumbnail does not exist until transcoding finishes, so the
81
+ * feed falls back to the frame the composer decoded at pick time, matched on
82
+ * the uploaded video's fileId. Entries without both halves are dropped: a row
83
+ * with an empty url still satisfies the feed's lookup, which suppresses its own
84
+ * pending state and leaves the frame blank instead of showing the play button
85
+ * over a plain background.
86
+ */
87
+ export declare function toLocalVideoThumbnails(videos: Pick<IDisplayImage, 'fileId' | 'thumbNail'>[]): {
88
+ fileId: string;
89
+ thumbnailUrl: string;
90
+ }[];
91
+ //# sourceMappingURL=mediaAttachments.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mediaAttachments.d.ts","sourceRoot":"","sources":["../../../../src/social/utils/mediaAttachments.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEtD;;GAEG;AACH,eAAO,MAAM,qBAAqB,KAAK,CAAC;AAExC;;;GAGG;AACH,MAAM,MAAM,kBAAkB,GAAG,aAAa,GAAG;IAC/C,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,kBAAkB,EAC3D,MAAM,EAAE,aAAa,EAAE,EACvB,UAAU,EAAE,CAAC,EAAE,GACd,CAAC,EAAE,CAkBL;AAED;;GAEG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,aAAa,EAAE,EACvB,SAAS,EAAE,aAAa,EAAE,GACzB,aAAa,EAAE,CAKjB;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,aAAa,EAAE,EACvB,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,OAAO,CAAC,aAAa,CAAC,GAC5B,aAAa,EAAE,CAMjB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CACnC,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,EACtB,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,OAAO,GACnB,GAAG,CAAC,MAAM,CAAC,CASb;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,IAAI,CAAC,aAAa,EAAE,QAAQ,GAAG,WAAW,CAAC,EAAE,GACpD;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,EAAE,CAU5C"}
@@ -0,0 +1,38 @@
1
+ import type { IVideoPost } from '../components/legacy/Social/PostList';
2
+ /** A post child as the feed resolves it: the child post plus its own payload. */
3
+ export type ResolvedPostChild = {
4
+ post?: Amity.Post;
5
+ dataType?: string;
6
+ data?: Record<string, any>;
7
+ };
8
+ export type ClassifiedPostChildren = {
9
+ /** Medium-size download urls, de-duplicated. */
10
+ images: string[];
11
+ videos: IVideoPost[];
12
+ polls: {
13
+ pollId: string;
14
+ }[];
15
+ livestreams: Amity.Room['roomId'][];
16
+ /**
17
+ * The media children the carousel renders, in the same order — and for
18
+ * videos, at the same indices — as `videos`. The full-screen player is built
19
+ * from `videos` but opened with an index taken from this array, so a child
20
+ * that lands in one and not the other opens the wrong video.
21
+ */
22
+ media: Amity.Post[];
23
+ };
24
+ /**
25
+ * Sort a post's resolved children into what each surface needs.
26
+ *
27
+ * Every hop into a child's payload is guarded, because the cost of being wrong
28
+ * is out of proportion to the saving: an ErrorBoundary sits around the whole
29
+ * UIKit without an onError, so one TypeError in here replaces the entire app
30
+ * with a generic error page and takes its own stack down with it.
31
+ *
32
+ * `ContentDataVideo` types `videoFileId` as always present but every size
33
+ * inside it as optional, so `original` is the hop that can legitimately be
34
+ * missing. The keys above it are guarded on the same principle rather than on
35
+ * evidence that they go missing.
36
+ */
37
+ export declare function classifyPostChildren(children: ResolvedPostChild[], apiRegion: string): ClassifiedPostChildren;
38
+ //# sourceMappingURL=postChildren.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"postChildren.d.ts","sourceRoot":"","sources":["../../../../src/social/utils/postChildren.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sCAAsC,CAAC;AAEvE,iFAAiF;AACjF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,CAAC,EAAE,KAAK,CAAC,IAAI,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,gDAAgD;IAChD,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,KAAK,EAAE;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC5B,WAAW,EAAE,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;IACpC;;;;;OAKG;IACH,KAAK,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC;CACrB,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,iBAAiB,EAAE,EAC7B,SAAS,EAAE,MAAM,GAChB,sBAAsB,CA4CxB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amityco/react-native-social-uikit",
3
- "version": "4.1.1-fe2678f6.0",
3
+ "version": "4.2.0",
4
4
  "description": "Social UIKit",
5
5
  "main": "lib/commonjs/index",
6
6
  "module": "lib/module/index",
@@ -57,7 +57,7 @@
57
57
  "registry": "https://registry.npmjs.org/"
58
58
  },
59
59
  "devDependencies": {
60
- "@amityco/ts-sdk-react-native": "7.25.1-144f8feb.0",
60
+ "@amityco/ts-sdk-react-native": "7.27.0",
61
61
  "@babel/plugin-transform-export-namespace-from": "^7.27.1",
62
62
  "@commitlint/config-conventional": "^17.0.2",
63
63
  "@evilmartians/lefthook": "^1.2.2",
@@ -119,7 +119,7 @@
119
119
  "@types/react": "^19.1.0"
120
120
  },
121
121
  "peerDependencies": {
122
- "@amityco/ts-sdk-react-native": "7.25.1-144f8feb.0",
122
+ "@amityco/ts-sdk-react-native": "7.27.0",
123
123
  "@livekit/react-native": "^2.9.6",
124
124
  "@livekit/react-native-webrtc": "^137.0.2",
125
125
  "@react-native-async-storage/async-storage": "^1.19.3",
@@ -1,7 +1,7 @@
1
1
  import { SVGProps } from 'react';
2
2
 
3
3
  const icon = (props?: SVGProps<SVGSVGElement>) => {
4
- const { width = 24, height = 24, fill = '#292B32' } = props || {};
4
+ const { width = 24, height = 24, fill = 'currentColor' } = props || {};
5
5
 
6
6
  return `<svg width="${width}" height="${height}" viewBox="0 0 ${width} ${height}" fill="none" xmlns="http://www.w3.org/2000/svg">
7
7
  <path d="M16.0078 15.3789L17.1328 14.2539C17.3086 14.0781 17.625 14.2188 17.625 14.4648V19.5625C17.625 20.5117 16.8516 21.25 15.9375 21.25H3.5625C2.61328 21.25 1.875 20.5117 1.875 19.5625V7.1875C1.875 6.27344 2.61328 5.5 3.5625 5.5H13.1602C13.4062 5.5 13.5469 5.81641 13.3711 5.99219L12.2461 7.11719C12.1758 7.1875 12.1055 7.1875 12.0352 7.1875H3.5625V19.5625H15.9375V15.5898C15.9375 15.5195 15.9375 15.4492 16.0078 15.3789ZM21.4922 8.3125L12.2812 17.5234L9.08203 17.875C8.16797 17.9805 7.39453 17.207 7.5 16.293L7.85156 13.0938L17.0625 3.88281C17.8711 3.07422 19.1719 3.07422 19.9805 3.88281L21.4922 5.39453C22.3008 6.20312 22.3008 7.50391 21.4922 8.3125ZM18.0469 9.36719L16.0078 7.32812L9.46875 13.8672L9.1875 16.1875L11.5078 15.9062L18.0469 9.36719ZM20.2969 6.58984L18.7852 5.07812C18.6445 4.90234 18.3984 4.90234 18.2578 5.07812L17.2031 6.13281L19.2422 8.20703L20.332 7.11719C20.4727 6.94141 20.4727 6.73047 20.2969 6.58984Z" fill="${fill}"/>
@@ -1,7 +1,7 @@
1
1
  import { SVGProps } from 'react';
2
2
 
3
3
  const icon = (props?: SVGProps<SVGSVGElement>) => {
4
- const { width = 24, height = 24, fill = '#292B32' } = props || {};
4
+ const { width = 24, height = 24, fill = 'currentColor' } = props || {};
5
5
 
6
6
  return `<svg
7
7
  width="${width}"
@@ -60,6 +60,21 @@ export const closeIcon = (
60
60
  ): string => `<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
61
61
  <path d="M7.65234 6.25L11.4492 2.48828C11.6602 2.27734 11.6602 1.89062 11.4492 1.67969L10.5703 0.800781C10.3594 0.589844 9.97266 0.589844 9.76172 0.800781L6 4.59766L2.20312 0.800781C1.99219 0.589844 1.60547 0.589844 1.39453 0.800781L0.515625 1.67969C0.304688 1.89062 0.304688 2.27734 0.515625 2.48828L4.3125 6.25L0.515625 10.0469C0.304688 10.2578 0.304688 10.6445 0.515625 10.8555L1.39453 11.7344C1.60547 11.9453 1.99219 11.9453 2.20312 11.7344L6 7.9375L9.76172 11.7344C9.97266 11.9453 10.3594 11.9453 10.5703 11.7344L11.4492 10.8555C11.6602 10.6445 11.6602 10.2578 11.4492 10.0469L7.65234 6.25Z" fill="${color}"/>
62
62
  </svg>`;
63
+ // Ported verbatim from web UIKit icons/Close, for the carousel composer's
64
+ // per-frame remove button.
65
+ //
66
+ // Not a swap for `closeIcon` above, which eleven other screens render — the
67
+ // difference that matters here is that this one carries its padding INSIDE its
68
+ // own 32-unit viewBox: the cross spans 8.4 to 23.6, so it fills about 47% of
69
+ // the box against `closeIcon`'s near-100%. Rendered at 20 inside a 28dp disc it
70
+ // draws a ~9dp cross with the scrim still visible around it, which is exactly
71
+ // what web looks like. Using `closeIcon` at the same 20 filled the disc edge to
72
+ // edge and left a bare white X floating on the photo.
73
+ export const mediaRemoveIcon = (
74
+ color: string = '#FFFFFF'
75
+ ): string => `<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
76
+ <path d="M18.2031 16.2861L23.2656 21.3486C23.5469 21.6299 23.5469 22.1455 23.2656 22.4268L22.0938 23.5986C21.8125 23.8799 21.2969 23.8799 21.0156 23.5986L16 18.5361L10.9375 23.5986C10.6562 23.8799 10.1406 23.8799 9.85938 23.5986L8.6875 22.4268C8.40625 22.1455 8.40625 21.6299 8.6875 21.3486L13.75 16.2861L8.6875 11.2705C8.40625 10.9893 8.40625 10.4736 8.6875 10.1924L9.85938 9.02051C10.1406 8.73926 10.6562 8.73926 10.9375 9.02051L16 14.083L21.0156 9.02051C21.2969 8.73926 21.8125 8.73926 22.0938 9.02051L23.2656 10.1924C23.5469 10.4736 23.5469 10.9893 23.2656 11.2705L18.2031 16.2861Z" fill="${color}"/>
77
+ </svg>`;
63
78
  // Ported from web UIKit icons/MutedFilled (speaker + waves) — shown when audio
64
79
  // is ON (unmuted). Used by the carousel full-screen media viewer.
65
80
  export const soundOnIcon = (
@@ -1,117 +1,202 @@
1
1
  import { FileRepository, ContentFeedType } from '@amityco/ts-sdk-react-native';
2
2
 
3
+ import NetInfo from '@react-native-community/netinfo';
3
4
  import { Platform } from 'react-native';
4
5
  import { appendFileToFormData } from '../utils/fileUpload';
5
6
 
7
+ /**
8
+ * The rejection shape every uploader in this module throws.
9
+ *
10
+ * `LoadingImage` / `LoadingVideo` currently ignore the thrown value and show a
11
+ * generic toast, so the contract is deliberately loose — but keeping one shape
12
+ * means a caller that *does* want to branch (e.g. the `INVALID_IMAGE`
13
+ * moderation case) can read `code` instead of sniffing strings.
14
+ */
15
+ export type UploadError = {
16
+ message: string;
17
+ details?: string;
18
+ code?: string;
19
+ originalError?: unknown;
20
+ };
21
+
22
+ /**
23
+ * Thrown when the SDK resolves without a payload. A 2xx with no `data` is still
24
+ * a failed upload as far as the caller is concerned — the callers immediately
25
+ * read `file[0]?.fileId`, so resolving `undefined` would only move the failure
26
+ * somewhere less diagnosable.
27
+ */
28
+ const NO_FILE_DATA_ERROR: UploadError = {
29
+ message: 'Upload failed - no file data returned',
30
+ };
31
+
32
+ /**
33
+ * Thrown before an attempt is even made when the device has no connection.
34
+ *
35
+ * Rejecting up front is not belt-and-braces — on iOS it is the only thing that
36
+ * produces a failure at all. Android's networking stack rejects an offline
37
+ * request almost immediately, so the uploaders' `catch` ran and the frame
38
+ * showed its error state; NSURLSession instead sits on the request waiting for
39
+ * connectivity, so the promise never settled, the `catch` never ran, and the
40
+ * frame stayed on its spinner forever with no way to reach the failed state
41
+ * The spec asks for an error state per attachment that failed to upload,
42
+ * whatever the platform.
43
+ */
44
+ const OFFLINE_ERROR: UploadError = {
45
+ message: 'Upload failed - no internet connection',
46
+ code: 'OFFLINE',
47
+ };
48
+
49
+ /**
50
+ * Reject immediately when there is no connection, rather than handing the
51
+ * request to a networking stack that may never answer.
52
+ *
53
+ * Only covers being offline as the attempt STARTS. A connection dropped
54
+ * mid-upload still leaves iOS waiting, because nothing here can abort a
55
+ * request already inside the SDK — the composer's reconnect retry is what
56
+ * eventually recovers that case.
57
+ */
58
+ async function assertOnline(): Promise<void> {
59
+ const state = await NetInfo.fetch();
60
+ if (!state.isConnected) throw OFFLINE_ERROR;
61
+ }
62
+
63
+ /**
64
+ * Normalise whatever the SDK / networking layer threw into an `UploadError`.
65
+ *
66
+ * A dropped connection surfaces here as a plain `Error` ("Network request
67
+ * failed") carrying no Amity error code — which is exactly the reported repro:
68
+ * the user toggles the network off mid-upload and the request rejects.
69
+ */
70
+ function toUploadError(error: any): UploadError {
71
+ return {
72
+ message: 'Upload failed',
73
+ details:
74
+ error?.message || "We couldn't complete your upload. Please try again.",
75
+ originalError: error,
76
+ };
77
+ }
78
+
79
+ /**
80
+ * Every uploader below is a plain `async` function with try/catch,
81
+ * NOT `new Promise(async (resolve, reject) => …)`.
82
+ *
83
+ * The old wrapper was actively harmful, not merely redundant. An `async`
84
+ * executor returns a promise that `new Promise` neither awaits nor observes, so
85
+ * anything the executor threw — and a mid-upload network drop makes
86
+ * `FileRepository.upload*` throw — settled only that orphaned promise. The
87
+ * outer promise stayed **pending forever**: `LoadingVideo.uploadFileToAmity`
88
+ * sat awaiting it, its `catch` never ran, `handleLoadEnd()` was never called,
89
+ * `loading` stayed `true`, and the frame kept spinning even after the
90
+ * connection came back, so the tappable retry overlay was unreachable.
91
+ *
92
+ * Throwing from an `async` function has no such hole: a `throw` anywhere in the
93
+ * body — including from code the function `await`ed — settles the promise it
94
+ * returned. `uploadImageFile` already had the try/catch, which is
95
+ * why the image path showed a failed state while the video path hung.
96
+ */
6
97
  export async function uploadFile(
7
98
  filePath: string,
8
99
  perCentCallback?: (percent: number) => void
9
100
  ): Promise<Amity.File<any>[]> {
10
- return await new Promise(async (resolve, reject) => {
11
- const formData = new FormData();
12
- const parts = filePath.split('/');
13
- const fileName = parts[parts.length - 1];
14
- const fileType = Platform.OS === 'ios' ? 'image/jpeg' : 'image/jpg';
101
+ const formData = new FormData();
102
+ const parts = filePath.split('/');
103
+ const fileName = parts[parts.length - 1];
104
+ const fileType = Platform.OS === 'ios' ? 'image/jpeg' : 'image/jpg';
15
105
 
16
- await appendFileToFormData(formData, 'files', filePath, fileName, fileType);
106
+ let file: Amity.File<any>[] | undefined;
17
107
 
18
- const { data: file } = await FileRepository.uploadFile(
19
- formData,
20
- (percent) => {
21
- perCentCallback && perCentCallback(percent);
22
- }
23
- );
24
- if (file) {
25
- resolve(file);
26
- } else {
27
- reject('Upload error');
28
- }
29
- });
108
+ await assertOnline();
109
+
110
+ try {
111
+ appendFileToFormData(formData, 'files', filePath, fileName, fileType);
112
+
113
+ ({ data: file } = await FileRepository.uploadFile(formData, (percent) => {
114
+ perCentCallback && perCentCallback(percent);
115
+ }));
116
+ } catch (error: any) {
117
+ throw toUploadError(error);
118
+ }
119
+
120
+ // Deliberately outside the try: an empty payload is not an exception to
121
+ // re-wrap, and throwing it in here would round-trip it through
122
+ // `toUploadError` and lose the specific message.
123
+ if (!file) throw NO_FILE_DATA_ERROR;
124
+
125
+ return file;
30
126
  }
127
+
31
128
  export async function uploadImageFile(
32
129
  filePath: string,
33
130
  perCentCallback?: (percent: number) => void
34
131
  ): Promise<Amity.File<'image'>[]> {
35
- return await new Promise(async (resolve, reject) => {
36
- try {
37
- const formData = new FormData();
38
- const parts = filePath.split('/');
39
- const fileName = parts[parts.length - 1];
40
- const fileType = Platform.OS === 'ios' ? 'image/jpeg' : 'image/jpg';
41
-
42
- await appendFileToFormData(
43
- formData,
44
- 'files',
45
- filePath,
46
- fileName,
47
- fileType
48
- );
49
-
50
- const { data: file } = await FileRepository.uploadImage(
51
- formData,
52
- (percent) => {
53
- perCentCallback && perCentCallback(percent);
54
- }
55
- );
56
-
57
- if (file) {
58
- resolve(file);
59
- } else {
60
- reject({ message: 'Upload failed - no file data returned' });
61
- }
62
- } catch (error: any) {
63
- if (
64
- error?.message?.includes('INVALID_IMAGE') ||
65
- error?.message?.includes('Inappropriate')
66
- ) {
67
- reject({
68
- message: 'Inappropriate image',
69
- details: 'Please choose a different image to upload.',
70
- code: 'INVALID_IMAGE',
71
- });
72
- } else {
73
- reject({
74
- message: 'Upload failed',
75
- details:
76
- error?.message ||
77
- "We couldn't complete your upload. Please try again.",
78
- originalError: error,
79
- });
80
- }
132
+ const formData = new FormData();
133
+ const parts = filePath.split('/');
134
+ const fileName = parts[parts.length - 1];
135
+ const fileType = Platform.OS === 'ios' ? 'image/jpeg' : 'image/jpg';
136
+
137
+ let file: Amity.File<'image'>[] | undefined;
138
+
139
+ await assertOnline();
140
+
141
+ try {
142
+ appendFileToFormData(formData, 'files', filePath, fileName, fileType);
143
+
144
+ ({ data: file } = await FileRepository.uploadImage(formData, (percent) => {
145
+ perCentCallback && perCentCallback(percent);
146
+ }));
147
+ } catch (error: any) {
148
+ // Moderation rejects the image server-side; that is spelled out here
149
+ // rather than folded into the generic "please try again" copy.
150
+ if (
151
+ error?.message?.includes('INVALID_IMAGE') ||
152
+ error?.message?.includes('Inappropriate')
153
+ ) {
154
+ throw {
155
+ message: 'Inappropriate image',
156
+ details: 'Please choose a different image to upload.',
157
+ code: 'INVALID_IMAGE',
158
+ } as UploadError;
81
159
  }
82
- });
160
+
161
+ throw toUploadError(error);
162
+ }
163
+
164
+ if (!file) throw NO_FILE_DATA_ERROR;
165
+
166
+ return file;
83
167
  }
168
+
84
169
  export async function uploadVideoFile(
85
170
  filePath: string,
86
171
  perCentCallback?: (percent: number) => void
87
172
  ): Promise<Amity.File<any>[]> {
88
- return await new Promise(async (resolve, reject) => {
89
- const formData = new FormData();
90
- const parts = filePath.split('/');
91
- const fileName = parts[parts.length - 1];
173
+ const formData = new FormData();
174
+ const parts = filePath.split('/');
175
+ const fileName = parts[parts.length - 1];
92
176
 
93
- await appendFileToFormData(
94
- formData,
95
- 'files',
96
- filePath,
97
- fileName,
98
- 'video/mp4'
99
- );
177
+ let file: Amity.File<any>[] | undefined;
178
+
179
+ await assertOnline();
180
+
181
+ try {
182
+ appendFileToFormData(formData, 'files', filePath, fileName, 'video/mp4');
100
183
 
101
- const { data: file } = await FileRepository.uploadVideo(
184
+ ({ data: file } = await FileRepository.uploadVideo(
102
185
  formData,
103
186
  ContentFeedType.POST,
104
187
  (percent) => {
105
188
  perCentCallback && perCentCallback(percent);
106
189
  }
107
- );
108
- if (file) {
109
- resolve(file);
110
- } else {
111
- reject('Upload error');
112
- }
113
- });
190
+ ));
191
+ } catch (error: any) {
192
+ throw toUploadError(error);
193
+ }
194
+
195
+ if (!file) throw NO_FILE_DATA_ERROR;
196
+
197
+ return file;
114
198
  }
199
+
115
200
  export async function deleteAmityFile(
116
201
  fileId: string
117
202
  ): Promise<{ success: boolean }> {
@@ -30,6 +30,9 @@ const feedSlice = createSlice({
30
30
  ) => {
31
31
  const { postId, postDetail } = action.payload;
32
32
  const index = state.postList.findIndex((item) => item.postId === postId);
33
+ // Same miss as in globalfeedSlice: a postId this list never held wrote a
34
+ // "-1" property instead of updating anything, leaving the entries stale.
35
+ if (index < 0) return;
33
36
  state.postList[index] = postDetail;
34
37
  },
35
38
  deleteByPostId: (state, action: PayloadAction<{ postId: string }>) => {
@@ -66,6 +66,11 @@ const globalFeedSlice = createSlice({
66
66
  const index = state.postList.findIndex(
67
67
  (item) => !isAmityAd(item) && item.postId === postId
68
68
  );
69
+ // A post edited from a feed this list never held (a community feed, say)
70
+ // is simply not here. Without this guard the -1 miss was written as a
71
+ // "-1" property on the array: the real entries stayed stale and nothing
72
+ // reported a failure.
73
+ if (index < 0) return;
69
74
  state.postList[index] = postDetail;
70
75
  },
71
76
  deleteByPostId: (state, action: PayloadAction<{ postId: string }>) => {
@@ -0,0 +1,58 @@
1
+ import { PayloadAction, createSlice } from '@reduxjs/toolkit';
2
+
3
+ /**
4
+ * A video post has no server-side thumbnail until the backend finishes
5
+ * transcoding, so for the first stretch of a new post's life the feed has
6
+ * nothing to render. The composer has already decoded a frame locally to show
7
+ * its own preview, so keep that frame around and let the feed borrow it until
8
+ * the real one exists.
9
+ *
10
+ * Keyed by the ORIGINAL video's fileId, which is what the created post carries
11
+ * in `data.videoFileId.original`. Mirrors web's LayoutProvider `videoThumbnail`
12
+ * + the VideoContent fallback.
13
+ *
14
+ * Only the most recently created post is kept — the same bound web has — so
15
+ * this never grows with session length. These are local file uris, valid only
16
+ * for this process; nothing persists them.
17
+ */
18
+ export type LocalVideoThumbnail = {
19
+ /** fileId of the original (untranscoded) video */
20
+ fileId: string;
21
+ /** local uri of the frame the composer decoded */
22
+ thumbnailUrl: string;
23
+ };
24
+
25
+ interface LocalVideoThumbnailState {
26
+ postId: string | null;
27
+ videos: LocalVideoThumbnail[];
28
+ }
29
+
30
+ const initialState: LocalVideoThumbnailState = {
31
+ postId: null,
32
+ videos: [],
33
+ };
34
+
35
+ const localVideoThumbnailSlice = createSlice({
36
+ name: 'localVideoThumbnail',
37
+ initialState,
38
+ reducers: {
39
+ setLocalVideoThumbnails: (
40
+ state,
41
+ action: PayloadAction<{
42
+ postId: string;
43
+ videos: LocalVideoThumbnail[];
44
+ }>
45
+ ) => {
46
+ state.postId = action.payload.postId;
47
+ state.videos = action.payload.videos.filter(
48
+ ({ fileId, thumbnailUrl }) => !!fileId && !!thumbnailUrl
49
+ );
50
+ },
51
+ clearLocalVideoThumbnails: (state) => {
52
+ state.postId = null;
53
+ state.videos = [];
54
+ },
55
+ },
56
+ });
57
+
58
+ export default localVideoThumbnailSlice;
@@ -20,6 +20,10 @@ interface UIState {
20
20
  toastMessage?: string;
21
21
  isLoadingToast?: boolean;
22
22
  isSuccessToast?: boolean;
23
+ /** Bumped once per feed reload. Feed items are keyed by post id, so a reload
24
+ * reuses their component instances and any position they were holding
25
+ * survives it; anything that must start over on a reload watches this. */
26
+ feedReloadToken: number;
23
27
  }
24
28
  const initialState: UIState = {
25
29
  showPostTypeChoiceModal: false,
@@ -30,6 +34,7 @@ const initialState: UIState = {
30
34
  postSetting: null,
31
35
  needApprovalOnPostCreation: true,
32
36
  isPublic: false,
37
+ feedReloadToken: 0,
33
38
  };
34
39
 
35
40
  const uiSlice = createSlice({
@@ -87,6 +92,9 @@ const uiSlice = createSlice({
87
92
  state.isLoadingToast = action.payload.isLoadingToast;
88
93
  state.isSuccessToast = action.payload.isSuccessToast;
89
94
  },
95
+ markFeedReloaded: (state) => {
96
+ state.feedReloadToken += 1;
97
+ },
90
98
  hideToastMessage: (state) => {
91
99
  state.showToastMessage = false;
92
100
  state.toastMessage = '';
@@ -5,6 +5,7 @@ import postDetailSlice from '../slices/postDetailSlice';
5
5
  import uiSlice from '../slices/uiSlice';
6
6
  import bottomSheetSlice from '../slices/bottomSheetSlice';
7
7
  import toastSlice from '../slices/toastSlice';
8
+ import localVideoThumbnailSlice from '../slices/localVideoThumbnailSlice';
8
9
  import { createContext } from 'react';
9
10
  import {
10
11
  createStoreHook,
@@ -34,6 +35,7 @@ export const store: Store = configureStore({
34
35
  ui: uiSlice.reducer,
35
36
  bottomSheet: bottomSheetSlice.reducer,
36
37
  toast: toastSlice.reducer,
38
+ localVideoThumbnail: localVideoThumbnailSlice.reducer,
37
39
  },
38
40
  middleware: (getDefaultMiddleware) =>
39
41
  getDefaultMiddleware({