@ethora/chat-component-rn 26.7.3 → 26.7.4
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/CHANGELOG.md +45 -0
- package/README.md +9 -8
- package/lib/commonjs/assets/icons.js +7 -2
- package/lib/commonjs/assets/icons.js.map +1 -1
- package/lib/commonjs/components/Header/HeaderRoomList.js +20 -6
- package/lib/commonjs/components/Header/HeaderRoomList.js.map +1 -1
- package/lib/commonjs/components/MainComponents/ChatRoom.js +26 -4
- package/lib/commonjs/components/MainComponents/ChatRoom.js.map +1 -1
- package/lib/commonjs/components/MainComponents/MessageList.js +2 -1
- package/lib/commonjs/components/MainComponents/MessageList.js.map +1 -1
- package/lib/commonjs/components/MainComponents/ProfileImagePlaceholder.js +5 -5
- package/lib/commonjs/components/MainComponents/ProfileImagePlaceholder.js.map +1 -1
- package/lib/commonjs/components/MainComponents/RoomList.js +62 -32
- package/lib/commonjs/components/MainComponents/RoomList.js.map +1 -1
- package/lib/commonjs/components/Menu/HeaderRoomListMenu.js +208 -70
- package/lib/commonjs/components/Menu/HeaderRoomListMenu.js.map +1 -1
- package/lib/commonjs/components/Modals/AttachSheet/AttachSheet.js +307 -99
- package/lib/commonjs/components/Modals/AttachSheet/AttachSheet.js.map +1 -1
- package/lib/commonjs/components/Modals/ChatProfileModal/ChatProfileModal.js +552 -213
- package/lib/commonjs/components/Modals/ChatProfileModal/ChatProfileModal.js.map +1 -1
- package/lib/commonjs/components/Modals/ChatProfileModal/ReportChatModal.js +226 -0
- package/lib/commonjs/components/Modals/ChatProfileModal/ReportChatModal.js.map +1 -0
- package/lib/commonjs/components/Modals/FilePreviewModal/FilePreviewModal.js +6 -2
- package/lib/commonjs/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
- package/lib/commonjs/components/Modals/Modal/Modal.js +8 -0
- package/lib/commonjs/components/Modals/Modal/Modal.js.map +1 -1
- package/lib/commonjs/components/Modals/NewChatModal/NewChatModal.js +202 -104
- package/lib/commonjs/components/Modals/NewChatModal/NewChatModal.js.map +1 -1
- package/lib/commonjs/components/Modals/ProfileHeader/ProfileHeader.js +556 -0
- package/lib/commonjs/components/Modals/ProfileHeader/ProfileHeader.js.map +1 -0
- package/lib/commonjs/components/Modals/ProfileHeader/ProfileMenu.js +210 -0
- package/lib/commonjs/components/Modals/ProfileHeader/ProfileMenu.js.map +1 -0
- package/lib/commonjs/components/Modals/SelectUsersModal/SelectUsersModal.js +4 -2
- package/lib/commonjs/components/Modals/SelectUsersModal/SelectUsersModal.js.map +1 -1
- package/lib/commonjs/components/Modals/UserProfileModal/UserProfileModal.js +507 -244
- package/lib/commonjs/components/Modals/UserProfileModal/UserProfileModal.js.map +1 -1
- package/lib/commonjs/components/Modals/UserSettingsModal/UserSettingsModal.js +74 -46
- package/lib/commonjs/components/Modals/UserSettingsModal/UserSettingsModal.js.map +1 -1
- package/lib/commonjs/components/Modals/styledModalComponents.js +9 -2
- package/lib/commonjs/components/Modals/styledModalComponents.js.map +1 -1
- package/lib/commonjs/components/RoomComponents/ChatRoomItem.js +17 -7
- package/lib/commonjs/components/RoomComponents/ChatRoomItem.js.map +1 -1
- package/lib/commonjs/components/styled/SendInput.js +80 -1
- package/lib/commonjs/components/styled/SendInput.js.map +1 -1
- package/lib/commonjs/components/styled/StyledComponents.js +9 -3
- package/lib/commonjs/components/styled/StyledComponents.js.map +1 -1
- package/lib/commonjs/components/styled/StyledInputComponents/StyledInputComponents.js +11 -4
- package/lib/commonjs/components/styled/StyledInputComponents/StyledInputComponents.js.map +1 -1
- package/lib/commonjs/helpers/getChatBackground.js +23 -0
- package/lib/commonjs/helpers/getChatBackground.js.map +1 -0
- package/lib/commonjs/helpers/mediaLibraryRuntime.js +31 -0
- package/lib/commonjs/helpers/mediaLibraryRuntime.js.map +1 -0
- package/lib/commonjs/helpers/sheetGestures.js +32 -0
- package/lib/commonjs/helpers/sheetGestures.js.map +1 -0
- package/lib/commonjs/i18n/strings.js +121 -1
- package/lib/commonjs/i18n/strings.js.map +1 -1
- package/lib/commonjs/networking/api-requests/user.api.js +59 -0
- package/lib/commonjs/networking/api-requests/user.api.js.map +1 -1
- package/lib/commonjs/roomStore/chatSettingsSlice.js +3 -1
- package/lib/commonjs/roomStore/chatSettingsSlice.js.map +1 -1
- package/lib/module/assets/icons.js +6 -1
- package/lib/module/assets/icons.js.map +1 -1
- package/lib/module/components/Header/HeaderRoomList.js +20 -6
- package/lib/module/components/Header/HeaderRoomList.js.map +1 -1
- package/lib/module/components/MainComponents/ChatRoom.js +26 -4
- package/lib/module/components/MainComponents/ChatRoom.js.map +1 -1
- package/lib/module/components/MainComponents/MessageList.js +2 -1
- package/lib/module/components/MainComponents/MessageList.js.map +1 -1
- package/lib/module/components/MainComponents/ProfileImagePlaceholder.js +5 -5
- package/lib/module/components/MainComponents/ProfileImagePlaceholder.js.map +1 -1
- package/lib/module/components/MainComponents/RoomList.js +62 -32
- package/lib/module/components/MainComponents/RoomList.js.map +1 -1
- package/lib/module/components/Menu/HeaderRoomListMenu.js +211 -73
- package/lib/module/components/Menu/HeaderRoomListMenu.js.map +1 -1
- package/lib/module/components/Modals/AttachSheet/AttachSheet.js +298 -102
- package/lib/module/components/Modals/AttachSheet/AttachSheet.js.map +1 -1
- package/lib/module/components/Modals/ChatProfileModal/ChatProfileModal.js +555 -217
- package/lib/module/components/Modals/ChatProfileModal/ChatProfileModal.js.map +1 -1
- package/lib/module/components/Modals/ChatProfileModal/ReportChatModal.js +223 -0
- package/lib/module/components/Modals/ChatProfileModal/ReportChatModal.js.map +1 -0
- package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js +6 -2
- package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
- package/lib/module/components/Modals/Modal/Modal.js +9 -1
- package/lib/module/components/Modals/Modal/Modal.js.map +1 -1
- package/lib/module/components/Modals/NewChatModal/NewChatModal.js +205 -106
- package/lib/module/components/Modals/NewChatModal/NewChatModal.js.map +1 -1
- package/lib/module/components/Modals/ProfileHeader/ProfileHeader.js +548 -0
- package/lib/module/components/Modals/ProfileHeader/ProfileHeader.js.map +1 -0
- package/lib/module/components/Modals/ProfileHeader/ProfileMenu.js +205 -0
- package/lib/module/components/Modals/ProfileHeader/ProfileMenu.js.map +1 -0
- package/lib/module/components/Modals/SelectUsersModal/SelectUsersModal.js +4 -2
- package/lib/module/components/Modals/SelectUsersModal/SelectUsersModal.js.map +1 -1
- package/lib/module/components/Modals/UserProfileModal/UserProfileModal.js +509 -248
- package/lib/module/components/Modals/UserProfileModal/UserProfileModal.js.map +1 -1
- package/lib/module/components/Modals/UserSettingsModal/UserSettingsModal.js +76 -48
- package/lib/module/components/Modals/UserSettingsModal/UserSettingsModal.js.map +1 -1
- package/lib/module/components/Modals/styledModalComponents.js +9 -2
- package/lib/module/components/Modals/styledModalComponents.js.map +1 -1
- package/lib/module/components/RoomComponents/ChatRoomItem.js +17 -7
- package/lib/module/components/RoomComponents/ChatRoomItem.js.map +1 -1
- package/lib/module/components/styled/SendInput.js +80 -1
- package/lib/module/components/styled/SendInput.js.map +1 -1
- package/lib/module/components/styled/StyledComponents.js +9 -3
- package/lib/module/components/styled/StyledComponents.js.map +1 -1
- package/lib/module/components/styled/StyledInputComponents/StyledInputComponents.js +11 -4
- package/lib/module/components/styled/StyledInputComponents/StyledInputComponents.js.map +1 -1
- package/lib/module/helpers/getChatBackground.js +18 -0
- package/lib/module/helpers/getChatBackground.js.map +1 -0
- package/lib/module/helpers/mediaLibraryRuntime.js +25 -0
- package/lib/module/helpers/mediaLibraryRuntime.js.map +1 -0
- package/lib/module/helpers/sheetGestures.js +26 -0
- package/lib/module/helpers/sheetGestures.js.map +1 -0
- package/lib/module/i18n/strings.js +121 -1
- package/lib/module/i18n/strings.js.map +1 -1
- package/lib/module/networking/api-requests/user.api.js +56 -0
- package/lib/module/networking/api-requests/user.api.js.map +1 -1
- package/lib/module/roomStore/chatSettingsSlice.js +3 -1
- package/lib/module/roomStore/chatSettingsSlice.js.map +1 -1
- package/lib/typescript/assets/icons.d.ts +12 -0
- package/lib/typescript/assets/icons.d.ts.map +1 -1
- package/lib/typescript/components/Header/HeaderRoomList.d.ts.map +1 -1
- package/lib/typescript/components/MainComponents/ChatRoom.d.ts.map +1 -1
- package/lib/typescript/components/MainComponents/MessageList.d.ts.map +1 -1
- package/lib/typescript/components/MainComponents/RoomList.d.ts.map +1 -1
- package/lib/typescript/components/Menu/HeaderRoomListMenu.d.ts.map +1 -1
- package/lib/typescript/components/Modals/AttachSheet/AttachSheet.d.ts +9 -0
- package/lib/typescript/components/Modals/AttachSheet/AttachSheet.d.ts.map +1 -1
- package/lib/typescript/components/Modals/ChatProfileModal/ChatProfileModal.d.ts +10 -0
- package/lib/typescript/components/Modals/ChatProfileModal/ChatProfileModal.d.ts.map +1 -1
- package/lib/typescript/components/Modals/ChatProfileModal/ReportChatModal.d.ts +9 -0
- package/lib/typescript/components/Modals/ChatProfileModal/ReportChatModal.d.ts.map +1 -0
- package/lib/typescript/components/Modals/FilePreviewModal/FilePreviewModal.d.ts.map +1 -1
- package/lib/typescript/components/Modals/Modal/Modal.d.ts.map +1 -1
- package/lib/typescript/components/Modals/NewChatModal/NewChatModal.d.ts +1 -1
- package/lib/typescript/components/Modals/NewChatModal/NewChatModal.d.ts.map +1 -1
- package/lib/typescript/components/Modals/ProfileHeader/ProfileHeader.d.ts +75 -0
- package/lib/typescript/components/Modals/ProfileHeader/ProfileHeader.d.ts.map +1 -0
- package/lib/typescript/components/Modals/ProfileHeader/ProfileMenu.d.ts +25 -0
- package/lib/typescript/components/Modals/ProfileHeader/ProfileMenu.d.ts.map +1 -0
- package/lib/typescript/components/Modals/SelectUsersModal/SelectUsersModal.d.ts +6 -1
- package/lib/typescript/components/Modals/SelectUsersModal/SelectUsersModal.d.ts.map +1 -1
- package/lib/typescript/components/Modals/UserProfileModal/UserProfileModal.d.ts +16 -0
- package/lib/typescript/components/Modals/UserProfileModal/UserProfileModal.d.ts.map +1 -1
- package/lib/typescript/components/Modals/UserSettingsModal/UserSettingsModal.d.ts +1 -0
- package/lib/typescript/components/Modals/UserSettingsModal/UserSettingsModal.d.ts.map +1 -1
- package/lib/typescript/components/Modals/styledModalComponents.d.ts.map +1 -1
- package/lib/typescript/components/RoomComponents/ChatRoomItem.d.ts.map +1 -1
- package/lib/typescript/components/styled/SendInput.d.ts.map +1 -1
- package/lib/typescript/components/styled/StyledComponents.d.ts.map +1 -1
- package/lib/typescript/components/styled/StyledInputComponents/StyledInputComponents.d.ts.map +1 -1
- package/lib/typescript/helpers/getChatBackground.d.ts +15 -0
- package/lib/typescript/helpers/getChatBackground.d.ts.map +1 -0
- package/lib/typescript/helpers/mediaLibraryRuntime.d.ts +3 -0
- package/lib/typescript/helpers/mediaLibraryRuntime.d.ts.map +1 -0
- package/lib/typescript/helpers/sheetGestures.d.ts +20 -0
- package/lib/typescript/helpers/sheetGestures.d.ts.map +1 -0
- package/lib/typescript/i18n/strings.d.ts.map +1 -1
- package/lib/typescript/networking/api-requests/user.api.d.ts +27 -0
- package/lib/typescript/networking/api-requests/user.api.d.ts.map +1 -1
- package/lib/typescript/roomStore/chatSettingsSlice.d.ts.map +1 -1
- package/lib/typescript/types/types.d.ts +8 -5
- package/lib/typescript/types/types.d.ts.map +1 -1
- package/package.json +6 -1
- package/src/assets/icons.tsx +29 -9
- package/src/components/Header/HeaderRoomList.tsx +20 -5
- package/src/components/MainComponents/ChatRoom.tsx +29 -7
- package/src/components/MainComponents/MessageList.tsx +2 -1
- package/src/components/MainComponents/ProfileImagePlaceholder.tsx +5 -5
- package/src/components/MainComponents/RoomList.tsx +65 -34
- package/src/components/Menu/HeaderRoomListMenu.tsx +209 -64
- package/src/components/Modals/AttachSheet/AttachSheet.tsx +318 -86
- package/src/components/Modals/ChatProfileModal/ChatProfileModal.tsx +598 -279
- package/src/components/Modals/ChatProfileModal/ReportChatModal.tsx +244 -0
- package/src/components/Modals/FilePreviewModal/FilePreviewModal.tsx +4 -2
- package/src/components/Modals/Modal/Modal.tsx +7 -0
- package/src/components/Modals/NewChatModal/NewChatModal.tsx +219 -117
- package/src/components/Modals/ProfileHeader/ProfileHeader.tsx +615 -0
- package/src/components/Modals/ProfileHeader/ProfileMenu.tsx +217 -0
- package/src/components/Modals/SelectUsersModal/SelectUsersModal.tsx +14 -4
- package/src/components/Modals/UserProfileModal/UserProfileModal.tsx +617 -307
- package/src/components/Modals/UserSettingsModal/UserSettingsModal.tsx +75 -58
- package/src/components/Modals/styledModalComponents.tsx +9 -2
- package/src/components/RoomComponents/ChatRoomItem.tsx +14 -7
- package/src/components/styled/SendInput.tsx +83 -2
- package/src/components/styled/StyledComponents.ts +9 -3
- package/src/components/styled/StyledInputComponents/StyledInputComponents.tsx +11 -4
- package/src/helpers/getChatBackground.ts +18 -0
- package/src/helpers/mediaLibraryRuntime.ts +25 -0
- package/src/helpers/sheetGestures.ts +25 -0
- package/src/i18n/strings.ts +121 -1
- package/src/networking/api-requests/user.api.ts +71 -0
- package/src/roomStore/chatSettingsSlice.ts +3 -1
- package/src/types/types.ts +8 -5
|
@@ -35,6 +35,14 @@ interface RoomListProps {
|
|
|
35
35
|
onRoomClick?: (chat: IRoom) => void;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
/** Page ground behind the room list, the search field and the header's
|
|
39
|
+
* rounded bottom corners. */
|
|
40
|
+
const LIST_BACKGROUND = '#E8EDF2';
|
|
41
|
+
|
|
42
|
+
/** First-paint fallback until the floating search strip reports its real
|
|
43
|
+
* height; the list's top padding follows the measurement after that. */
|
|
44
|
+
const SEARCH_BAR_ESTIMATE = 60;
|
|
45
|
+
|
|
38
46
|
const RoomList: React.FC<RoomListProps> = ({
|
|
39
47
|
chats,
|
|
40
48
|
burgerMenu = false,
|
|
@@ -120,6 +128,10 @@ const RoomList: React.FC<RoomListProps> = ({
|
|
|
120
128
|
}
|
|
121
129
|
}, [burgerMenu]);
|
|
122
130
|
|
|
131
|
+
// Measured, so the list's top padding always matches the floating
|
|
132
|
+
// search strip — including when a larger font or a taller field grows it.
|
|
133
|
+
const [searchBarHeight, setSearchBarHeight] = useState(SEARCH_BAR_ESTIMATE);
|
|
134
|
+
|
|
123
135
|
const toggleDrawer = () => {
|
|
124
136
|
if (isDrawerOpen) {
|
|
125
137
|
closeDrawer();
|
|
@@ -180,39 +192,50 @@ const RoomList: React.FC<RoomListProps> = ({
|
|
|
180
192
|
<>
|
|
181
193
|
<View style={styles.scrollContainer}>
|
|
182
194
|
<HeaderRoomList setDrawerOpen={toggleDrawer} />
|
|
183
|
-
{/*
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
<View style={styles.
|
|
190
|
-
<
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
+
{/* The search field floats OVER the list rather than
|
|
196
|
+
sitting above it in the column: its strip is transparent
|
|
197
|
+
and the rooms scroll underneath, which is what the design
|
|
198
|
+
shows. It is still fixed (not a ListHeaderComponent), so
|
|
199
|
+
it stays reachable however far the list is scrolled — as a
|
|
200
|
+
list header it scrolled away behind the opaque header. */}
|
|
201
|
+
<View style={styles.listArea}>
|
|
202
|
+
<FlatList
|
|
203
|
+
data={filteredChats}
|
|
204
|
+
keyExtractor={(item) => item.jid}
|
|
205
|
+
// Clears the floating field on first paint; scrolling
|
|
206
|
+
// then slides the rooms under it.
|
|
207
|
+
contentContainerStyle={{ paddingTop: searchBarHeight }}
|
|
208
|
+
renderItem={({ item }) => (
|
|
209
|
+
<Pressable
|
|
210
|
+
// Stable testID so e2e drivers can target a room
|
|
211
|
+
// row by its jid local-part (e.g. Main chat under
|
|
212
|
+
// app id `..._...759` → testID `room-...759`).
|
|
213
|
+
testID={`room-${(item.jid || '').split('@')[0]}`}
|
|
214
|
+
accessibilityLabel={`room-${item.title || item.name}`}
|
|
215
|
+
onPress={() => performClick(item)}
|
|
216
|
+
onPressIn={handlePressIn}
|
|
217
|
+
onPressOut={handlePressOut}
|
|
218
|
+
>
|
|
219
|
+
<ChatRoomItem chat={item} config={config} />
|
|
220
|
+
</Pressable>
|
|
221
|
+
)}
|
|
222
|
+
style={styles.chatList}
|
|
195
223
|
/>
|
|
224
|
+
<View
|
|
225
|
+
testID="room-list-search"
|
|
226
|
+
style={styles.searchBar}
|
|
227
|
+
onLayout={(e) =>
|
|
228
|
+
setSearchBarHeight(e.nativeEvent.layout.height)
|
|
229
|
+
}
|
|
230
|
+
>
|
|
231
|
+
<SearchInput
|
|
232
|
+
icon={<SearchIcon height={20} />}
|
|
233
|
+
value={searchTerm}
|
|
234
|
+
onChangeText={handleSearchChange}
|
|
235
|
+
placeholder={t('search.placeholder')}
|
|
236
|
+
/>
|
|
237
|
+
</View>
|
|
196
238
|
</View>
|
|
197
|
-
<FlatList
|
|
198
|
-
data={filteredChats}
|
|
199
|
-
keyExtractor={(item) => item.jid}
|
|
200
|
-
renderItem={({ item }) => (
|
|
201
|
-
<Pressable
|
|
202
|
-
// Stable testID so e2e drivers can target a room
|
|
203
|
-
// row by its jid local-part (e.g. Main chat under
|
|
204
|
-
// app id `..._...759` → testID `room-...759`).
|
|
205
|
-
testID={`room-${(item.jid || '').split('@')[0]}`}
|
|
206
|
-
accessibilityLabel={`room-${item.title || item.name}`}
|
|
207
|
-
onPress={() => performClick(item)}
|
|
208
|
-
onPressIn={handlePressIn}
|
|
209
|
-
onPressOut={handlePressOut}
|
|
210
|
-
>
|
|
211
|
-
<ChatRoomItem chat={item} config={config} />
|
|
212
|
-
</Pressable>
|
|
213
|
-
)}
|
|
214
|
-
style={styles.chatList}
|
|
215
|
-
/>
|
|
216
239
|
|
|
217
240
|
<HeaderRoomListMenu
|
|
218
241
|
closeDrawer={closeDrawer}
|
|
@@ -238,7 +261,7 @@ const styles = StyleSheet.create({
|
|
|
238
261
|
width: '100%',
|
|
239
262
|
height: '100%',
|
|
240
263
|
flex: 1,
|
|
241
|
-
backgroundColor:
|
|
264
|
+
backgroundColor: LIST_BACKGROUND,
|
|
242
265
|
},
|
|
243
266
|
scrollContainer: {
|
|
244
267
|
flexGrow: 1,
|
|
@@ -249,6 +272,9 @@ const styles = StyleSheet.create({
|
|
|
249
272
|
padding: 16,
|
|
250
273
|
justifyContent: 'space-between',
|
|
251
274
|
},
|
|
275
|
+
listArea: {
|
|
276
|
+
flex: 1,
|
|
277
|
+
},
|
|
252
278
|
searchBar: {
|
|
253
279
|
// `row` matters: SearchInputWrapper is `flex: 1` plus a fixed 44px
|
|
254
280
|
// height. In a column parent that flex resolves VERTICALLY against a
|
|
@@ -256,14 +282,19 @@ const styles = StyleSheet.create({
|
|
|
256
282
|
// but its magnifier. As a row it resolves to width, which is what it
|
|
257
283
|
// meant back when the field was a list-header child.
|
|
258
284
|
flexDirection: 'row',
|
|
285
|
+
position: 'absolute',
|
|
286
|
+
top: 0,
|
|
287
|
+
left: 0,
|
|
288
|
+
right: 0,
|
|
259
289
|
paddingTop: 8,
|
|
290
|
+
paddingBottom: 8,
|
|
260
291
|
paddingHorizontal: 16,
|
|
261
|
-
backgroundColor: '
|
|
292
|
+
backgroundColor: 'transparent',
|
|
262
293
|
},
|
|
263
294
|
chatList: {
|
|
264
295
|
flex: 1,
|
|
265
296
|
paddingHorizontal: 16,
|
|
266
|
-
backgroundColor:
|
|
297
|
+
backgroundColor: LIST_BACKGROUND,
|
|
267
298
|
},
|
|
268
299
|
});
|
|
269
300
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { FC, useMemo } from 'react';
|
|
1
|
+
import React, { FC, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
2
|
import {
|
|
3
3
|
Alert,
|
|
4
4
|
Animated,
|
|
@@ -7,6 +7,8 @@ import {
|
|
|
7
7
|
TouchableWithoutFeedback,
|
|
8
8
|
StyleSheet,
|
|
9
9
|
Dimensions,
|
|
10
|
+
Modal,
|
|
11
|
+
PanResponder,
|
|
10
12
|
View,
|
|
11
13
|
} from 'react-native';
|
|
12
14
|
import {
|
|
@@ -16,10 +18,15 @@ import {
|
|
|
16
18
|
SettingIcon,
|
|
17
19
|
} from '../../assets/icons';
|
|
18
20
|
import { useDispatch } from 'react-redux';
|
|
21
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
19
22
|
import { setActiveModal } from '../../roomStore/chatSettingsSlice';
|
|
20
23
|
import { MODAL_TYPES } from '../../helpers/constants/MODAL_TYPES';
|
|
21
24
|
import { useChatSettingState } from '../../hooks/useChatSettingState';
|
|
22
25
|
import { useLogout } from '../../hooks/useLogout';
|
|
26
|
+
import {
|
|
27
|
+
shouldClaimVerticalDrag,
|
|
28
|
+
shouldDismissOnDrag,
|
|
29
|
+
} from '../../helpers/sheetGestures';
|
|
23
30
|
import type { IConfig } from '../../types/types';
|
|
24
31
|
|
|
25
32
|
type LogoutConfig = NonNullable<IConfig['logout']>;
|
|
@@ -85,7 +92,7 @@ export const runLogoutFlow = async (
|
|
|
85
92
|
}
|
|
86
93
|
};
|
|
87
94
|
|
|
88
|
-
const {
|
|
95
|
+
const { height: windowHeight } = Dimensions.get('window');
|
|
89
96
|
|
|
90
97
|
interface HeaderRoomListMenuProps {
|
|
91
98
|
isDrawerOpen: boolean;
|
|
@@ -106,11 +113,102 @@ export const HeaderRoomListMenu: FC<HeaderRoomListMenuProps> = ({
|
|
|
106
113
|
const logoutConfig = config?.logout?.enabled ? config.logout : undefined;
|
|
107
114
|
const primaryColor = config?.colors?.primary ?? '#0052CD';
|
|
108
115
|
|
|
109
|
-
const
|
|
116
|
+
const insets = useSafeAreaInsets();
|
|
117
|
+
// Measured, so the sheet is exactly as tall as its rows and starts
|
|
118
|
+
// fully off-screen no matter how many options the host enables.
|
|
119
|
+
const [sheetHeight, setSheetHeight] = useState(windowHeight * 0.4);
|
|
120
|
+
// Finger offset, layered on top of the open/close animation the parent
|
|
121
|
+
// drives. Reset once the sheet is unmounted, so a swipe-dismissed sheet
|
|
122
|
+
// never snaps back into view on its way out.
|
|
123
|
+
const dragY = useRef(new Animated.Value(0)).current;
|
|
124
|
+
const sheetHeightRef = useRef(sheetHeight);
|
|
125
|
+
sheetHeightRef.current = sheetHeight;
|
|
126
|
+
|
|
127
|
+
// closeDrawer is a fresh function each render; the responder is built
|
|
128
|
+
// once, so it reads the current one through a ref rather than the one it
|
|
129
|
+
// closed over on mount.
|
|
130
|
+
const closeDrawerRef = useRef(closeDrawer);
|
|
131
|
+
closeDrawerRef.current = closeDrawer;
|
|
132
|
+
|
|
133
|
+
useEffect(() => {
|
|
134
|
+
if (!isDrawerOpen) {
|
|
135
|
+
dragY.setValue(0);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
// Hand `dragY` to the native driver up front. The open/close value is
|
|
139
|
+
// already native (the parent animates it that way), and a transform
|
|
140
|
+
// that mixes a native node with a JS-only one drops the JS updates on
|
|
141
|
+
// the floor — which is exactly a drag that moves nothing.
|
|
142
|
+
Animated.timing(dragY, {
|
|
143
|
+
toValue: 0,
|
|
144
|
+
duration: 0,
|
|
145
|
+
useNativeDriver: true,
|
|
146
|
+
}).start();
|
|
147
|
+
}, [isDrawerOpen, dragY]);
|
|
148
|
+
|
|
149
|
+
const openTranslateY = drawerAnimation.interpolate({
|
|
110
150
|
inputRange: [0, 1],
|
|
111
|
-
outputRange: [
|
|
151
|
+
outputRange: [sheetHeight, 0],
|
|
112
152
|
});
|
|
113
153
|
|
|
154
|
+
const dismissByDrag = () => {
|
|
155
|
+
Animated.timing(dragY, {
|
|
156
|
+
toValue: sheetHeightRef.current,
|
|
157
|
+
duration: 180,
|
|
158
|
+
useNativeDriver: true,
|
|
159
|
+
}).start();
|
|
160
|
+
closeDrawerRef.current();
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const makeDragResponder = (claimOnStart: boolean) =>
|
|
164
|
+
PanResponder.create({
|
|
165
|
+
// The grab area claims on touch-DOWN: nothing to negotiate against,
|
|
166
|
+
// so a pull on the handle always drags.
|
|
167
|
+
onStartShouldSetPanResponder: () => claimOnStart,
|
|
168
|
+
// CAPTURE matters on the body: a drag that starts on a row makes
|
|
169
|
+
// that row's Touchable the responder on touch-down, and a plain
|
|
170
|
+
// `onMoveShouldSetPanResponder` is never consulted afterwards.
|
|
171
|
+
// Capturing on the first clearly-vertical movement takes the gesture
|
|
172
|
+
// back, while a tap (dy ≈ 0) still reaches the row.
|
|
173
|
+
onMoveShouldSetPanResponderCapture: (_evt, g) =>
|
|
174
|
+
claimOnStart || shouldClaimVerticalDrag(g.dy, g.dx),
|
|
175
|
+
onMoveShouldSetPanResponder: (_evt, g) =>
|
|
176
|
+
claimOnStart || shouldClaimVerticalDrag(g.dy, g.dx),
|
|
177
|
+
onPanResponderTerminationRequest: () => false,
|
|
178
|
+
onPanResponderMove: (_evt, g) => {
|
|
179
|
+
dragY.setValue(Math.max(0, g.dy));
|
|
180
|
+
},
|
|
181
|
+
onPanResponderRelease: (_evt, g) => {
|
|
182
|
+
if (shouldDismissOnDrag(g.dy, g.vy)) {
|
|
183
|
+
dismissByDrag();
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
Animated.spring(dragY, {
|
|
187
|
+
toValue: 0,
|
|
188
|
+
tension: 90,
|
|
189
|
+
friction: 14,
|
|
190
|
+
useNativeDriver: true,
|
|
191
|
+
}).start();
|
|
192
|
+
},
|
|
193
|
+
onPanResponderTerminate: () => {
|
|
194
|
+
Animated.spring(dragY, {
|
|
195
|
+
toValue: 0,
|
|
196
|
+
tension: 90,
|
|
197
|
+
friction: 14,
|
|
198
|
+
useNativeDriver: true,
|
|
199
|
+
}).start();
|
|
200
|
+
},
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// Built once — `useRef(expr)` would re-run PanResponder.create every
|
|
204
|
+
// render and throw the result away.
|
|
205
|
+
const bodyPanRef = useRef<ReturnType<typeof makeDragResponder> | null>(null);
|
|
206
|
+
const handlePanRef = useRef<ReturnType<typeof makeDragResponder> | null>(null);
|
|
207
|
+
if (!bodyPanRef.current) {bodyPanRef.current = makeDragResponder(false);}
|
|
208
|
+
if (!handlePanRef.current) {handlePanRef.current = makeDragResponder(true);}
|
|
209
|
+
const pan = bodyPanRef.current;
|
|
210
|
+
const handlePan = handlePanRef.current;
|
|
211
|
+
|
|
114
212
|
const menuOptions = useMemo(() => {
|
|
115
213
|
const options: {
|
|
116
214
|
label: string;
|
|
@@ -122,7 +220,7 @@ export const HeaderRoomListMenu: FC<HeaderRoomListMenuProps> = ({
|
|
|
122
220
|
}[] = [
|
|
123
221
|
{
|
|
124
222
|
label: 'New Chat',
|
|
125
|
-
icon: <AddNewIcon color=
|
|
223
|
+
icon: <AddNewIcon color={primaryColor} />,
|
|
126
224
|
onClick: () => {
|
|
127
225
|
dispatch(setActiveModal(MODAL_TYPES.NEW_CHAT));
|
|
128
226
|
console.log('New chat clicked');
|
|
@@ -131,7 +229,7 @@ export const HeaderRoomListMenu: FC<HeaderRoomListMenuProps> = ({
|
|
|
131
229
|
},
|
|
132
230
|
{
|
|
133
231
|
label: 'Profile',
|
|
134
|
-
icon: <ProfileIcon color=
|
|
232
|
+
icon: <ProfileIcon color={primaryColor} />,
|
|
135
233
|
onClick: () => {
|
|
136
234
|
dispatch(setActiveModal(MODAL_TYPES.PROFILE));
|
|
137
235
|
console.log('Profile clicked');
|
|
@@ -139,7 +237,7 @@ export const HeaderRoomListMenu: FC<HeaderRoomListMenuProps> = ({
|
|
|
139
237
|
},
|
|
140
238
|
{
|
|
141
239
|
label: 'Settings',
|
|
142
|
-
icon: <SettingIcon color=
|
|
240
|
+
icon: <SettingIcon color={primaryColor} />,
|
|
143
241
|
onClick: () => {
|
|
144
242
|
dispatch(setActiveModal(MODAL_TYPES.SETTINGS));
|
|
145
243
|
console.log('Settings clicked');
|
|
@@ -162,47 +260,80 @@ export const HeaderRoomListMenu: FC<HeaderRoomListMenuProps> = ({
|
|
|
162
260
|
}, [dispatch, logoutConfig, performLogout, primaryColor, closeDrawer]);
|
|
163
261
|
|
|
164
262
|
return (
|
|
165
|
-
|
|
263
|
+
// Presented through a real <Modal>: as an in-tree overlay the sheet was
|
|
264
|
+
// bounded by the room list's own container, so it stopped short of the
|
|
265
|
+
// screen's bottom edge (that pale strip under it) and the dim never
|
|
266
|
+
// reached the host's chrome.
|
|
267
|
+
<Modal
|
|
268
|
+
transparent
|
|
269
|
+
visible={isDrawerOpen}
|
|
270
|
+
animationType="none"
|
|
271
|
+
statusBarTranslucent
|
|
272
|
+
onRequestClose={closeDrawer}
|
|
273
|
+
>
|
|
274
|
+
<TouchableWithoutFeedback onPress={closeDrawer}>
|
|
275
|
+
<Animated.View
|
|
276
|
+
style={[styles.overlay, { opacity: overlayAnimation }]}
|
|
277
|
+
/>
|
|
278
|
+
</TouchableWithoutFeedback>
|
|
279
|
+
{/* A bottom sheet rather than the old full-height right-hand drawer:
|
|
280
|
+
* three rows never needed the whole screen, and coming up from the
|
|
281
|
+
* bottom keeps them within thumb reach. Inset by 2pt all round so a
|
|
282
|
+
* sliver of the dimmed screen shows around it. */}
|
|
166
283
|
<Animated.View
|
|
284
|
+
testID="header-menu-sheet"
|
|
285
|
+
onLayout={(e) => {
|
|
286
|
+
const measured = e.nativeEvent.layout.height;
|
|
287
|
+
if (measured > 0) {setSheetHeight(measured);}
|
|
288
|
+
}}
|
|
167
289
|
style={[
|
|
168
|
-
styles.
|
|
169
|
-
{
|
|
290
|
+
styles.sheet,
|
|
291
|
+
{
|
|
292
|
+
paddingBottom: insets.bottom + 20,
|
|
293
|
+
// Two entries rather than Animated.add: the parent's value is
|
|
294
|
+
// driven natively, and stacking the transforms keeps each node
|
|
295
|
+
// on its own driver instead of mixing them in one expression.
|
|
296
|
+
transform: [{ translateY: openTranslateY }, { translateY: dragY }],
|
|
297
|
+
},
|
|
170
298
|
]}
|
|
299
|
+
{...pan.panHandlers}
|
|
171
300
|
>
|
|
172
|
-
{
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
>
|
|
184
|
-
<
|
|
185
|
-
style={
|
|
186
|
-
|
|
187
|
-
|
|
301
|
+
{/* Full-width grab area, not just the 40pt pill: it claims the
|
|
302
|
+
* gesture the moment it is touched. */}
|
|
303
|
+
<View
|
|
304
|
+
testID="header-menu-grabber"
|
|
305
|
+
style={styles.grabArea}
|
|
306
|
+
{...handlePan.panHandlers}
|
|
307
|
+
>
|
|
308
|
+
<View style={styles.grabber} />
|
|
309
|
+
</View>
|
|
310
|
+
<View style={styles.card}>
|
|
311
|
+
{menuOptions.map((option, index) => (
|
|
312
|
+
<View key={index} style={styles.menuItemWrapper}>
|
|
313
|
+
<TouchableOpacity
|
|
314
|
+
style={styles.menuItem}
|
|
315
|
+
testID={`header-menu-${option.label}`}
|
|
316
|
+
activeOpacity={0.6}
|
|
317
|
+
onPress={() => {
|
|
318
|
+
option.onClick();
|
|
319
|
+
if (!option.closesDrawer) {
|
|
320
|
+
closeDrawer();
|
|
321
|
+
}
|
|
188
322
|
}}
|
|
189
323
|
>
|
|
190
|
-
{option.icon}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
324
|
+
<View style={styles.iconSlot}>{option.icon}</View>
|
|
325
|
+
<Text style={[styles.label, option?.styles]}>
|
|
326
|
+
{option.label}
|
|
327
|
+
</Text>
|
|
328
|
+
</TouchableOpacity>
|
|
329
|
+
{index < menuOptions.length - 1 && (
|
|
330
|
+
<View style={styles.divider} />
|
|
331
|
+
)}
|
|
332
|
+
</View>
|
|
333
|
+
))}
|
|
334
|
+
</View>
|
|
197
335
|
</Animated.View>
|
|
198
|
-
|
|
199
|
-
<TouchableWithoutFeedback onPress={closeDrawer}>
|
|
200
|
-
<Animated.View
|
|
201
|
-
style={[styles.overlay, { opacity: overlayAnimation }]}
|
|
202
|
-
/>
|
|
203
|
-
</TouchableWithoutFeedback>
|
|
204
|
-
)}
|
|
205
|
-
</>
|
|
336
|
+
</Modal>
|
|
206
337
|
);
|
|
207
338
|
};
|
|
208
339
|
|
|
@@ -214,40 +345,54 @@ const styles = StyleSheet.create({
|
|
|
214
345
|
menuItem: {
|
|
215
346
|
flexDirection: 'row',
|
|
216
347
|
alignItems: 'center',
|
|
217
|
-
|
|
348
|
+
paddingVertical: 18,
|
|
349
|
+
paddingHorizontal: 16,
|
|
350
|
+
},
|
|
351
|
+
iconSlot: {
|
|
352
|
+
width: 26,
|
|
353
|
+
alignItems: 'center',
|
|
218
354
|
},
|
|
219
355
|
label: {
|
|
220
356
|
fontSize: 16,
|
|
221
|
-
|
|
357
|
+
fontWeight: '500',
|
|
358
|
+
color: '#141414',
|
|
359
|
+
marginLeft: 12,
|
|
222
360
|
},
|
|
223
361
|
divider: {
|
|
224
|
-
height:
|
|
225
|
-
|
|
226
|
-
backgroundColor: '#
|
|
362
|
+
height: StyleSheet.hairlineWidth,
|
|
363
|
+
marginLeft: 54,
|
|
364
|
+
backgroundColor: '#E4E4E9',
|
|
227
365
|
},
|
|
228
|
-
|
|
366
|
+
sheet: {
|
|
229
367
|
position: 'absolute',
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
368
|
+
// Inset rather than flush: the dimmed screen stays visible as a thin
|
|
369
|
+
// line around the sheet.
|
|
370
|
+
left: 3,
|
|
371
|
+
right: 3,
|
|
372
|
+
bottom: 3,
|
|
373
|
+
paddingTop: 8,
|
|
374
|
+
backgroundColor: '#FFFFFF',
|
|
375
|
+
borderRadius: 20,
|
|
236
376
|
shadowColor: '#000',
|
|
237
|
-
shadowOpacity: 0.
|
|
238
|
-
shadowOffset: { width:
|
|
239
|
-
shadowRadius:
|
|
240
|
-
elevation:
|
|
377
|
+
shadowOpacity: 0.12,
|
|
378
|
+
shadowOffset: { width: 0, height: -3 },
|
|
379
|
+
shadowRadius: 12,
|
|
380
|
+
elevation: 12,
|
|
241
381
|
zIndex: 999,
|
|
242
382
|
},
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
383
|
+
grabArea: {
|
|
384
|
+
alignItems: 'center',
|
|
385
|
+
paddingTop: 6,
|
|
386
|
+
paddingBottom: 12,
|
|
387
|
+
},
|
|
388
|
+
grabber: {
|
|
389
|
+
width: 40,
|
|
390
|
+
height: 4,
|
|
391
|
+
borderRadius: 2,
|
|
392
|
+
backgroundColor: '#C9CBD1',
|
|
247
393
|
},
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
marginBottom: 10,
|
|
394
|
+
card: {
|
|
395
|
+
overflow: 'hidden',
|
|
251
396
|
},
|
|
252
397
|
overlay: {
|
|
253
398
|
position: 'absolute',
|