@amityco/react-native-social-uikit 4.1.1-dfb439b1.0 → 4.1.1-e85011d9.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 (48) hide show
  1. package/lib/commonjs/core/assets/icons/xml.js +1 -15
  2. package/lib/commonjs/core/assets/icons/xml.js.map +1 -1
  3. package/lib/commonjs/core/hooks/usePostByIds.js +38 -0
  4. package/lib/commonjs/core/hooks/usePostByIds.js.map +1 -0
  5. package/lib/commonjs/social/components/MediaViewer/MediaViewer.js +3 -24
  6. package/lib/commonjs/social/components/MediaViewer/MediaViewer.js.map +1 -1
  7. package/lib/commonjs/social/features/post/Composer/PostComposer.js +32 -39
  8. package/lib/commonjs/social/features/post/Composer/PostComposer.js.map +1 -1
  9. package/lib/module/core/assets/icons/xml.js +0 -12
  10. package/lib/module/core/assets/icons/xml.js.map +1 -1
  11. package/lib/module/core/hooks/usePostByIds.js +32 -0
  12. package/lib/module/core/hooks/usePostByIds.js.map +1 -0
  13. package/lib/module/social/components/MediaViewer/MediaViewer.js +4 -25
  14. package/lib/module/social/components/MediaViewer/MediaViewer.js.map +1 -1
  15. package/lib/module/social/features/post/Composer/PostComposer.js +34 -41
  16. package/lib/module/social/features/post/Composer/PostComposer.js.map +1 -1
  17. package/lib/typescript/core/assets/icons/xml.d.ts +0 -2
  18. package/lib/typescript/core/assets/icons/xml.d.ts.map +1 -1
  19. package/lib/typescript/core/hooks/usePostByIds.d.ts +8 -0
  20. package/lib/typescript/core/hooks/usePostByIds.d.ts.map +1 -0
  21. package/lib/typescript/social/components/MediaViewer/MediaViewer.d.ts.map +1 -1
  22. package/lib/typescript/social/features/post/Composer/PostComposer.d.ts.map +1 -1
  23. package/package.json +1 -3
  24. package/src/core/assets/icons/xml.ts +0 -16
  25. package/src/core/hooks/usePostByIds.ts +33 -0
  26. package/src/social/components/MediaViewer/MediaViewer.tsx +1 -30
  27. package/src/social/features/post/Composer/PostComposer.tsx +48 -65
  28. package/lib/commonjs/social/components/MediaSelector/MediaSelector.js +0 -164
  29. package/lib/commonjs/social/components/MediaSelector/MediaSelector.js.map +0 -1
  30. package/lib/commonjs/social/components/MediaSelector/index.js +0 -13
  31. package/lib/commonjs/social/components/MediaSelector/index.js.map +0 -1
  32. package/lib/commonjs/social/components/MediaSelector/styles.js +0 -130
  33. package/lib/commonjs/social/components/MediaSelector/styles.js.map +0 -1
  34. package/lib/module/social/components/MediaSelector/MediaSelector.js +0 -158
  35. package/lib/module/social/components/MediaSelector/MediaSelector.js.map +0 -1
  36. package/lib/module/social/components/MediaSelector/index.js +0 -2
  37. package/lib/module/social/components/MediaSelector/index.js.map +0 -1
  38. package/lib/module/social/components/MediaSelector/styles.js +0 -123
  39. package/lib/module/social/components/MediaSelector/styles.js.map +0 -1
  40. package/lib/typescript/social/components/MediaSelector/MediaSelector.d.ts +0 -14
  41. package/lib/typescript/social/components/MediaSelector/MediaSelector.d.ts.map +0 -1
  42. package/lib/typescript/social/components/MediaSelector/index.d.ts +0 -2
  43. package/lib/typescript/social/components/MediaSelector/index.d.ts.map +0 -1
  44. package/lib/typescript/social/components/MediaSelector/styles.d.ts +0 -116
  45. package/lib/typescript/social/components/MediaSelector/styles.d.ts.map +0 -1
  46. package/src/social/components/MediaSelector/MediaSelector.tsx +0 -231
  47. package/src/social/components/MediaSelector/index.tsx +0 -1
  48. package/src/social/components/MediaSelector/styles.ts +0 -124
