@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.
- package/README.md +7 -0
- package/lib/commonjs/core/assets/icons/post.js +1 -1
- package/lib/commonjs/core/assets/icons/post.js.map +1 -1
- package/lib/commonjs/core/assets/icons/story.js +1 -1
- package/lib/commonjs/core/assets/icons/story.js.map +1 -1
- package/lib/commonjs/core/assets/icons/xml.js +16 -2
- package/lib/commonjs/core/assets/icons/xml.js.map +1 -1
- package/lib/commonjs/core/legacy/file.js +145 -62
- package/lib/commonjs/core/legacy/file.js.map +1 -1
- package/lib/commonjs/core/stores/slices/feedSlice.js +3 -0
- package/lib/commonjs/core/stores/slices/feedSlice.js.map +1 -1
- package/lib/commonjs/core/stores/slices/globalfeedSlice.js +5 -0
- package/lib/commonjs/core/stores/slices/globalfeedSlice.js.map +1 -1
- package/lib/commonjs/core/stores/slices/localVideoThumbnailSlice.js +46 -0
- package/lib/commonjs/core/stores/slices/localVideoThumbnailSlice.js.map +1 -0
- package/lib/commonjs/core/stores/slices/uiSlice.js +5 -1
- package/lib/commonjs/core/stores/slices/uiSlice.js.map +1 -1
- package/lib/commonjs/core/stores/store/index.js +3 -1
- package/lib/commonjs/core/stores/store/index.js.map +1 -1
- package/lib/commonjs/social/components/LoadingImage/index.js +73 -12
- package/lib/commonjs/social/components/LoadingImage/index.js.map +1 -1
- package/lib/commonjs/social/components/LoadingImage/styles.js +8 -3
- package/lib/commonjs/social/components/LoadingImage/styles.js.map +1 -1
- package/lib/commonjs/social/components/LoadingVideo/index.js +90 -16
- package/lib/commonjs/social/components/LoadingVideo/index.js.map +1 -1
- package/lib/commonjs/social/components/LoadingVideo/styles.js +14 -5
- package/lib/commonjs/social/components/LoadingVideo/styles.js.map +1 -1
- package/lib/commonjs/social/components/MediaViewer/MediaViewer.js +13 -13
- package/lib/commonjs/social/components/MediaViewer/MediaViewer.js.map +1 -1
- package/lib/commonjs/social/components/MediaViewer/styles.js +11 -1
- package/lib/commonjs/social/components/MediaViewer/styles.js.map +1 -1
- package/lib/commonjs/social/components/PostContent/index.js +8 -29
- package/lib/commonjs/social/components/PostContent/index.js.map +1 -1
- package/lib/commonjs/social/components/SelectedMediaComponent/SelectedMediaComponent.js +109 -17
- package/lib/commonjs/social/components/SelectedMediaComponent/SelectedMediaComponent.js.map +1 -1
- package/lib/commonjs/social/components/legacy/LoadingVideo/index.js +17 -6
- package/lib/commonjs/social/components/legacy/LoadingVideo/index.js.map +1 -1
- package/lib/commonjs/social/elements/PostMediaElement/PostMediaElement.js +121 -23
- package/lib/commonjs/social/elements/PostMediaElement/PostMediaElement.js.map +1 -1
- package/lib/commonjs/social/elements/PostMediaElement/styles.js +7 -0
- package/lib/commonjs/social/elements/PostMediaElement/styles.js.map +1 -1
- package/lib/commonjs/social/features/community/PendingRequest/components/PendingPostList/PendingPostList.js +11 -5
- package/lib/commonjs/social/features/community/PendingRequest/components/PendingPostList/PendingPostList.js.map +1 -1
- package/lib/commonjs/social/features/feed/components/NewsFeed/NewsFeed.js +14 -0
- package/lib/commonjs/social/features/feed/components/NewsFeed/NewsFeed.js.map +1 -1
- package/lib/commonjs/social/features/post/Composer/PostComposer.js +234 -86
- package/lib/commonjs/social/features/post/Composer/PostComposer.js.map +1 -1
- package/lib/commonjs/social/hooks/index.js +11 -0
- package/lib/commonjs/social/hooks/index.js.map +1 -1
- package/lib/commonjs/social/hooks/useCommunities.js +5 -3
- package/lib/commonjs/social/hooks/useCommunities.js.map +1 -1
- package/lib/commonjs/social/hooks/useCommunityPermission.js +43 -0
- package/lib/commonjs/social/hooks/useCommunityPermission.js.map +1 -0
- package/lib/commonjs/social/hooks/useCustomRankingGlobalFeed.js +10 -1
- package/lib/commonjs/social/hooks/useCustomRankingGlobalFeed.js.map +1 -1
- package/lib/commonjs/social/hooks/useFile.js +11 -0
- package/lib/commonjs/social/hooks/useFile.js.map +1 -1
- package/lib/commonjs/social/screens/CommunityProfile/components/Header/Header.js +18 -7
- package/lib/commonjs/social/screens/CommunityProfile/components/Header/Header.js.map +1 -1
- package/lib/commonjs/social/utils/mediaAttachments.js +150 -0
- package/lib/commonjs/social/utils/mediaAttachments.js.map +1 -0
- package/lib/commonjs/social/utils/postChildren.js +72 -0
- package/lib/commonjs/social/utils/postChildren.js.map +1 -0
- package/lib/module/core/assets/icons/post.js +1 -1
- package/lib/module/core/assets/icons/post.js.map +1 -1
- package/lib/module/core/assets/icons/story.js +1 -1
- package/lib/module/core/assets/icons/story.js.map +1 -1
- package/lib/module/core/assets/icons/xml.js +13 -0
- package/lib/module/core/assets/icons/xml.js.map +1 -1
- package/lib/module/core/legacy/file.js +145 -62
- package/lib/module/core/legacy/file.js.map +1 -1
- package/lib/module/core/stores/slices/feedSlice.js +3 -0
- package/lib/module/core/stores/slices/feedSlice.js.map +1 -1
- package/lib/module/core/stores/slices/globalfeedSlice.js +5 -0
- package/lib/module/core/stores/slices/globalfeedSlice.js.map +1 -1
- package/lib/module/core/stores/slices/localVideoThumbnailSlice.js +41 -0
- package/lib/module/core/stores/slices/localVideoThumbnailSlice.js.map +1 -0
- package/lib/module/core/stores/slices/uiSlice.js +5 -1
- package/lib/module/core/stores/slices/uiSlice.js.map +1 -1
- package/lib/module/core/stores/store/index.js +3 -1
- package/lib/module/core/stores/store/index.js.map +1 -1
- package/lib/module/social/components/LoadingImage/index.js +74 -14
- package/lib/module/social/components/LoadingImage/index.js.map +1 -1
- package/lib/module/social/components/LoadingImage/styles.js +8 -3
- package/lib/module/social/components/LoadingImage/styles.js.map +1 -1
- package/lib/module/social/components/LoadingVideo/index.js +91 -17
- package/lib/module/social/components/LoadingVideo/index.js.map +1 -1
- package/lib/module/social/components/LoadingVideo/styles.js +14 -5
- package/lib/module/social/components/LoadingVideo/styles.js.map +1 -1
- package/lib/module/social/components/MediaViewer/MediaViewer.js +13 -13
- package/lib/module/social/components/MediaViewer/MediaViewer.js.map +1 -1
- package/lib/module/social/components/MediaViewer/styles.js +11 -1
- package/lib/module/social/components/MediaViewer/styles.js.map +1 -1
- package/lib/module/social/components/PostContent/index.js +8 -29
- package/lib/module/social/components/PostContent/index.js.map +1 -1
- package/lib/module/social/components/SelectedMediaComponent/SelectedMediaComponent.js +110 -18
- package/lib/module/social/components/SelectedMediaComponent/SelectedMediaComponent.js.map +1 -1
- package/lib/module/social/components/legacy/LoadingVideo/index.js +17 -6
- package/lib/module/social/components/legacy/LoadingVideo/index.js.map +1 -1
- package/lib/module/social/elements/PostMediaElement/PostMediaElement.js +123 -25
- package/lib/module/social/elements/PostMediaElement/PostMediaElement.js.map +1 -1
- package/lib/module/social/elements/PostMediaElement/styles.js +7 -0
- package/lib/module/social/elements/PostMediaElement/styles.js.map +1 -1
- package/lib/module/social/features/community/PendingRequest/components/PendingPostList/PendingPostList.js +12 -6
- package/lib/module/social/features/community/PendingRequest/components/PendingPostList/PendingPostList.js.map +1 -1
- package/lib/module/social/features/feed/components/NewsFeed/NewsFeed.js +15 -1
- package/lib/module/social/features/feed/components/NewsFeed/NewsFeed.js.map +1 -1
- package/lib/module/social/features/post/Composer/PostComposer.js +236 -89
- package/lib/module/social/features/post/Composer/PostComposer.js.map +1 -1
- package/lib/module/social/hooks/index.js +1 -0
- package/lib/module/social/hooks/index.js.map +1 -1
- package/lib/module/social/hooks/useCommunities.js +5 -3
- package/lib/module/social/hooks/useCommunities.js.map +1 -1
- package/lib/module/social/hooks/useCommunityPermission.js +37 -0
- package/lib/module/social/hooks/useCommunityPermission.js.map +1 -0
- package/lib/module/social/hooks/useCustomRankingGlobalFeed.js +10 -1
- package/lib/module/social/hooks/useCustomRankingGlobalFeed.js.map +1 -1
- package/lib/module/social/hooks/useFile.js +11 -0
- package/lib/module/social/hooks/useFile.js.map +1 -1
- package/lib/module/social/screens/CommunityProfile/components/Header/Header.js +19 -8
- package/lib/module/social/screens/CommunityProfile/components/Header/Header.js.map +1 -1
- package/lib/module/social/utils/mediaAttachments.js +139 -0
- package/lib/module/social/utils/mediaAttachments.js.map +1 -0
- package/lib/module/social/utils/postChildren.js +66 -0
- package/lib/module/social/utils/postChildren.js.map +1 -0
- package/lib/typescript/core/assets/icons/xml.d.ts +1 -0
- package/lib/typescript/core/assets/icons/xml.d.ts.map +1 -1
- package/lib/typescript/core/legacy/file.d.ts +32 -0
- package/lib/typescript/core/legacy/file.d.ts.map +1 -1
- package/lib/typescript/core/stores/slices/feedSlice.d.ts.map +1 -1
- package/lib/typescript/core/stores/slices/globalfeedSlice.d.ts.map +1 -1
- package/lib/typescript/core/stores/slices/localVideoThumbnailSlice.d.ts +35 -0
- package/lib/typescript/core/stores/slices/localVideoThumbnailSlice.d.ts.map +1 -0
- package/lib/typescript/core/stores/slices/uiSlice.d.ts +5 -0
- package/lib/typescript/core/stores/slices/uiSlice.d.ts.map +1 -1
- package/lib/typescript/core/stores/store/index.d.ts.map +1 -1
- package/lib/typescript/core/types/mention.d.ts +34 -0
- package/lib/typescript/core/types/mention.d.ts.map +1 -1
- package/lib/typescript/social/components/LoadingImage/index.d.ts +8 -2
- package/lib/typescript/social/components/LoadingImage/index.d.ts.map +1 -1
- package/lib/typescript/social/components/LoadingImage/styles.d.ts.map +1 -1
- package/lib/typescript/social/components/LoadingVideo/index.d.ts +9 -2
- package/lib/typescript/social/components/LoadingVideo/index.d.ts.map +1 -1
- package/lib/typescript/social/components/LoadingVideo/styles.d.ts +1 -0
- package/lib/typescript/social/components/LoadingVideo/styles.d.ts.map +1 -1
- package/lib/typescript/social/components/MediaViewer/MediaViewer.d.ts.map +1 -1
- package/lib/typescript/social/components/MediaViewer/styles.d.ts +1 -0
- package/lib/typescript/social/components/MediaViewer/styles.d.ts.map +1 -1
- package/lib/typescript/social/components/PostContent/index.d.ts.map +1 -1
- package/lib/typescript/social/components/SelectedMediaComponent/SelectedMediaComponent.d.ts +2 -2
- package/lib/typescript/social/components/SelectedMediaComponent/SelectedMediaComponent.d.ts.map +1 -1
- package/lib/typescript/social/components/legacy/LoadingVideo/index.d.ts.map +1 -1
- package/lib/typescript/social/elements/PostMediaElement/PostMediaElement.d.ts.map +1 -1
- package/lib/typescript/social/elements/PostMediaElement/styles.d.ts +4 -0
- package/lib/typescript/social/elements/PostMediaElement/styles.d.ts.map +1 -1
- package/lib/typescript/social/features/community/PendingRequest/components/PendingPostList/PendingPostList.d.ts.map +1 -1
- package/lib/typescript/social/features/feed/components/NewsFeed/NewsFeed.d.ts.map +1 -1
- package/lib/typescript/social/features/post/Composer/PostComposer.d.ts.map +1 -1
- package/lib/typescript/social/hooks/index.d.ts +1 -0
- package/lib/typescript/social/hooks/index.d.ts.map +1 -1
- package/lib/typescript/social/hooks/useCommunities.d.ts +2 -1
- package/lib/typescript/social/hooks/useCommunities.d.ts.map +1 -1
- package/lib/typescript/social/hooks/useCommunityPermission.d.ts +14 -0
- package/lib/typescript/social/hooks/useCommunityPermission.d.ts.map +1 -0
- package/lib/typescript/social/hooks/useCustomRankingGlobalFeed.d.ts.map +1 -1
- package/lib/typescript/social/hooks/useFile.d.ts +11 -0
- package/lib/typescript/social/hooks/useFile.d.ts.map +1 -1
- package/lib/typescript/social/screens/CommunityProfile/components/Header/Header.d.ts.map +1 -1
- package/lib/typescript/social/utils/mediaAttachments.d.ts +91 -0
- package/lib/typescript/social/utils/mediaAttachments.d.ts.map +1 -0
- package/lib/typescript/social/utils/postChildren.d.ts +38 -0
- package/lib/typescript/social/utils/postChildren.d.ts.map +1 -0
- package/package.json +3 -3
- package/src/core/assets/icons/post.tsx +1 -1
- package/src/core/assets/icons/story.tsx +1 -1
- package/src/core/assets/icons/xml.ts +15 -0
- package/src/core/legacy/file.tsx +169 -84
- package/src/core/stores/slices/feedSlice.ts +3 -0
- package/src/core/stores/slices/globalfeedSlice.ts +5 -0
- package/src/core/stores/slices/localVideoThumbnailSlice.ts +58 -0
- package/src/core/stores/slices/uiSlice.ts +8 -0
- package/src/core/stores/store/index.ts +2 -0
- package/src/core/types/mention.ts +34 -0
- package/src/social/components/LoadingImage/index.tsx +81 -12
- package/src/social/components/LoadingImage/styles.ts +8 -3
- package/src/social/components/LoadingVideo/index.tsx +111 -21
- package/src/social/components/LoadingVideo/styles.ts +13 -4
- package/src/social/components/MediaViewer/MediaViewer.tsx +11 -12
- package/src/social/components/MediaViewer/styles.ts +9 -1
- package/src/social/components/PostContent/index.tsx +3 -35
- package/src/social/components/SelectedMediaComponent/SelectedMediaComponent.tsx +103 -13
- package/src/social/components/legacy/LoadingVideo/index.tsx +26 -15
- package/src/social/elements/PostMediaElement/PostMediaElement.tsx +139 -29
- package/src/social/elements/PostMediaElement/styles.ts +7 -0
- package/src/social/features/community/PendingRequest/components/PendingPostList/PendingPostList.tsx +11 -6
- package/src/social/features/feed/components/NewsFeed/NewsFeed.tsx +16 -1
- package/src/social/features/post/Composer/PostComposer.tsx +304 -91
- package/src/social/hooks/index.ts +1 -0
- package/src/social/hooks/useCommunities.ts +4 -2
- package/src/social/hooks/useCommunityPermission.ts +40 -0
- package/src/social/hooks/useCustomRankingGlobalFeed.ts +8 -1
- package/src/social/hooks/useFile.ts +11 -0
- package/src/social/screens/CommunityProfile/components/Header/Header.tsx +19 -10
- package/src/social/utils/mediaAttachments.ts +158 -0
- package/src/social/utils/postChildren.ts +85 -0
|
@@ -6,21 +6,26 @@ import {
|
|
|
6
6
|
ScrollView,
|
|
7
7
|
View,
|
|
8
8
|
} from 'react-native';
|
|
9
|
+
import { createVideoThumbnail } from 'react-native-compressor';
|
|
9
10
|
import LoadingImage from '../LoadingImage';
|
|
10
11
|
import LoadingVideo from '../LoadingVideo';
|
|
11
12
|
import { MediaViewer, type MediaViewerItem } from '../MediaViewer';
|
|
12
13
|
import { IDisplayImage } from '../../../core/types';
|
|
13
14
|
import {
|
|
14
15
|
getFrameRatio,
|
|
16
|
+
getVideoDisplayDims,
|
|
15
17
|
FRAME_RATIO_VALUE,
|
|
16
18
|
DEFAULT_FRAME_RATIO,
|
|
17
19
|
type FrameRatio,
|
|
18
20
|
} from '../../utils/getFrameRatio';
|
|
19
21
|
import { useStyles } from './styles';
|
|
20
22
|
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
|
|
23
|
+
// Peek scroll: next slide peeks by (PEEK_INSET − SPACE_BETWEEN). The composer
|
|
24
|
+
// body (PostComposer `inputWrapper`) already insets everything 12px
|
|
25
|
+
// horizontally, so this component adds NO side padding of its own — it only
|
|
26
|
+
// sizes the slide so the peek lands at the spec's 31px (SelectedMediaComponent
|
|
27
|
+
// REQ-003a). slideWidth = trackWidth − PEEK_INSET; peek = PEEK_INSET − 8 = 31.
|
|
28
|
+
const PEEK_INSET = 39;
|
|
24
29
|
const SPACE_BETWEEN = 8;
|
|
25
30
|
|
|
26
31
|
export type SelectedMediaComponentProps = {
|
|
@@ -37,7 +42,10 @@ export type SelectedMediaComponentProps = {
|
|
|
37
42
|
) => void;
|
|
38
43
|
onUploadError?: (hasError: boolean, source: string) => void;
|
|
39
44
|
isEditMode?: boolean;
|
|
40
|
-
|
|
45
|
+
// Per-frame upload state, keyed by the item's `source` url — the parent
|
|
46
|
+
// aggregates it instead of holding one boolean that the first upload to
|
|
47
|
+
// finish would clear on behalf of every other frame.
|
|
48
|
+
onUploadingChange?: (isUploading: boolean, source: string) => void;
|
|
41
49
|
};
|
|
42
50
|
|
|
43
51
|
export function SelectedMediaComponent({
|
|
@@ -47,7 +55,7 @@ export function SelectedMediaComponent({
|
|
|
47
55
|
onLoadFinish,
|
|
48
56
|
onUploadError,
|
|
49
57
|
isEditMode = false,
|
|
50
|
-
|
|
58
|
+
onUploadingChange,
|
|
51
59
|
}: SelectedMediaComponentProps) {
|
|
52
60
|
const styles = useStyles();
|
|
53
61
|
const scrollRef = useRef<ScrollView>(null);
|
|
@@ -61,14 +69,62 @@ export function SelectedMediaComponent({
|
|
|
61
69
|
const aspectRatio = FRAME_RATIO_VALUE[ratio];
|
|
62
70
|
const slideWidth = trackWidth > 0 ? trackWidth - PEEK_INSET : 0;
|
|
63
71
|
|
|
64
|
-
// Frames follow the aspect ratio
|
|
65
|
-
// the first item changes
|
|
66
|
-
const
|
|
72
|
+
// Frames follow the aspect ratio classified from the item currently in first
|
|
73
|
+
// position (REQ-003d/003e); recalculated when the first item changes.
|
|
74
|
+
const first = media[0];
|
|
75
|
+
const firstUrl = first?.url;
|
|
76
|
+
const firstWidth = first?.width;
|
|
77
|
+
const firstHeight = first?.height;
|
|
78
|
+
const firstRotation = first?.rotation;
|
|
67
79
|
useEffect(() => {
|
|
68
|
-
if (
|
|
80
|
+
if (!firstUrl) {
|
|
69
81
|
setRatio(DEFAULT_FRAME_RATIO);
|
|
70
82
|
return undefined;
|
|
71
83
|
}
|
|
84
|
+
// Video: classify from the record/extractor dims, applying rotation
|
|
85
|
+
// (REQ-003d1 — MUST NOT silently fall back to 1:1 for video). Shares the
|
|
86
|
+
// one rotation derivation with the published carousel (REQ-SDK-002b).
|
|
87
|
+
if (mediaType === 'video') {
|
|
88
|
+
if (firstWidth && firstHeight) {
|
|
89
|
+
const { width, height } = getVideoDisplayDims({
|
|
90
|
+
width: firstWidth,
|
|
91
|
+
height: firstHeight,
|
|
92
|
+
rotation: firstRotation,
|
|
93
|
+
});
|
|
94
|
+
setRatio(getFrameRatio(width, height));
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
// No usable dims — a locally-picked video can arrive with none (the
|
|
98
|
+
// Android extractor reports 0×0 when MediaMetadataRetriever fails, and
|
|
99
|
+
// iOS picks carry none by design, see PostComposer). Measure the
|
|
100
|
+
// generated thumbnail instead of defaulting to a square frame
|
|
101
|
+
// (REQ-003d2, PDT-4904): it is a rendered frame, so its dimensions are
|
|
102
|
+
// already display-oriented and need no rotation applied.
|
|
103
|
+
//
|
|
104
|
+
// LoadingVideo decodes a thumbnail for this same url too, so frame 0
|
|
105
|
+
// decodes twice. Kept deliberately: reporting dims back up through
|
|
106
|
+
// LoadingVideo would widen the composer's callback surface, and the
|
|
107
|
+
// decode is a cached native call on a url the child already proves
|
|
108
|
+
// works.
|
|
109
|
+
let active = true;
|
|
110
|
+
createVideoThumbnail(firstUrl)
|
|
111
|
+
.then(({ width, height }) => {
|
|
112
|
+
if (active) setRatio(getFrameRatio(width, height));
|
|
113
|
+
})
|
|
114
|
+
.catch(() => {
|
|
115
|
+
// Thumbnail decode failed too — nothing left to classify from, so
|
|
116
|
+
// fall back to the square frame (REQ-003d2).
|
|
117
|
+
if (active) setRatio(DEFAULT_FRAME_RATIO);
|
|
118
|
+
});
|
|
119
|
+
return () => {
|
|
120
|
+
active = false;
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
// Image: prefer carried dims, else measure the source.
|
|
124
|
+
if (firstWidth && firstHeight) {
|
|
125
|
+
setRatio(getFrameRatio(firstWidth, firstHeight));
|
|
126
|
+
return undefined;
|
|
127
|
+
}
|
|
72
128
|
let active = true;
|
|
73
129
|
Image.getSize(
|
|
74
130
|
firstUrl,
|
|
@@ -82,7 +138,7 @@ export function SelectedMediaComponent({
|
|
|
82
138
|
return () => {
|
|
83
139
|
active = false;
|
|
84
140
|
};
|
|
85
|
-
}, [firstUrl, mediaType]);
|
|
141
|
+
}, [firstUrl, firstWidth, firstHeight, firstRotation, mediaType]);
|
|
86
142
|
|
|
87
143
|
const onLayout = (event: LayoutChangeEvent) => {
|
|
88
144
|
const width = event.nativeEvent.layout.width;
|
|
@@ -94,6 +150,24 @@ export function SelectedMediaComponent({
|
|
|
94
150
|
uri: m.url,
|
|
95
151
|
}));
|
|
96
152
|
|
|
153
|
+
// Removing a frame must not leave the strip parked past the last one. The
|
|
154
|
+
// ScrollView keeps its offset while the content shrinks underneath it, so
|
|
155
|
+
// deleting the frame that was on screen left a half-scrolled gap where it had
|
|
156
|
+
// been instead of settling on the frame that took its place.
|
|
157
|
+
const offsetRef = useRef(0);
|
|
158
|
+
const previousTotalRef = useRef(total);
|
|
159
|
+
useEffect(() => {
|
|
160
|
+
const previousTotal = previousTotalRef.current;
|
|
161
|
+
previousTotalRef.current = total;
|
|
162
|
+
if (total >= previousTotal || slideWidth <= 0) return;
|
|
163
|
+
|
|
164
|
+
const maxOffset = Math.max(0, (total - 1) * (slideWidth + SPACE_BETWEEN));
|
|
165
|
+
if (offsetRef.current <= maxOffset) return;
|
|
166
|
+
|
|
167
|
+
offsetRef.current = maxOffset;
|
|
168
|
+
scrollRef.current?.scrollTo({ x: maxOffset, animated: false });
|
|
169
|
+
}, [total, slideWidth]);
|
|
170
|
+
|
|
97
171
|
// PDT-4310/4312: on close, return to the frame originally tapped from — not
|
|
98
172
|
// the last frame viewed in full screen.
|
|
99
173
|
const handleCloseViewer = () => {
|
|
@@ -113,6 +187,7 @@ export function SelectedMediaComponent({
|
|
|
113
187
|
<LoadingVideo
|
|
114
188
|
carousel
|
|
115
189
|
source={item.url}
|
|
190
|
+
displayUri={item.localUrl}
|
|
116
191
|
onClose={onClose}
|
|
117
192
|
index={index}
|
|
118
193
|
onLoadFinish={onLoadFinish}
|
|
@@ -123,7 +198,7 @@ export function SelectedMediaComponent({
|
|
|
123
198
|
fileCount={total}
|
|
124
199
|
isEditMode={isEditMode}
|
|
125
200
|
postId={item.postId}
|
|
126
|
-
|
|
201
|
+
onUploadingChange={onUploadingChange}
|
|
127
202
|
onPlay={() => setViewerIndex(index)}
|
|
128
203
|
/>
|
|
129
204
|
) : (
|
|
@@ -134,6 +209,7 @@ export function SelectedMediaComponent({
|
|
|
134
209
|
<LoadingImage
|
|
135
210
|
carousel
|
|
136
211
|
source={item.url}
|
|
212
|
+
displayUri={item.localUrl}
|
|
137
213
|
onClose={onClose}
|
|
138
214
|
index={index}
|
|
139
215
|
onLoadFinish={onLoadFinish}
|
|
@@ -143,7 +219,7 @@ export function SelectedMediaComponent({
|
|
|
143
219
|
fileCount={total}
|
|
144
220
|
isEditMode={isEditMode}
|
|
145
221
|
postId={item.postId}
|
|
146
|
-
|
|
222
|
+
onUploadingChange={onUploadingChange}
|
|
147
223
|
/>
|
|
148
224
|
</Pressable>
|
|
149
225
|
)}
|
|
@@ -171,10 +247,24 @@ export function SelectedMediaComponent({
|
|
|
171
247
|
decelerationRate="fast"
|
|
172
248
|
snapToInterval={slideWidth + SPACE_BETWEEN}
|
|
173
249
|
snapToAlignment="start"
|
|
250
|
+
scrollEventThrottle={16}
|
|
251
|
+
onScroll={(event) => {
|
|
252
|
+
offsetRef.current = event.nativeEvent.contentOffset.x;
|
|
253
|
+
}}
|
|
174
254
|
>
|
|
175
255
|
{media.map((item, index) => (
|
|
176
256
|
<View
|
|
177
|
-
|
|
257
|
+
// Key on the item's stable local identity, not on its
|
|
258
|
+
// url. `url` is rewritten from the local pick path
|
|
259
|
+
// to the remote `?size=medium` one the instant that frame's
|
|
260
|
+
// upload finishes, so a url-based key tore down and
|
|
261
|
+
// remounted the slide — and with it the child's mount
|
|
262
|
+
// effect, which is what re-runs uploads and re-orders the
|
|
263
|
+
// in-flight completion callbacks against their siblings.
|
|
264
|
+
// The identity survives the swap, so a finishing upload
|
|
265
|
+
// now only re-renders its own frame. Falls back to the old
|
|
266
|
+
// key for entries built without one (legacy EditPostModal).
|
|
267
|
+
key={item.localId ?? `${item.url}-${index}`}
|
|
178
268
|
style={[
|
|
179
269
|
styles.selectedMedia__slide,
|
|
180
270
|
{
|
|
@@ -89,24 +89,35 @@ const LoadingVideo = ({
|
|
|
89
89
|
}, [progress]);
|
|
90
90
|
|
|
91
91
|
const uploadFileToAmity = useCallback(async () => {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
92
|
+
// `uploadVideoFile` now rejects on a failed upload instead of
|
|
93
|
+
// hanging on an unsettled promise, so this call needs a catch. This legacy
|
|
94
|
+
// component has no error UI to show (unlike its v4 counterpart, which
|
|
95
|
+
// renders a tappable retry overlay) — the least-bad end state is to stop
|
|
96
|
+
// the spinner so the frame is not stuck pretending to make progress, and
|
|
97
|
+
// to leave the close button reachable so the user can drop the attachment.
|
|
98
|
+
try {
|
|
99
|
+
const file: Amity.File<any>[] = await uploadVideoFile(
|
|
100
|
+
source,
|
|
101
|
+
(percent: number) => {
|
|
102
|
+
setProgress(percent);
|
|
103
|
+
}
|
|
104
|
+
);
|
|
105
|
+
if (file) {
|
|
106
|
+
setIsProcess(false);
|
|
107
|
+
handleLoadEnd();
|
|
108
|
+
onLoadFinish &&
|
|
109
|
+
onLoadFinish(
|
|
110
|
+
file[0]?.fileId as string,
|
|
111
|
+
file[0]?.fileUrl as string,
|
|
112
|
+
file[0]?.attributes.name as string,
|
|
113
|
+
index as number,
|
|
114
|
+
source,
|
|
115
|
+
thumbNail
|
|
116
|
+
);
|
|
96
117
|
}
|
|
97
|
-
)
|
|
98
|
-
if (file) {
|
|
118
|
+
} catch (error) {
|
|
99
119
|
setIsProcess(false);
|
|
100
120
|
handleLoadEnd();
|
|
101
|
-
onLoadFinish &&
|
|
102
|
-
onLoadFinish(
|
|
103
|
-
file[0]?.fileId as string,
|
|
104
|
-
file[0]?.fileUrl as string,
|
|
105
|
-
file[0]?.attributes.name as string,
|
|
106
|
-
index as number,
|
|
107
|
-
source,
|
|
108
|
-
thumbNail
|
|
109
|
-
);
|
|
110
121
|
}
|
|
111
122
|
}, [source]);
|
|
112
123
|
|
|
@@ -1,5 +1,12 @@
|
|
|
1
|
-
import { useMemo, useRef, useState, type MutableRefObject } from 'react';
|
|
2
1
|
import {
|
|
2
|
+
useEffect,
|
|
3
|
+
useMemo,
|
|
4
|
+
useRef,
|
|
5
|
+
useState,
|
|
6
|
+
type MutableRefObject,
|
|
7
|
+
} from 'react';
|
|
8
|
+
import {
|
|
9
|
+
Animated,
|
|
3
10
|
Image,
|
|
4
11
|
ImageStyle,
|
|
5
12
|
LayoutChangeEvent,
|
|
@@ -12,6 +19,7 @@ import {
|
|
|
12
19
|
View,
|
|
13
20
|
} from 'react-native';
|
|
14
21
|
import { SvgXml } from 'react-native-svg';
|
|
22
|
+
import { FileRepository } from '@amityco/ts-sdk-react-native';
|
|
15
23
|
import { Typography } from '../../../core/components/Typography/Typography';
|
|
16
24
|
import useAuth from '../../../core/hooks/useAuth';
|
|
17
25
|
import {
|
|
@@ -26,6 +34,9 @@ import {
|
|
|
26
34
|
type FrameRatio,
|
|
27
35
|
} from '../../utils/getFrameRatio';
|
|
28
36
|
import { getIndicatorConfig } from '../../utils/getIndicatorConfig';
|
|
37
|
+
import { ImageSizeState } from '../../enums/imageSizeState';
|
|
38
|
+
import { useUIKitSelector } from '../../../core/stores/store';
|
|
39
|
+
import type { RootState } from '../../../core/stores/store';
|
|
29
40
|
import { useStyles } from './styles';
|
|
30
41
|
|
|
31
42
|
type MediaPost = Amity.Post<'image'> | Amity.Post<'video'>;
|
|
@@ -81,6 +92,34 @@ export function PostMediaElement({
|
|
|
81
92
|
const mediaPosts = useMemo(() => (posts ?? []).filter(isMediaPost), [posts]);
|
|
82
93
|
const ratio = useMemo(() => getAttachmentRatio(mediaPosts[0]), [mediaPosts]);
|
|
83
94
|
|
|
95
|
+
// A reload starts the carousel over. It cannot be left to unmounting: the
|
|
96
|
+
// feed keys its items by post id, so a reload reuses this instance and both
|
|
97
|
+
// `current` and the ScrollView's own offset would otherwise survive it,
|
|
98
|
+
// leaving the viewer on whichever frame they had reached before.
|
|
99
|
+
const feedReloadToken = useUIKitSelector(
|
|
100
|
+
(state: RootState) => state.ui.feedReloadToken
|
|
101
|
+
);
|
|
102
|
+
useEffect(() => {
|
|
103
|
+
setCurrent(0);
|
|
104
|
+
scrollRef.current?.scrollTo({ x: 0, animated: false });
|
|
105
|
+
}, [feedReloadToken]);
|
|
106
|
+
|
|
107
|
+
// An edit that removes frames must not leave the carousel pointing past the
|
|
108
|
+
// end of what is left. The feed reuses this instance across the edit, so both
|
|
109
|
+
// `current` and the ScrollView's own offset outlive the frame they referred
|
|
110
|
+
// to: the counter read "4/3" over an empty frame, and the indicator drew no
|
|
111
|
+
// active dot at all because no page matched the index.
|
|
112
|
+
const frameCount = mediaPosts.length;
|
|
113
|
+
useEffect(() => {
|
|
114
|
+
const lastIndex = Math.max(0, frameCount - 1);
|
|
115
|
+
if (current <= lastIndex) return;
|
|
116
|
+
setCurrent(lastIndex);
|
|
117
|
+
scrollRef.current?.scrollTo({
|
|
118
|
+
x: lastIndex * (trackWidth || screenWidth),
|
|
119
|
+
animated: false,
|
|
120
|
+
});
|
|
121
|
+
}, [frameCount, current, trackWidth, screenWidth]);
|
|
122
|
+
|
|
84
123
|
if (mediaPosts.length === 0) {
|
|
85
124
|
if (!loading) return null;
|
|
86
125
|
// Loading placeholder: a full-bleed frame in the base-shade4 colour, so the
|
|
@@ -197,20 +236,57 @@ function PaginationIndicator({ current, total }: PaginationIndicatorProps) {
|
|
|
197
236
|
|
|
198
237
|
return (
|
|
199
238
|
<View style={styles.postMedia__indicator}>
|
|
200
|
-
{dots.map((dot) =>
|
|
201
|
-
|
|
202
|
-
<
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
239
|
+
{dots.map((dot) =>
|
|
240
|
+
dot.state === 'edge' ? (
|
|
241
|
+
<EdgeDot key={dot.key} side={dot.side} />
|
|
242
|
+
) : (
|
|
243
|
+
<View key={dot.key} style={styles.postMedia__dotSlot}>
|
|
244
|
+
<View
|
|
245
|
+
style={[
|
|
246
|
+
styles.postMedia__dot,
|
|
247
|
+
dot.state === 'active'
|
|
248
|
+
? styles.postMedia__dotActive
|
|
249
|
+
: styles.postMedia__dotInactive,
|
|
250
|
+
]}
|
|
251
|
+
/>
|
|
252
|
+
</View>
|
|
253
|
+
)
|
|
254
|
+
)}
|
|
255
|
+
</View>
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// The strip keeps a constant width (fixed-width slots, always 6 elements), so
|
|
260
|
+
// an edge dot only needs to slide in from its own side over 300ms when it
|
|
261
|
+
// first appears (REQ-013b).
|
|
262
|
+
function EdgeDot({ side }: { side?: 'left' | 'right' }) {
|
|
263
|
+
const styles = useStyles();
|
|
264
|
+
const anim = useRef(new Animated.Value(0)).current;
|
|
265
|
+
|
|
266
|
+
useEffect(() => {
|
|
267
|
+
const animation = Animated.timing(anim, {
|
|
268
|
+
toValue: 1,
|
|
269
|
+
duration: 300,
|
|
270
|
+
useNativeDriver: true,
|
|
271
|
+
});
|
|
272
|
+
animation.start();
|
|
273
|
+
return () => animation.stop();
|
|
274
|
+
}, [anim]);
|
|
275
|
+
|
|
276
|
+
const translateX = anim.interpolate({
|
|
277
|
+
inputRange: [0, 1],
|
|
278
|
+
outputRange: [side === 'left' ? -6 : 6, 0],
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
return (
|
|
282
|
+
<View style={styles.postMedia__dotSlot}>
|
|
283
|
+
<Animated.View
|
|
284
|
+
style={[
|
|
285
|
+
styles.postMedia__dot,
|
|
286
|
+
styles.postMedia__dotEdge,
|
|
287
|
+
{ opacity: anim, transform: [{ translateX }] },
|
|
288
|
+
]}
|
|
289
|
+
/>
|
|
214
290
|
</View>
|
|
215
291
|
);
|
|
216
292
|
}
|
|
@@ -260,20 +336,22 @@ type ImageMediaProps = { post: Amity.Post<'image'> };
|
|
|
260
336
|
function ImageMedia({ post }: ImageMediaProps) {
|
|
261
337
|
const styles = useStyles();
|
|
262
338
|
const { apiRegion } = useAuth();
|
|
263
|
-
const [isBroken, setIsBroken] = useState(false);
|
|
264
339
|
const fileId = post.getImageInfo()?.fileId;
|
|
265
340
|
const url = fileId
|
|
266
341
|
? `https://api.${apiRegion}.amity.co/api/v3/files/${fileId}/download?size=large`
|
|
267
342
|
: undefined;
|
|
343
|
+
// Keyed to the url rather than a bare boolean, so replacing the image retries
|
|
344
|
+
// the load instead of staying broken for the session (see VideoMedia).
|
|
345
|
+
const [brokenUrl, setBrokenUrl] = useState<string>();
|
|
268
346
|
|
|
269
|
-
if (!url ||
|
|
347
|
+
if (!url || brokenUrl === url) return <BrokenFrame />;
|
|
270
348
|
|
|
271
349
|
return (
|
|
272
350
|
<Image
|
|
273
351
|
style={styles.postMedia__media as StyleProp<ImageStyle>}
|
|
274
352
|
source={{ uri: url }}
|
|
275
353
|
resizeMode="cover"
|
|
276
|
-
onError={() =>
|
|
354
|
+
onError={() => setBrokenUrl(url)}
|
|
277
355
|
/>
|
|
278
356
|
);
|
|
279
357
|
}
|
|
@@ -282,16 +360,50 @@ type VideoMediaProps = { post: Amity.Post<'video'> };
|
|
|
282
360
|
|
|
283
361
|
function VideoMedia({ post }: VideoMediaProps) {
|
|
284
362
|
const styles = useStyles();
|
|
285
|
-
const { apiRegion } = useAuth();
|
|
286
|
-
const [isBroken, setIsBroken] = useState(false);
|
|
287
363
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
364
|
+
// Resolve the thumbnail through the SDK's linked file object rather than
|
|
365
|
+
// building a download url from `data.thumbnailFileId`. The file behind that
|
|
366
|
+
// id does not exist until the server finishes transcoding, so a url built
|
|
367
|
+
// from the id alone is well-formed and still 404s — which is what rendered a
|
|
368
|
+
// broken-image icon on every just-created video post. The file object is the
|
|
369
|
+
// honest signal: it is absent until the thumbnail actually exists. Mirrors
|
|
370
|
+
// web's useImage/useFile.
|
|
371
|
+
const thumbnail = post.getVideoThumbnailInfo?.();
|
|
372
|
+
const serverUrl = thumbnail?.fileUrl
|
|
373
|
+
? FileRepository.fileUrlWithSize(thumbnail.fileUrl, ImageSizeState.large)
|
|
292
374
|
: undefined;
|
|
293
375
|
|
|
294
|
-
|
|
376
|
+
// While the server has none, borrow the frame the composer decoded when this
|
|
377
|
+
// video was picked, matched on the original video's fileId (web parity:
|
|
378
|
+
// VideoContent falls back to LayoutProvider's videoThumbnail the same way).
|
|
379
|
+
const localThumbnails = useUIKitSelector(
|
|
380
|
+
(state: RootState) => state.localVideoThumbnail.videos
|
|
381
|
+
);
|
|
382
|
+
const originalFileId = (post.data as Amity.ContentDataVideo | undefined)
|
|
383
|
+
?.videoFileId?.original;
|
|
384
|
+
const localUrl = serverUrl
|
|
385
|
+
? undefined
|
|
386
|
+
: localThumbnails.find(({ fileId }) => fileId === originalFileId)
|
|
387
|
+
?.thumbnailUrl;
|
|
388
|
+
|
|
389
|
+
const url = serverUrl ?? localUrl;
|
|
390
|
+
|
|
391
|
+
// Keyed to the url, not a bare boolean: the url changes when the real
|
|
392
|
+
// thumbnail lands, and a latched flag would keep the frame broken for the
|
|
393
|
+
// rest of the session.
|
|
394
|
+
const [brokenUrl, setBrokenUrl] = useState<string>();
|
|
395
|
+
|
|
396
|
+
const playButton = (
|
|
397
|
+
<View style={styles.postMedia__playButton}>
|
|
398
|
+
<SvgXml xml={videoControlIcon} width="40" height="40" />
|
|
399
|
+
</View>
|
|
400
|
+
);
|
|
401
|
+
|
|
402
|
+
// Nothing to show yet, from either source: still a normal state for a new
|
|
403
|
+
// post, so keep the play affordance rather than flagging it broken.
|
|
404
|
+
if (!url) return <View style={styles.postMedia__pending}>{playButton}</View>;
|
|
405
|
+
|
|
406
|
+
if (brokenUrl === url) return <BrokenFrame />;
|
|
295
407
|
|
|
296
408
|
return (
|
|
297
409
|
<>
|
|
@@ -299,11 +411,9 @@ function VideoMedia({ post }: VideoMediaProps) {
|
|
|
299
411
|
style={styles.postMedia__media as StyleProp<ImageStyle>}
|
|
300
412
|
source={{ uri: url }}
|
|
301
413
|
resizeMode="cover"
|
|
302
|
-
onError={() =>
|
|
414
|
+
onError={() => setBrokenUrl(url)}
|
|
303
415
|
/>
|
|
304
|
-
|
|
305
|
-
<SvgXml xml={videoControlIcon} width="40" height="40" />
|
|
306
|
-
</View>
|
|
416
|
+
{playButton}
|
|
307
417
|
</>
|
|
308
418
|
);
|
|
309
419
|
}
|
|
@@ -48,6 +48,13 @@ export const useStyles = () => {
|
|
|
48
48
|
justifyContent: 'center',
|
|
49
49
|
backgroundColor: theme.colors.baseShade4,
|
|
50
50
|
},
|
|
51
|
+
// A video whose thumbnail the server has not produced yet: same plain
|
|
52
|
+
// container as the broken frame, but it carries the play button instead of
|
|
53
|
+
// the broken-image glyph.
|
|
54
|
+
postMedia__pending: {
|
|
55
|
+
flex: 1,
|
|
56
|
+
backgroundColor: theme.colors.baseShade4,
|
|
57
|
+
},
|
|
51
58
|
postMedia__counter: {
|
|
52
59
|
position: 'absolute',
|
|
53
60
|
top: 8, // web: 0.5rem
|
package/src/social/features/community/PendingRequest/components/PendingPostList/PendingPostList.tsx
CHANGED
|
@@ -19,7 +19,7 @@ import { usePostCollection } from '../../../../../hooks/collections/post/usePost
|
|
|
19
19
|
import ActionButton from '../../../../../elements/ActionButton';
|
|
20
20
|
import { usePendingPostQuery } from '../../../../../hooks/usePendingPostQuery';
|
|
21
21
|
import { BrandBadge } from '../../../../../elements/BrandBadge';
|
|
22
|
-
import {
|
|
22
|
+
import { useCommunityPermission } from '../../../../../hooks';
|
|
23
23
|
|
|
24
24
|
import { SvgXml } from 'react-native-svg';
|
|
25
25
|
import { Title } from '../../../../../elements';
|
|
@@ -50,8 +50,13 @@ const usePendingPostList = ({
|
|
|
50
50
|
},
|
|
51
51
|
});
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
// Reviewing a community's posts is a property of the membership, not of the
|
|
54
|
+
// user: a moderator of one community carries no moderator role on their own
|
|
55
|
+
// user object, so reading the user's roles hid the accept/decline buttons
|
|
56
|
+
// from every moderator who was not also a network-wide admin.
|
|
57
|
+
const { canReviewCommunityPosts } = useCommunityPermission(
|
|
58
|
+
community.communityId
|
|
59
|
+
);
|
|
55
60
|
|
|
56
61
|
useEffect(() => {
|
|
57
62
|
onPendingPostCountChange?.(collection.data.length);
|
|
@@ -91,7 +96,7 @@ const usePendingPostList = ({
|
|
|
91
96
|
renderDivider,
|
|
92
97
|
openBottomSheet,
|
|
93
98
|
handleDeletePost,
|
|
94
|
-
|
|
99
|
+
canReviewCommunityPosts,
|
|
95
100
|
};
|
|
96
101
|
};
|
|
97
102
|
|
|
@@ -110,7 +115,7 @@ const PendingPostList = ({
|
|
|
110
115
|
renderDivider,
|
|
111
116
|
openBottomSheet,
|
|
112
117
|
handleDeletePost,
|
|
113
|
-
|
|
118
|
+
canReviewCommunityPosts,
|
|
114
119
|
} = usePendingPostList({ community, onPendingPostCountChange });
|
|
115
120
|
|
|
116
121
|
return (
|
|
@@ -221,7 +226,7 @@ const PendingPostList = ({
|
|
|
221
226
|
mentionPositionArr={item?.metadata?.mentioned ?? []}
|
|
222
227
|
/>
|
|
223
228
|
</View>
|
|
224
|
-
{
|
|
229
|
+
{canReviewCommunityPosts && (
|
|
225
230
|
<>
|
|
226
231
|
<View style={styles.divider} />
|
|
227
232
|
<View style={styles.actionContainer}>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { FC, memo } from 'react';
|
|
1
|
+
import { FC, memo, useCallback } from 'react';
|
|
2
2
|
import { View } from 'react-native';
|
|
3
|
+
import { useFocusEffect } from '@react-navigation/native';
|
|
3
4
|
import { ComponentID, PageID } from '../../../../enums/enumUIKitID';
|
|
4
5
|
import AmityGlobalFeedComponent from '../GlobalFeed/GlobalFeed';
|
|
5
6
|
import { useStyles } from './styles';
|
|
@@ -7,6 +8,8 @@ import { useAmityComponent } from '../../../../hooks';
|
|
|
7
8
|
import { useCustomRankingGlobalFeed } from '../../../../hooks/useCustomRankingGlobalFeed';
|
|
8
9
|
import NewsFeedLoadingComponent from '../../../../components/NewsFeedLoadingComponent/NewsFeedLoadingComponent';
|
|
9
10
|
import { AmityEmptyNewsFeedComponent } from '../../../..';
|
|
11
|
+
import uiSlice from '../../../../../core/stores/slices/uiSlice';
|
|
12
|
+
import { useUIKitDispatch } from '../../../../../core/stores/store';
|
|
10
13
|
|
|
11
14
|
type AmityNewsFeedComponentType = {
|
|
12
15
|
pageId?: PageID;
|
|
@@ -27,6 +30,18 @@ const AmityNewsFeedComponent: FC<AmityNewsFeedComponentType> = ({
|
|
|
27
30
|
const { itemWithAds, refresh, globalFeedPosts, loading, onNextPage } =
|
|
28
31
|
useCustomRankingGlobalFeed();
|
|
29
32
|
|
|
33
|
+
const dispatch = useUIKitDispatch();
|
|
34
|
+
const { markFeedReloaded } = uiSlice.actions;
|
|
35
|
+
|
|
36
|
+
// Coming back to the feed is a reload as far as the viewer is concerned, but
|
|
37
|
+
// the screen is never unmounted, so nothing here re-runs on its own. Say so
|
|
38
|
+
// explicitly, the same way pull-to-refresh does.
|
|
39
|
+
useFocusEffect(
|
|
40
|
+
useCallback(() => {
|
|
41
|
+
dispatch(markFeedReloaded());
|
|
42
|
+
}, [dispatch, markFeedReloaded])
|
|
43
|
+
);
|
|
44
|
+
|
|
30
45
|
if (isExcluded) return null;
|
|
31
46
|
|
|
32
47
|
if (
|