stream-chat-react-native-core 9.0.0-beta.14 → 9.0.0-beta.16
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/lib/commonjs/components/AttachmentPicker/components/AttachmentPickerContent.js +1 -1
- package/lib/commonjs/components/AttachmentPicker/components/AttachmentPickerContent.js.map +1 -1
- package/lib/commonjs/components/AttachmentPicker/components/AttachmentTypePickerButton.js +1 -1
- package/lib/commonjs/components/AttachmentPicker/components/AttachmentTypePickerButton.js.map +1 -1
- package/lib/commonjs/components/Channel/Channel.js +62 -17
- package/lib/commonjs/components/Channel/Channel.js.map +1 -1
- package/lib/commonjs/components/ChannelList/hooks/useChannelActionItems.js +1 -1
- package/lib/commonjs/components/ChannelList/hooks/useChannelActionItems.js.map +1 -1
- package/lib/commonjs/components/ChannelPreview/ChannelDetailsBottomSheet.js +17 -2
- package/lib/commonjs/components/ChannelPreview/ChannelDetailsBottomSheet.js.map +1 -1
- package/lib/commonjs/components/ChannelPreview/ChannelPreviewMessage.js +5 -4
- package/lib/commonjs/components/ChannelPreview/ChannelPreviewMessage.js.map +1 -1
- package/lib/commonjs/components/ChannelPreview/ChannelSwipableWrapper.js +26 -31
- package/lib/commonjs/components/ChannelPreview/ChannelSwipableWrapper.js.map +1 -1
- package/lib/commonjs/components/KeyboardCompatibleView/KeyboardCompatibleView.js +65 -36
- package/lib/commonjs/components/KeyboardCompatibleView/KeyboardCompatibleView.js.map +1 -1
- package/lib/commonjs/components/KeyboardCompatibleView/KeyboardControllerAvoidingView.js +1 -1
- package/lib/commonjs/components/KeyboardCompatibleView/KeyboardControllerAvoidingView.js.map +1 -1
- package/lib/commonjs/components/Message/utils/messageActions.js +1 -1
- package/lib/commonjs/components/Message/utils/messageActions.js.map +1 -1
- package/lib/commonjs/components/MessageMenu/MessageUserReactions.js +3 -1
- package/lib/commonjs/components/MessageMenu/MessageUserReactions.js.map +1 -1
- package/lib/commonjs/components/UIComponents/BottomSheetModal.js +4 -2
- package/lib/commonjs/components/UIComponents/BottomSheetModal.js.map +1 -1
- package/lib/commonjs/icons/index.js +12 -0
- package/lib/commonjs/icons/index.js.map +1 -1
- package/lib/commonjs/store/OfflineDB.js +1 -0
- package/lib/commonjs/store/OfflineDB.js.map +1 -1
- package/lib/commonjs/store/apis/index.js +11 -0
- package/lib/commonjs/store/apis/index.js.map +1 -1
- package/lib/commonjs/store/apis/updatePendingTask.js +34 -0
- package/lib/commonjs/store/apis/updatePendingTask.js.map +1 -0
- package/lib/commonjs/version.json +1 -1
- package/lib/module/components/AttachmentPicker/components/AttachmentPickerContent.js +1 -1
- package/lib/module/components/AttachmentPicker/components/AttachmentPickerContent.js.map +1 -1
- package/lib/module/components/AttachmentPicker/components/AttachmentTypePickerButton.js +1 -1
- package/lib/module/components/AttachmentPicker/components/AttachmentTypePickerButton.js.map +1 -1
- package/lib/module/components/Channel/Channel.js +62 -17
- package/lib/module/components/Channel/Channel.js.map +1 -1
- package/lib/module/components/ChannelList/hooks/useChannelActionItems.js +1 -1
- package/lib/module/components/ChannelList/hooks/useChannelActionItems.js.map +1 -1
- package/lib/module/components/ChannelPreview/ChannelDetailsBottomSheet.js +17 -2
- package/lib/module/components/ChannelPreview/ChannelDetailsBottomSheet.js.map +1 -1
- package/lib/module/components/ChannelPreview/ChannelPreviewMessage.js +5 -4
- package/lib/module/components/ChannelPreview/ChannelPreviewMessage.js.map +1 -1
- package/lib/module/components/ChannelPreview/ChannelSwipableWrapper.js +26 -31
- package/lib/module/components/ChannelPreview/ChannelSwipableWrapper.js.map +1 -1
- package/lib/module/components/KeyboardCompatibleView/KeyboardCompatibleView.js +65 -36
- package/lib/module/components/KeyboardCompatibleView/KeyboardCompatibleView.js.map +1 -1
- package/lib/module/components/KeyboardCompatibleView/KeyboardControllerAvoidingView.js +1 -1
- package/lib/module/components/KeyboardCompatibleView/KeyboardControllerAvoidingView.js.map +1 -1
- package/lib/module/components/Message/utils/messageActions.js +1 -1
- package/lib/module/components/Message/utils/messageActions.js.map +1 -1
- package/lib/module/components/MessageMenu/MessageUserReactions.js +3 -1
- package/lib/module/components/MessageMenu/MessageUserReactions.js.map +1 -1
- package/lib/module/components/UIComponents/BottomSheetModal.js +4 -2
- package/lib/module/components/UIComponents/BottomSheetModal.js.map +1 -1
- package/lib/module/icons/index.js +12 -0
- package/lib/module/icons/index.js.map +1 -1
- package/lib/module/store/OfflineDB.js +1 -0
- package/lib/module/store/OfflineDB.js.map +1 -1
- package/lib/module/store/apis/index.js +11 -0
- package/lib/module/store/apis/index.js.map +1 -1
- package/lib/module/store/apis/updatePendingTask.js +34 -0
- package/lib/module/store/apis/updatePendingTask.js.map +1 -0
- package/lib/module/version.json +1 -1
- package/lib/typescript/components/Channel/Channel.d.ts.map +1 -1
- package/lib/typescript/components/ChannelPreview/ChannelDetailsBottomSheet.d.ts.map +1 -1
- package/lib/typescript/components/ChannelPreview/ChannelPreviewMessage.d.ts.map +1 -1
- package/lib/typescript/components/ChannelPreview/ChannelSwipableWrapper.d.ts.map +1 -1
- package/lib/typescript/components/KeyboardCompatibleView/KeyboardCompatibleView.d.ts +3 -2
- package/lib/typescript/components/KeyboardCompatibleView/KeyboardCompatibleView.d.ts.map +1 -1
- package/lib/typescript/components/KeyboardCompatibleView/KeyboardControllerAvoidingView.d.ts.map +1 -1
- package/lib/typescript/components/MessageMenu/MessageUserReactions.d.ts.map +1 -1
- package/lib/typescript/components/UIComponents/BottomSheetModal.d.ts.map +1 -1
- package/lib/typescript/icons/index.d.ts +1 -0
- package/lib/typescript/icons/index.d.ts.map +1 -1
- package/lib/typescript/store/OfflineDB.d.ts +1 -0
- package/lib/typescript/store/OfflineDB.d.ts.map +1 -1
- package/lib/typescript/store/apis/index.d.ts +1 -0
- package/lib/typescript/store/apis/index.d.ts.map +1 -1
- package/lib/typescript/store/apis/updatePendingTask.d.ts +3 -0
- package/lib/typescript/store/apis/updatePendingTask.d.ts.map +1 -0
- package/lib/typescript/store/mappers/mapTaskToStorable.d.ts +9 -0
- package/lib/typescript/store/mappers/mapTaskToStorable.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/offline-support/optimistic-update.js +225 -0
- package/src/components/AttachmentPicker/components/AttachmentPickerContent.tsx +2 -2
- package/src/components/AttachmentPicker/components/AttachmentTypePickerButton.tsx +2 -2
- package/src/components/Channel/Channel.tsx +50 -4
- package/src/components/ChannelList/hooks/__tests__/useChannelActionItems.test.tsx +2 -2
- package/src/components/ChannelList/hooks/useChannelActionItems.tsx +1 -1
- package/src/components/ChannelPreview/ChannelDetailsBottomSheet.tsx +15 -1
- package/src/components/ChannelPreview/ChannelPreviewMessage.tsx +7 -2
- package/src/components/ChannelPreview/ChannelSwipableWrapper.tsx +26 -29
- package/src/components/ChannelPreview/__tests__/ChannelSwipableWrapper.test.tsx +201 -0
- package/src/components/KeyboardCompatibleView/KeyboardCompatibleView.tsx +43 -25
- package/src/components/KeyboardCompatibleView/KeyboardControllerAvoidingView.tsx +1 -7
- package/src/components/Message/utils/messageActions.ts +1 -1
- package/src/components/MessageMenu/MessageUserReactions.tsx +3 -1
- package/src/components/MessageMenu/__tests__/MessageUserReactions.test.tsx +9 -2
- package/src/components/UIComponents/BottomSheetModal.tsx +4 -2
- package/src/icons/index.ts +1 -0
- package/src/store/OfflineDB.ts +2 -0
- package/src/store/apis/__tests__/updatePendingTask.test.ts +66 -0
- package/src/store/apis/index.ts +1 -0
- package/src/store/apis/updatePendingTask.ts +24 -0
- package/src/version.json +1 -1
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Text } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import { act, render } from '@testing-library/react-native';
|
|
5
|
+
import type { Channel } from 'stream-chat';
|
|
6
|
+
|
|
7
|
+
import type { ChannelActionItem } from '../../ChannelList/hooks/useChannelActionItems';
|
|
8
|
+
import * as ChannelActionItemsModule from '../../ChannelList/hooks/useChannelActionItems';
|
|
9
|
+
import * as ChannelActionsModule from '../../ChannelList/hooks/useChannelActions';
|
|
10
|
+
import { ChannelSwipableWrapper } from '../ChannelSwipableWrapper';
|
|
11
|
+
import * as UseIsChannelMutedModule from '../hooks/useIsChannelMuted';
|
|
12
|
+
|
|
13
|
+
const rightActionsProbe = {
|
|
14
|
+
items: [] as Array<{ action: () => void; id: string }>,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const mockSwipableWrapper = jest.fn(
|
|
18
|
+
({
|
|
19
|
+
children,
|
|
20
|
+
swipableProps,
|
|
21
|
+
}: React.PropsWithChildren<{
|
|
22
|
+
swipableProps?: { renderRightActions?: (...args: never[]) => void };
|
|
23
|
+
}>) => {
|
|
24
|
+
const rightActions = swipableProps?.renderRightActions?.({} as never, {} as never);
|
|
25
|
+
return (
|
|
26
|
+
<>
|
|
27
|
+
{children}
|
|
28
|
+
{rightActions}
|
|
29
|
+
</>
|
|
30
|
+
);
|
|
31
|
+
},
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
jest.mock('../../../contexts', () => ({
|
|
35
|
+
useTheme: () => ({
|
|
36
|
+
theme: {
|
|
37
|
+
semantics: {
|
|
38
|
+
accentPrimary: '#00f',
|
|
39
|
+
backgroundCoreSurface: '#fff',
|
|
40
|
+
textOnAccent: '#000',
|
|
41
|
+
textPrimary: '#111',
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
}),
|
|
45
|
+
}));
|
|
46
|
+
|
|
47
|
+
jest.mock('../../../contexts/swipeableContext/SwipeRegistryContext', () => ({
|
|
48
|
+
useSwipeRegistryContext: () => ({
|
|
49
|
+
closeAll: jest.fn(),
|
|
50
|
+
}),
|
|
51
|
+
}));
|
|
52
|
+
|
|
53
|
+
jest.mock('../../UIComponents', () => ({
|
|
54
|
+
BottomSheetModal: ({ children }: React.PropsWithChildren) => <>{children}</>,
|
|
55
|
+
RightActions: ({ items }: { items: Array<{ action: () => void; id: string }> }) => {
|
|
56
|
+
rightActionsProbe.items = items;
|
|
57
|
+
return null;
|
|
58
|
+
},
|
|
59
|
+
SwipableWrapper: (...args: unknown[]) => mockSwipableWrapper(...args),
|
|
60
|
+
}));
|
|
61
|
+
|
|
62
|
+
describe('ChannelSwipableWrapper', () => {
|
|
63
|
+
const channel = { cid: 'messaging:test-channel', id: 'test-channel' } as Channel;
|
|
64
|
+
|
|
65
|
+
beforeEach(() => {
|
|
66
|
+
jest.clearAllMocks();
|
|
67
|
+
rightActionsProbe.items = [];
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('uses channel mute for direct-channel swipe actions and keeps mute user in the sheet', () => {
|
|
71
|
+
const muteChannel = jest.fn();
|
|
72
|
+
const unmuteChannel = jest.fn();
|
|
73
|
+
const customBottomSheet = jest.fn(() => null);
|
|
74
|
+
const items: ChannelActionItem[] = [
|
|
75
|
+
{
|
|
76
|
+
Icon: () => null,
|
|
77
|
+
action: jest.fn(),
|
|
78
|
+
id: 'mute',
|
|
79
|
+
label: 'Mute User',
|
|
80
|
+
placement: 'sheet',
|
|
81
|
+
type: 'standard',
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
Icon: () => null,
|
|
85
|
+
action: jest.fn(),
|
|
86
|
+
id: 'archive',
|
|
87
|
+
label: 'Archive Chat',
|
|
88
|
+
placement: 'sheet',
|
|
89
|
+
type: 'standard',
|
|
90
|
+
},
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
jest.spyOn(ChannelActionsModule, 'useChannelActions').mockReturnValue({
|
|
94
|
+
archive: jest.fn(),
|
|
95
|
+
blockUser: jest.fn(),
|
|
96
|
+
deleteChannel: jest.fn(),
|
|
97
|
+
leave: jest.fn(),
|
|
98
|
+
muteChannel,
|
|
99
|
+
muteUser: jest.fn(),
|
|
100
|
+
pin: jest.fn(),
|
|
101
|
+
unarchive: jest.fn(),
|
|
102
|
+
unblockUser: jest.fn(),
|
|
103
|
+
unmuteChannel,
|
|
104
|
+
unmuteUser: jest.fn(),
|
|
105
|
+
unpin: jest.fn(),
|
|
106
|
+
});
|
|
107
|
+
jest.spyOn(ChannelActionItemsModule, 'useChannelActionItems').mockReturnValue(items);
|
|
108
|
+
jest.spyOn(UseIsChannelMutedModule, 'useIsChannelMuted').mockReturnValue({
|
|
109
|
+
createdAt: null,
|
|
110
|
+
expiresAt: null,
|
|
111
|
+
muted: false,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
render(
|
|
115
|
+
<ChannelSwipableWrapper channel={channel} ChannelDetailsBottomSheet={customBottomSheet}>
|
|
116
|
+
<Text>child</Text>
|
|
117
|
+
</ChannelSwipableWrapper>,
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
expect(customBottomSheet).toHaveBeenCalledWith(
|
|
121
|
+
expect.objectContaining({
|
|
122
|
+
channel,
|
|
123
|
+
items,
|
|
124
|
+
}),
|
|
125
|
+
undefined,
|
|
126
|
+
);
|
|
127
|
+
expect(rightActionsProbe.items.map((item) => item.id)).toEqual(['openSheet', 'mute']);
|
|
128
|
+
|
|
129
|
+
act(() => {
|
|
130
|
+
rightActionsProbe.items[1].action();
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
expect(muteChannel).toHaveBeenCalledTimes(1);
|
|
134
|
+
expect(unmuteChannel).not.toHaveBeenCalled();
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('removes mute group from the sheet while keeping mute as the quick swipe action', () => {
|
|
138
|
+
const muteChannel = jest.fn();
|
|
139
|
+
const customBottomSheet = jest.fn(() => null);
|
|
140
|
+
const muteItem = {
|
|
141
|
+
Icon: () => null,
|
|
142
|
+
action: jest.fn(),
|
|
143
|
+
id: 'mute',
|
|
144
|
+
label: 'Mute Group',
|
|
145
|
+
placement: 'swipe',
|
|
146
|
+
type: 'standard',
|
|
147
|
+
} as const satisfies ChannelActionItem;
|
|
148
|
+
const archiveItem = {
|
|
149
|
+
Icon: () => null,
|
|
150
|
+
action: jest.fn(),
|
|
151
|
+
id: 'archive',
|
|
152
|
+
label: 'Archive Group',
|
|
153
|
+
placement: 'both',
|
|
154
|
+
type: 'standard',
|
|
155
|
+
} as const satisfies ChannelActionItem;
|
|
156
|
+
|
|
157
|
+
jest.spyOn(ChannelActionsModule, 'useChannelActions').mockReturnValue({
|
|
158
|
+
archive: jest.fn(),
|
|
159
|
+
blockUser: jest.fn(),
|
|
160
|
+
deleteChannel: jest.fn(),
|
|
161
|
+
leave: jest.fn(),
|
|
162
|
+
muteChannel,
|
|
163
|
+
muteUser: jest.fn(),
|
|
164
|
+
pin: jest.fn(),
|
|
165
|
+
unarchive: jest.fn(),
|
|
166
|
+
unblockUser: jest.fn(),
|
|
167
|
+
unmuteChannel: jest.fn(),
|
|
168
|
+
unmuteUser: jest.fn(),
|
|
169
|
+
unpin: jest.fn(),
|
|
170
|
+
});
|
|
171
|
+
jest
|
|
172
|
+
.spyOn(ChannelActionItemsModule, 'useChannelActionItems')
|
|
173
|
+
.mockReturnValue([muteItem, archiveItem]);
|
|
174
|
+
jest.spyOn(UseIsChannelMutedModule, 'useIsChannelMuted').mockReturnValue({
|
|
175
|
+
createdAt: null,
|
|
176
|
+
expiresAt: null,
|
|
177
|
+
muted: false,
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
render(
|
|
181
|
+
<ChannelSwipableWrapper channel={channel} ChannelDetailsBottomSheet={customBottomSheet}>
|
|
182
|
+
<Text>child</Text>
|
|
183
|
+
</ChannelSwipableWrapper>,
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
expect(customBottomSheet).toHaveBeenCalledWith(
|
|
187
|
+
expect.objectContaining({
|
|
188
|
+
channel,
|
|
189
|
+
items: [archiveItem],
|
|
190
|
+
}),
|
|
191
|
+
undefined,
|
|
192
|
+
);
|
|
193
|
+
expect(rightActionsProbe.items.map((item) => item.id)).toEqual(['openSheet', 'mute']);
|
|
194
|
+
|
|
195
|
+
act(() => {
|
|
196
|
+
rightActionsProbe.items[1].action();
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
expect(muteChannel).toHaveBeenCalledTimes(1);
|
|
200
|
+
});
|
|
201
|
+
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import {
|
|
3
|
+
AccessibilityInfo,
|
|
3
4
|
AppState,
|
|
4
5
|
AppStateStatus,
|
|
5
6
|
Dimensions,
|
|
@@ -39,7 +40,7 @@ export class KeyboardCompatibleView extends React.Component<
|
|
|
39
40
|
KeyboardAvoidingViewProps,
|
|
40
41
|
'behavior' | 'enabled' | 'keyboardVerticalOffset'
|
|
41
42
|
> = {
|
|
42
|
-
behavior:
|
|
43
|
+
behavior: 'padding',
|
|
43
44
|
enabled: true,
|
|
44
45
|
keyboardVerticalOffset: Platform.OS === 'ios' ? 86.5 : -300, // default MessageComposer height
|
|
45
46
|
};
|
|
@@ -62,35 +63,40 @@ export class KeyboardCompatibleView extends React.Component<
|
|
|
62
63
|
this.viewRef = React.createRef();
|
|
63
64
|
}
|
|
64
65
|
|
|
65
|
-
_relativeKeyboardHeight(keyboardFrame: KeyboardMetrics) {
|
|
66
|
+
async _relativeKeyboardHeight(keyboardFrame: KeyboardMetrics): Promise<number> {
|
|
66
67
|
const frame = this._frame;
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
68
|
+
if (!frame || !keyboardFrame) {
|
|
69
|
+
return 0;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (
|
|
73
|
+
Platform.OS === 'ios' &&
|
|
74
|
+
keyboardFrame.screenY === 0 &&
|
|
75
|
+
(await AccessibilityInfo.prefersCrossFadeTransitions())
|
|
76
|
+
) {
|
|
73
77
|
return 0;
|
|
74
78
|
}
|
|
75
79
|
|
|
76
80
|
const keyboardY = keyboardFrame.screenY - (this.props.keyboardVerticalOffset ?? 0);
|
|
77
|
-
const relativeHeight = frame.y + frame.height - keyboardY;
|
|
78
81
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
82
|
+
if (this.props.behavior === 'height') {
|
|
83
|
+
return Math.max(this.state.bottom + frame.y + frame.height - keyboardY, 0);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Calculate the displacement needed for the view such that it
|
|
87
|
+
// no longer overlaps with the keyboard
|
|
88
|
+
const relativeHeight = Math.max(frame.y + frame.height - keyboardY, 0);
|
|
89
|
+
|
|
84
90
|
if (Platform.OS === 'android') {
|
|
85
91
|
const barHeights = Dimensions.get('screen').height - Dimensions.get('window').height;
|
|
86
|
-
|
|
92
|
+
const systemInsetFloor = Math.max(barHeights, StatusBar.currentHeight ?? 0);
|
|
93
|
+
|
|
94
|
+
if (relativeHeight <= systemInsetFloor) {
|
|
87
95
|
return 0;
|
|
88
96
|
}
|
|
89
97
|
}
|
|
90
98
|
|
|
91
|
-
|
|
92
|
-
// no longer overlaps with the keyboard
|
|
93
|
-
return Math.max(relativeHeight, 0);
|
|
99
|
+
return relativeHeight;
|
|
94
100
|
}
|
|
95
101
|
|
|
96
102
|
_onKeyboardChange: KeyboardEventListener = (event) => {
|
|
@@ -98,7 +104,12 @@ export class KeyboardCompatibleView extends React.Component<
|
|
|
98
104
|
this._updateBottomIfNecessary();
|
|
99
105
|
};
|
|
100
106
|
|
|
101
|
-
|
|
107
|
+
_onKeyboardHide: KeyboardEventListener = () => {
|
|
108
|
+
this._keyboardEvent = null;
|
|
109
|
+
this._updateBottomIfNecessary();
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
_onLayout: (event: LayoutChangeEvent) => void = async (event) => {
|
|
102
113
|
event.persist();
|
|
103
114
|
|
|
104
115
|
const oldFrame = this._frame;
|
|
@@ -110,7 +121,7 @@ export class KeyboardCompatibleView extends React.Component<
|
|
|
110
121
|
|
|
111
122
|
// update bottom height for the first time or when the height is changed
|
|
112
123
|
if (!oldFrame || oldFrame.height !== this._frame.height) {
|
|
113
|
-
this._updateBottomIfNecessary();
|
|
124
|
+
await this._updateBottomIfNecessary();
|
|
114
125
|
}
|
|
115
126
|
|
|
116
127
|
if (this.props.onLayout) {
|
|
@@ -127,14 +138,14 @@ export class KeyboardCompatibleView extends React.Component<
|
|
|
127
138
|
}
|
|
128
139
|
};
|
|
129
140
|
|
|
130
|
-
_updateBottomIfNecessary = () => {
|
|
141
|
+
_updateBottomIfNecessary = async () => {
|
|
131
142
|
if (this._keyboardEvent == null) {
|
|
132
143
|
this._setBottom(0);
|
|
133
144
|
return;
|
|
134
145
|
}
|
|
135
146
|
|
|
136
147
|
const { duration, easing, endCoordinates } = this._keyboardEvent;
|
|
137
|
-
const height = this._relativeKeyboardHeight(endCoordinates);
|
|
148
|
+
const height = await this._relativeKeyboardHeight(endCoordinates);
|
|
138
149
|
|
|
139
150
|
if (this._bottom === height) {
|
|
140
151
|
return;
|
|
@@ -142,7 +153,8 @@ export class KeyboardCompatibleView extends React.Component<
|
|
|
142
153
|
|
|
143
154
|
this._setBottom(height);
|
|
144
155
|
|
|
145
|
-
|
|
156
|
+
const enabled = this.props.enabled ?? true;
|
|
157
|
+
if (enabled && duration && easing) {
|
|
146
158
|
LayoutAnimation.configureNext({
|
|
147
159
|
// We have to pass the duration equal to minimal accepted duration defined here: RCTLayoutAnimation.m
|
|
148
160
|
duration: duration > 10 ? duration : 10,
|
|
@@ -169,11 +181,12 @@ export class KeyboardCompatibleView extends React.Component<
|
|
|
169
181
|
setKeyboardListeners = () => {
|
|
170
182
|
if (Platform.OS === 'ios') {
|
|
171
183
|
this._subscriptions = [
|
|
172
|
-
Keyboard.addListener('
|
|
184
|
+
Keyboard.addListener('keyboardWillHide', this._onKeyboardHide),
|
|
185
|
+
Keyboard.addListener('keyboardWillShow', this._onKeyboardChange),
|
|
173
186
|
];
|
|
174
187
|
} else {
|
|
175
188
|
this._subscriptions = [
|
|
176
|
-
Keyboard.addListener('keyboardDidHide', this.
|
|
189
|
+
Keyboard.addListener('keyboardDidHide', this._onKeyboardHide),
|
|
177
190
|
Keyboard.addListener('keyboardDidShow', this._onKeyboardChange),
|
|
178
191
|
];
|
|
179
192
|
}
|
|
@@ -218,6 +231,11 @@ export class KeyboardCompatibleView extends React.Component<
|
|
|
218
231
|
}
|
|
219
232
|
|
|
220
233
|
componentDidMount() {
|
|
234
|
+
if (!Keyboard.isVisible()) {
|
|
235
|
+
this._keyboardEvent = null;
|
|
236
|
+
this._setBottom(0);
|
|
237
|
+
}
|
|
238
|
+
|
|
221
239
|
this._appStateSubscription = AppState.addEventListener('change', this._handleAppStateChange);
|
|
222
240
|
this.setKeyboardListeners();
|
|
223
241
|
}
|
|
@@ -2,7 +2,6 @@ import React, { useEffect } from 'react';
|
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
Keyboard,
|
|
5
|
-
Platform,
|
|
6
5
|
KeyboardAvoidingViewProps as ReactNativeKeyboardAvoidingViewProps,
|
|
7
6
|
} from 'react-native';
|
|
8
7
|
|
|
@@ -51,12 +50,7 @@ export const KeyboardCompatibleView = (props: KeyboardCompatibleViewProps) => {
|
|
|
51
50
|
</KeyboardProvider>
|
|
52
51
|
);
|
|
53
52
|
}
|
|
54
|
-
const compatibleBehavior =
|
|
55
|
-
behavior === 'translate-with-padding'
|
|
56
|
-
? Platform.OS === 'ios'
|
|
57
|
-
? 'padding'
|
|
58
|
-
: 'position'
|
|
59
|
-
: behavior;
|
|
53
|
+
const compatibleBehavior = behavior === 'translate-with-padding' ? 'padding' : behavior;
|
|
60
54
|
|
|
61
55
|
return (
|
|
62
56
|
<KeyboardCompatibleViewDefault behavior={compatibleBehavior} {...rest}>
|
|
@@ -125,7 +125,9 @@ export const MessageUserReactions = (props: MessageUserReactionsProps) => {
|
|
|
125
125
|
const MessageUserReactionsItem = propMessageUserReactionsItem ?? contextMessageUserReactionsItem;
|
|
126
126
|
|
|
127
127
|
const onSelectReaction = useStableCallback((reactionType: string) => {
|
|
128
|
-
setSelectedReaction(
|
|
128
|
+
setSelectedReaction((currentReaction) =>
|
|
129
|
+
currentReaction === reactionType ? undefined : reactionType,
|
|
130
|
+
);
|
|
129
131
|
});
|
|
130
132
|
|
|
131
133
|
useEffect(() => {
|
|
@@ -100,14 +100,21 @@ describe('MessageUserReactions when the supportedReactions are defined', () => {
|
|
|
100
100
|
expect(reactionButtons[1].props.accessibilityLabel).toBe('reaction-button-love-unselected');
|
|
101
101
|
});
|
|
102
102
|
|
|
103
|
-
it('
|
|
103
|
+
it('toggles the selected reaction when a reaction button is pressed twice', () => {
|
|
104
104
|
const { getAllByLabelText } = renderComponent();
|
|
105
|
-
|
|
105
|
+
let reactionButtons = getAllByLabelText(/\breaction-button[^\s]+/);
|
|
106
106
|
|
|
107
107
|
fireEvent.press(reactionButtons[1]);
|
|
108
108
|
|
|
109
109
|
expect(reactionButtons[0].props.accessibilityLabel).toBe('reaction-button-like-unselected');
|
|
110
110
|
expect(reactionButtons[1].props.accessibilityLabel).toBe('reaction-button-love-selected');
|
|
111
|
+
|
|
112
|
+
fireEvent.press(reactionButtons[1]);
|
|
113
|
+
|
|
114
|
+
reactionButtons = getAllByLabelText(/\breaction-button[^\s]+/);
|
|
115
|
+
|
|
116
|
+
expect(reactionButtons[0].props.accessibilityLabel).toBe('reaction-button-like-unselected');
|
|
117
|
+
expect(reactionButtons[1].props.accessibilityLabel).toBe('reaction-button-love-unselected');
|
|
111
118
|
});
|
|
112
119
|
|
|
113
120
|
it('renders reactions list', () => {
|
|
@@ -242,8 +242,10 @@ export const BottomSheetModal = (props: PropsWithChildren<BottomSheetModalProps>
|
|
|
242
242
|
KeyboardControllerPackage.KeyboardEvents.addListener('keyboardDidHide', keyboardDidHide),
|
|
243
243
|
);
|
|
244
244
|
} else {
|
|
245
|
-
|
|
246
|
-
|
|
245
|
+
if (Platform.OS === 'ios') {
|
|
246
|
+
listeners.push(Keyboard.addListener('keyboardWillShow', keyboardDidShowRN));
|
|
247
|
+
listeners.push(Keyboard.addListener('keyboardWillHide', keyboardDidHide));
|
|
248
|
+
}
|
|
247
249
|
}
|
|
248
250
|
|
|
249
251
|
return () => listeners.forEach((l) => l.remove());
|
package/src/icons/index.ts
CHANGED
package/src/store/OfflineDB.ts
CHANGED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
2
|
+
|
|
3
|
+
import { addPendingTask, getPendingTasks, updatePendingTask } from '..';
|
|
4
|
+
import { generateMessage } from '../../../mock-builders/generator/message';
|
|
5
|
+
import { BetterSqlite } from '../../../test-utils/BetterSqlite';
|
|
6
|
+
import { SqliteClient } from '../../SqliteClient';
|
|
7
|
+
|
|
8
|
+
describe('updatePendingTask', () => {
|
|
9
|
+
beforeEach(async () => {
|
|
10
|
+
await SqliteClient.initializeDatabase();
|
|
11
|
+
await BetterSqlite.openDB();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
BetterSqlite.dropAllTables();
|
|
16
|
+
BetterSqlite.closeDB();
|
|
17
|
+
jest.clearAllMocks();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should replace an existing pending task row by id without changing its createdAt ordering', async () => {
|
|
21
|
+
const channelId = uuidv4();
|
|
22
|
+
const originalMessage = generateMessage({
|
|
23
|
+
cid: `messaging:${channelId}`,
|
|
24
|
+
id: uuidv4(),
|
|
25
|
+
text: 'original text',
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
await addPendingTask({
|
|
29
|
+
channelId,
|
|
30
|
+
channelType: 'messaging',
|
|
31
|
+
messageId: originalMessage.id,
|
|
32
|
+
payload: [originalMessage, {}],
|
|
33
|
+
type: 'send-message',
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const [originalRow] = await BetterSqlite.selectFromTable('pendingTasks');
|
|
37
|
+
const [originalTask] = await getPendingTasks({ messageId: originalMessage.id });
|
|
38
|
+
|
|
39
|
+
const editedMessage = {
|
|
40
|
+
...originalMessage,
|
|
41
|
+
text: 'edited text',
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
await updatePendingTask({
|
|
45
|
+
id: originalTask.id,
|
|
46
|
+
task: {
|
|
47
|
+
channelId,
|
|
48
|
+
channelType: 'messaging',
|
|
49
|
+
messageId: originalMessage.id,
|
|
50
|
+
payload: [editedMessage, {}],
|
|
51
|
+
type: 'send-message',
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const [updatedRow] = await BetterSqlite.selectFromTable('pendingTasks');
|
|
56
|
+
const [updatedTask] = await getPendingTasks({ messageId: originalMessage.id });
|
|
57
|
+
|
|
58
|
+
expect(updatedRow.id).toBe(originalRow.id);
|
|
59
|
+
expect(updatedRow.createdAt).toBe(originalRow.createdAt);
|
|
60
|
+
expect(updatedRow.type).toBe('send-message');
|
|
61
|
+
expect(JSON.parse(updatedRow.payload)[0].text).toBe('edited text');
|
|
62
|
+
expect(updatedTask.id).toBe(originalTask.id);
|
|
63
|
+
expect(updatedTask.type).toBe('send-message');
|
|
64
|
+
expect(updatedTask.payload[0].text).toBe('edited text');
|
|
65
|
+
});
|
|
66
|
+
});
|
package/src/store/apis/index.ts
CHANGED
|
@@ -13,6 +13,7 @@ export * from './getLastSyncedAt';
|
|
|
13
13
|
export * from './getMembers';
|
|
14
14
|
export * from './getReads';
|
|
15
15
|
export * from './updateMessage';
|
|
16
|
+
export * from './updatePendingTask';
|
|
16
17
|
export * from './updateReaction';
|
|
17
18
|
export * from './insertReaction';
|
|
18
19
|
export * from './deleteReaction';
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { DBUpdatePendingTaskType } from 'stream-chat';
|
|
2
|
+
|
|
3
|
+
import { mapTaskToStorable } from '../mappers/mapTaskToStorable';
|
|
4
|
+
import { createUpdateQuery } from '../sqlite-utils/createUpdateQuery';
|
|
5
|
+
import { SqliteClient } from '../SqliteClient';
|
|
6
|
+
|
|
7
|
+
export const updatePendingTask = async ({ id, task }: DBUpdatePendingTaskType) => {
|
|
8
|
+
const storableTask = mapTaskToStorable(task);
|
|
9
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
10
|
+
const { createdAt, id: taskId, ...nextTask } = storableTask;
|
|
11
|
+
|
|
12
|
+
const query = createUpdateQuery('pendingTasks', nextTask, {
|
|
13
|
+
id,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
SqliteClient.logger?.('info', 'updatePendingTask', {
|
|
17
|
+
id,
|
|
18
|
+
task: nextTask,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
await SqliteClient.executeSql.apply(null, query);
|
|
22
|
+
|
|
23
|
+
return [query];
|
|
24
|
+
};
|
package/src/version.json
CHANGED