@@ -1,231 +0,0 @@
1
- import { useCallback, useEffect, useState } from 'react';
2
- import {
3
- FlatList,
4
- Image,
5
- Modal,
6
- PermissionsAndroid,
7
- Platform,
8
- Text,
9
- TouchableOpacity,
10
- TouchableWithoutFeedback,
11
- View,
12
- } from 'react-native';
13
- import {
14
- CameraRoll,
15
- type PhotoIdentifier,
16
- } from '@react-native-camera-roll/camera-roll';
17
- import { useStyles } from './styles';
18
-
19
- type MediaSelectorProps = {
20
- visible: boolean;
21
- mediaType: 'photo' | 'video';
22
- /** URIs already added to the composer — used for silent duplicate detection. */
23
- existing: string[];
24
- /** How many more items may be added (10 - current count). */
25
- remaining: number;
26
- onClose: () => void;
27
- /** New, de-duplicated URIs in the order they were selected. */
28
- onConfirm: (uris: string[]) => void;
29
- };
30
-
31
- const PAGE_SIZE = 90;
32
-
33
- async function ensureLibraryPermission(
34
- mediaType: 'photo' | 'video'
35
- ): Promise<boolean> {
36
- if (Platform.OS !== 'android') return true;
37
- const version =
38
- typeof Platform.Version === 'number'
39
- ? Platform.Version
40
- : parseInt(Platform.Version, 10);
41
- const perms =
42
- version >= 33
43
- ? [
44
- mediaType === 'photo'
45
- ? PermissionsAndroid.PERMISSIONS.READ_MEDIA_IMAGES
46
- : PermissionsAndroid.PERMISSIONS.READ_MEDIA_VIDEO,
47
- ]
48
- : [PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE];
49
- try {
50
- const result = await PermissionsAndroid.requestMultiple(perms);
51
- return Object.values(result).every(
52
- (status) => status === PermissionsAndroid.RESULTS.GRANTED
53
- );
54
- } catch {
55
- return false;
56
- }
57
- }
58
-
59
- function formatDuration(seconds?: number): string {
60
- if (!seconds || seconds <= 0) return '';
61
- const total = Math.round(seconds);
62
- const mins = Math.floor(total / 60);
63
- const secs = total % 60;
64
- return `${mins}:${secs.toString().padStart(2, '0')}`;
65
- }
66
-
67
- export function MediaSelector({
68
- visible,
69
- mediaType,
70
- existing,
71
- remaining,
72
- onClose,
73
- onConfirm,
74
- }: MediaSelectorProps) {
75
- const { styles } = useStyles();
76
- const [items, setItems] = useState<PhotoIdentifier[]>([]);
77
- const [selected, setSelected] = useState<string[]>([]);
78
- const [cursor, setCursor] = useState<string | undefined>(undefined);
79
- const [hasNextPage, setHasNextPage] = useState(false);
80
- const [loading, setLoading] = useState(false);
81
-
82
- const loadPage = useCallback(
83
- async (after?: string) => {
84
- if (loading) return;
85
- setLoading(true);
86
- try {
87
- const res = await CameraRoll.getPhotos({
88
- first: PAGE_SIZE,
89
- after,
90
- assetType: mediaType === 'photo' ? 'Photos' : 'Videos',
91
- include: ['filename', 'playableDuration'],
92
- });
93
- setItems((prev) => (after ? [...prev, ...res.edges] : res.edges));
94
- setCursor(res.page_info.end_cursor);
95
- setHasNextPage(res.page_info.has_next_page);
96
- } catch {
97
- // permission denied / empty library — leave the list empty
98
- } finally {
99
- setLoading(false);
100
- }
101
- },
102
- [loading, mediaType]
103
- );
104
-
105
- // Reset and (re)load whenever the selector opens. A fresh open starts with an
106
- // empty selection; previously-added items are handled by duplicate detection.
107
- useEffect(() => {
108
- if (!visible) return;
109
- setSelected([]);
110
- setItems([]);
111
- setCursor(undefined);
112
- setHasNextPage(false);
113
- (async () => {
114
- const granted = await ensureLibraryPermission(mediaType);
115
- if (granted) loadPage();
116
- })();
117
- }, [visible, mediaType]);
118
-
119
- const toggle = useCallback(
120
- (uri: string) => {
121
- setSelected((prev) => {
122
- const index = prev.indexOf(uri);
123
- if (index !== -1) {
124
- // Deselect: removing from the array renumbers the rest automatically.
125
- return prev.filter((u) => u !== uri);
126
- }
127
- if (prev.length >= remaining) return prev; // respect the cap silently
128
- return [...prev, uri];
129
- });
130
- },
131
- [remaining]
132
- );
133
-
134
- const handleConfirm = useCallback(() => {
135
- const deduped = selected.filter((uri) => !existing.includes(uri));
136
- onConfirm(deduped);
137
- }, [selected, existing, onConfirm]);
138
-
139
- const onEndReached = useCallback(() => {
140
- if (hasNextPage && !loading) loadPage(cursor);
141
- }, [hasNextPage, loading, cursor, loadPage]);
142
-
143
- const renderItem = useCallback(
144
- ({ item }: { item: PhotoIdentifier }) => {
145
- const uri = item.node.image.uri;
146
- const order = selected.indexOf(uri);
147
- const isSelected = order !== -1;
148
- const duration =
149
- mediaType === 'video'
150
- ? formatDuration(item.node.image.playableDuration)
151
- : '';
152
- return (
153
- <TouchableWithoutFeedback onPress={() => toggle(uri)}>
154
- <View style={styles.cell}>
155
- <Image style={styles.cell__image} source={{ uri }} />
156
- {!!duration && (
157
- <Text style={styles.cell__videoDuration}>{duration}</Text>
158
- )}
159
- {isSelected ? (
160
- <>
161
- <View style={styles.cell__selectedOverlay} />
162
- <View style={styles.cell__badge}>
163
- <Text style={styles.cell__badgeLabel}>{order + 1}</Text>
164
- </View>
165
- </>
166
- ) : (
167
- <View style={styles.cell__badgeEmpty} />
168
- )}
169
- </View>
170
- </TouchableWithoutFeedback>
171
- );
172
- },
173
- [selected, mediaType, toggle, styles]
174
- );
175
-
176
- return (
177
- <Modal
178
- visible={visible}
179
- animationType="slide"
180
- onRequestClose={onClose}
181
- presentationStyle="fullScreen"
182
- >
183
- <View style={styles.container}>
184
- <View style={styles.header}>
185
- <TouchableOpacity style={styles.headerButton} onPress={onClose}>
186
- <Text style={styles.headerClose}>Cancel</Text>
187
- </TouchableOpacity>
188
- <Text style={styles.headerTitle}>
189
- {mediaType === 'photo' ? 'Photos' : 'Videos'}
190
- </Text>
191
- <TouchableOpacity
192
- style={styles.headerButton__right}
193
- disabled={selected.length === 0}
194
- onPress={handleConfirm}
195
- >
196
- <Text
197
- style={
198
- selected.length === 0
199
- ? styles.headerAdd__disabled
200
- : styles.headerAdd
201
- }
202
- >
203
- {selected.length > 0 ? `Add (${selected.length})` : 'Add'}
204
- </Text>
205
- </TouchableOpacity>
206
- </View>
207
- <FlatList
208
- data={items}
209
- numColumns={3}
210
- keyExtractor={(item) => item.node.image.uri}
211
- renderItem={renderItem}
212
- contentContainerStyle={styles.grid}
213
- onEndReached={onEndReached}
214
- onEndReachedThreshold={0.6}
215
- initialNumToRender={30}
216
- windowSize={5}
217
- ListEmptyComponent={
218
- !loading ? (
219
- <View style={styles.empty}>
220
- <Text style={styles.empty__text}>
221
- No {mediaType === 'photo' ? 'photos' : 'videos'} found on this
222
- device.
223
- </Text>
224
- </View>
225
- ) : null
226
- }
227
- />
228
- </View>
229
- </Modal>
230
- );
231
- }
@@ -1 +0,0 @@
1
- export { MediaSelector } from './MediaSelector';
@@ -1,124 +0,0 @@
1
- import { StyleSheet, useWindowDimensions } from 'react-native';
2
- import { useTheme } from 'react-native-paper';
3
- import type { MyMD3Theme } from '../../../core/providers/AmityUIKitProvider';
4
-
5
- const NUM_COLUMNS = 3;
6
- const CELL_GAP = 2;
7
-
8
- export const useStyles = () => {
9
- const theme = useTheme<MyMD3Theme>();
10
- const { width } = useWindowDimensions();
11
- const cellSize = Math.floor(
12
- (width - CELL_GAP * (NUM_COLUMNS - 1)) / NUM_COLUMNS
13
- );
14
-
15
- const styles = StyleSheet.create({
16
- container: {
17
- flex: 1,
18
- backgroundColor: theme.colors.background,
19
- },
20
- header: {
21
- flexDirection: 'row',
22
- alignItems: 'center',
23
- justifyContent: 'space-between',
24
- paddingHorizontal: 16,
25
- height: 56,
26
- },
27
- headerButton: {
28
- minWidth: 64,
29
- },
30
- headerButton__right: {
31
- minWidth: 64,
32
- alignItems: 'flex-end',
33
- },
34
- headerTitle: {
35
- color: theme.colors.base,
36
- fontSize: 16,
37
- fontWeight: '600',
38
- },
39
- headerClose: {
40
- color: theme.colors.base,
41
- fontSize: 16,
42
- },
43
- headerAdd: {
44
- color: theme.colors.primary,
45
- fontSize: 16,
46
- fontWeight: '600',
47
- },
48
- headerAdd__disabled: {
49
- color: theme.colors.baseShade3,
50
- fontSize: 16,
51
- fontWeight: '600',
52
- },
53
- grid: {
54
- paddingBottom: 24,
55
- },
56
- cell: {
57
- width: cellSize,
58
- height: cellSize,
59
- marginRight: CELL_GAP,
60
- marginBottom: CELL_GAP,
61
- backgroundColor: theme.colors.baseShade4,
62
- },
63
- cell__image: {
64
- width: '100%',
65
- height: '100%',
66
- },
67
- cell__selectedOverlay: {
68
- ...StyleSheet.absoluteFillObject,
69
- borderWidth: 2,
70
- borderColor: theme.colors.primary,
71
- },
72
- cell__badge: {
73
- position: 'absolute',
74
- top: 6,
75
- right: 6,
76
- minWidth: 22,
77
- height: 22,
78
- borderRadius: 11,
79
- paddingHorizontal: 5,
80
- alignItems: 'center',
81
- justifyContent: 'center',
82
- backgroundColor: theme.colors.primary,
83
- },
84
- cell__badgeEmpty: {
85
- position: 'absolute',
86
- top: 6,
87
- right: 6,
88
- width: 22,
89
- height: 22,
90
- borderRadius: 11,
91
- borderWidth: 1.5,
92
- borderColor: '#FFFFFF',
93
- backgroundColor: 'rgba(0,0,0,0.2)',
94
- },
95
- cell__badgeLabel: {
96
- color: '#FFFFFF',
97
- fontSize: 12,
98
- fontWeight: '700',
99
- },
100
- cell__videoDuration: {
101
- position: 'absolute',
102
- bottom: 6,
103
- right: 6,
104
- color: '#FFFFFF',
105
- fontSize: 11,
106
- fontWeight: '600',
107
- textShadowColor: 'rgba(0,0,0,0.6)',
108
- textShadowRadius: 2,
109
- },
110
- empty: {
111
- flex: 1,
112
- alignItems: 'center',
113
- justifyContent: 'center',
114
- padding: 32,
115
- },
116
- empty__text: {
117
- color: theme.colors.baseShade1,
118
- fontSize: 14,
119
- textAlign: 'center',
120
- },
121
- });
122
-
123
- return { styles, theme };
124
- };