@droppii-org/chat-mobile 0.2.44 → 0.2.46
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/module/core/index.js +2 -0
- package/lib/module/core/index.js.map +1 -1
- package/lib/module/core/useChatListener.js +7 -2
- package/lib/module/core/useChatListener.js.map +1 -1
- package/lib/module/core/useNetworkStatusSync.js +14 -0
- package/lib/module/core/useNetworkStatusSync.js.map +1 -0
- package/lib/module/hooks/message/usePinMessage.js +12 -8
- package/lib/module/hooks/message/usePinMessage.js.map +1 -1
- package/lib/module/screens/chat-detail/ChatComposer.js +87 -69
- package/lib/module/screens/chat-detail/ChatComposer.js.map +1 -1
- package/lib/module/screens/chat-detail/ChatDetail.js +39 -34
- package/lib/module/screens/chat-detail/ChatDetail.js.map +1 -1
- package/lib/module/screens/chat-detail/ChatListLegend.js +16 -8
- package/lib/module/screens/chat-detail/ChatListLegend.js.map +1 -1
- package/lib/module/screens/chat-detail/hooks/useChatComposerAnimation.js +7 -65
- package/lib/module/screens/chat-detail/hooks/useChatComposerAnimation.js.map +1 -1
- package/lib/module/screens/chat-detail/legend/LegendChatMessage.js +6 -1
- package/lib/module/screens/chat-detail/legend/LegendChatMessage.js.map +1 -1
- package/lib/module/store/pinnedMessage.js +10 -5
- package/lib/module/store/pinnedMessage.js.map +1 -1
- package/lib/module/translation/resources/i18n.js +1 -1
- package/lib/module/translation/resources/i18n.js.map +1 -1
- package/lib/typescript/src/core/index.d.ts.map +1 -1
- package/lib/typescript/src/core/useChatListener.d.ts.map +1 -1
- package/lib/typescript/src/core/useNetworkStatusSync.d.ts +2 -0
- package/lib/typescript/src/core/useNetworkStatusSync.d.ts.map +1 -0
- package/lib/typescript/src/hooks/message/usePinMessage.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/ChatComposer.d.ts +5 -1
- package/lib/typescript/src/screens/chat-detail/ChatComposer.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/ChatDetail.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/ChatListLegend.d.ts +15 -1
- package/lib/typescript/src/screens/chat-detail/ChatListLegend.d.ts.map +1 -1
- package/lib/typescript/src/screens/chat-detail/hooks/useChatComposerAnimation.d.ts +2 -3
- package/lib/typescript/src/screens/chat-detail/hooks/useChatComposerAnimation.d.ts.map +1 -1
- package/lib/typescript/src/store/pinnedMessage.d.ts +2 -1
- package/lib/typescript/src/store/pinnedMessage.d.ts.map +1 -1
- package/package.json +4 -2
- package/src/core/index.ts +2 -0
- package/src/core/useChatListener.ts +14 -1
- package/src/core/useNetworkStatusSync.ts +13 -0
- package/src/hooks/message/usePinMessage.ts +16 -8
- package/src/screens/chat-detail/ChatComposer.tsx +132 -99
- package/src/screens/chat-detail/ChatDetail.tsx +41 -37
- package/src/screens/chat-detail/ChatListLegend.tsx +36 -7
- package/src/screens/chat-detail/hooks/useChatComposerAnimation.ts +11 -106
- package/src/screens/chat-detail/legend/LegendChatMessage.tsx +6 -1
- package/src/store/pinnedMessage.ts +20 -6
- package/src/translation/resources/i18n.ts +1 -1
|
@@ -1,22 +1,15 @@
|
|
|
1
|
-
import { useCallback, useEffect
|
|
1
|
+
import { useCallback, useEffect } from 'react';
|
|
2
2
|
import { Keyboard } from 'react-native';
|
|
3
3
|
import {
|
|
4
|
-
runOnJS,
|
|
5
|
-
useAnimatedReaction,
|
|
6
4
|
useAnimatedStyle,
|
|
7
5
|
useSharedValue,
|
|
8
6
|
withTiming,
|
|
9
7
|
} from 'react-native-reanimated';
|
|
10
|
-
import {
|
|
11
|
-
useKeyboardState,
|
|
12
|
-
useReanimatedKeyboardAnimation,
|
|
13
|
-
} from 'react-native-keyboard-controller';
|
|
8
|
+
import { useKeyboardState } from 'react-native-keyboard-controller';
|
|
14
9
|
|
|
15
10
|
const ACCESSORY_ANIMATION_MS = 250;
|
|
16
|
-
const ACCESSORY_SETTLE_MS = 150;
|
|
17
11
|
|
|
18
12
|
interface UseChatComposerAnimationProps {
|
|
19
|
-
isAttachmentOpen: boolean;
|
|
20
13
|
panelHeight: number;
|
|
21
14
|
onAttachmentOpenChange: (isOpen: boolean) => void;
|
|
22
15
|
}
|
|
@@ -26,23 +19,17 @@ interface UseChatComposerAnimationReturn {
|
|
|
26
19
|
accessorySlotStyle: any;
|
|
27
20
|
toggleAttachmentVisibility: () => void;
|
|
28
21
|
openAttachment: () => void;
|
|
29
|
-
closeAttachment: () => void;
|
|
22
|
+
closeAttachment: (speedUp?: boolean) => void;
|
|
30
23
|
}
|
|
31
24
|
|
|
32
25
|
export const useChatComposerAnimation = ({
|
|
33
|
-
isAttachmentOpen,
|
|
34
26
|
panelHeight,
|
|
35
27
|
onAttachmentOpenChange,
|
|
36
28
|
}: UseChatComposerAnimationProps): UseChatComposerAnimationReturn => {
|
|
37
|
-
const isSwitchingToAttachmentRef = useRef(false);
|
|
38
29
|
const panelHeightSV = useSharedValue(panelHeight);
|
|
39
30
|
const attachmentHeightSV = useSharedValue(0);
|
|
40
|
-
const switchKeyboardHeightSV = useSharedValue(0);
|
|
41
|
-
const isKeyboardToAttachmentSwitchSV = useSharedValue(false);
|
|
42
31
|
|
|
43
32
|
const isKeyboardVisible = useKeyboardState((state) => state.isVisible);
|
|
44
|
-
const keyboardHeight = useKeyboardState((state) => state.height);
|
|
45
|
-
const { progress: keyboardProgress } = useReanimatedKeyboardAnimation();
|
|
46
33
|
|
|
47
34
|
useEffect(() => {
|
|
48
35
|
panelHeightSV.value = panelHeight;
|
|
@@ -55,92 +42,18 @@ export const useChatComposerAnimation = ({
|
|
|
55
42
|
onAttachmentOpenChange(true);
|
|
56
43
|
}, [attachmentHeightSV, panelHeight, onAttachmentOpenChange]);
|
|
57
44
|
|
|
58
|
-
const closeAttachment = useCallback(
|
|
59
|
-
|
|
60
|
-
duration: ACCESSORY_ANIMATION_MS,
|
|
61
|
-
});
|
|
62
|
-
onAttachmentOpenChange(false);
|
|
63
|
-
}, [attachmentHeightSV, onAttachmentOpenChange]);
|
|
64
|
-
|
|
65
|
-
const finishKeyboardToAttachmentSwitch = useCallback(() => {
|
|
66
|
-
if (!isSwitchingToAttachmentRef.current) {
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
isSwitchingToAttachmentRef.current = false;
|
|
71
|
-
isKeyboardToAttachmentSwitchSV.value = false;
|
|
72
|
-
|
|
73
|
-
const targetHeight = panelHeightSV.value;
|
|
74
|
-
const startHeight = switchKeyboardHeightSV.value;
|
|
75
|
-
|
|
76
|
-
if (Math.abs(startHeight - targetHeight) < 1) {
|
|
77
|
-
attachmentHeightSV.value = targetHeight;
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
attachmentHeightSV.value = withTiming(targetHeight, {
|
|
82
|
-
duration: ACCESSORY_SETTLE_MS,
|
|
83
|
-
});
|
|
84
|
-
}, [
|
|
85
|
-
attachmentHeightSV,
|
|
86
|
-
isKeyboardToAttachmentSwitchSV,
|
|
87
|
-
panelHeightSV,
|
|
88
|
-
switchKeyboardHeightSV,
|
|
89
|
-
]);
|
|
90
|
-
|
|
91
|
-
useEffect(() => {
|
|
92
|
-
if (!isKeyboardVisible && isSwitchingToAttachmentRef.current) {
|
|
93
|
-
finishKeyboardToAttachmentSwitch();
|
|
94
|
-
}
|
|
95
|
-
}, [finishKeyboardToAttachmentSwitch, isKeyboardVisible]);
|
|
96
|
-
|
|
97
|
-
useAnimatedReaction(
|
|
98
|
-
() => ({
|
|
99
|
-
switching: isKeyboardToAttachmentSwitchSV.value,
|
|
100
|
-
progress: keyboardProgress.value,
|
|
101
|
-
}),
|
|
102
|
-
(current, previous) => {
|
|
103
|
-
if (
|
|
104
|
-
current.switching &&
|
|
105
|
-
current.progress === 0 &&
|
|
106
|
-
(previous?.progress ?? 1) > 0
|
|
107
|
-
) {
|
|
108
|
-
runOnJS(finishKeyboardToAttachmentSwitch)();
|
|
109
|
-
}
|
|
110
|
-
},
|
|
111
|
-
[finishKeyboardToAttachmentSwitch]
|
|
112
|
-
);
|
|
113
|
-
|
|
114
|
-
useEffect(() => {
|
|
115
|
-
// Close panel when keyboard becomes visible (unless doing keyboard↔panel switch)
|
|
116
|
-
// Don't depend on closeAttachment to avoid triggering effect when callback changes
|
|
117
|
-
if (
|
|
118
|
-
!isSwitchingToAttachmentRef.current &&
|
|
119
|
-
isKeyboardVisible &&
|
|
120
|
-
isAttachmentOpen
|
|
121
|
-
) {
|
|
45
|
+
const closeAttachment = useCallback(
|
|
46
|
+
(speedUp = false) => {
|
|
122
47
|
attachmentHeightSV.value = withTiming(0, {
|
|
123
|
-
duration: ACCESSORY_ANIMATION_MS,
|
|
48
|
+
duration: ACCESSORY_ANIMATION_MS / (speedUp ? 2 : 1),
|
|
124
49
|
});
|
|
125
50
|
onAttachmentOpenChange(false);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
isKeyboardVisible,
|
|
130
|
-
attachmentHeightSV,
|
|
131
|
-
onAttachmentOpenChange,
|
|
132
|
-
]);
|
|
51
|
+
},
|
|
52
|
+
[attachmentHeightSV, onAttachmentOpenChange]
|
|
53
|
+
);
|
|
133
54
|
|
|
134
55
|
const accessorySlotStyle = useAnimatedStyle(() => {
|
|
135
|
-
|
|
136
|
-
return {
|
|
137
|
-
height: switchKeyboardHeightSV.value * (1 - keyboardProgress.value),
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
return {
|
|
142
|
-
height: attachmentHeightSV.value,
|
|
143
|
-
};
|
|
56
|
+
return { height: attachmentHeightSV.value };
|
|
144
57
|
});
|
|
145
58
|
|
|
146
59
|
const toggleAttachmentVisibility = useCallback(() => {
|
|
@@ -152,24 +65,16 @@ export const useChatComposerAnimation = ({
|
|
|
152
65
|
return;
|
|
153
66
|
}
|
|
154
67
|
|
|
155
|
-
if (isKeyboardVisible
|
|
156
|
-
// Dismiss keyboard first, then open attachment
|
|
68
|
+
if (isKeyboardVisible) {
|
|
157
69
|
Keyboard.dismiss();
|
|
158
|
-
isSwitchingToAttachmentRef.current = true;
|
|
159
|
-
switchKeyboardHeightSV.value = keyboardHeight;
|
|
160
|
-
isKeyboardToAttachmentSwitchSV.value = true;
|
|
161
70
|
onAttachmentOpenChange(true);
|
|
162
|
-
return;
|
|
163
71
|
}
|
|
164
72
|
|
|
165
73
|
openAttachment();
|
|
166
74
|
}, [
|
|
167
75
|
closeAttachment,
|
|
168
76
|
isKeyboardVisible,
|
|
169
|
-
keyboardHeight,
|
|
170
|
-
isKeyboardToAttachmentSwitchSV,
|
|
171
77
|
onAttachmentOpenChange,
|
|
172
|
-
switchKeyboardHeightSV,
|
|
173
78
|
openAttachment,
|
|
174
79
|
attachmentHeightSV,
|
|
175
80
|
]);
|
|
@@ -134,7 +134,12 @@ const styles = StyleSheet.create({
|
|
|
134
134
|
},
|
|
135
135
|
pinBadge: {
|
|
136
136
|
position: 'absolute',
|
|
137
|
-
|
|
137
|
+
// Capped at the inter-row gap (messageRowLeft/Right's marginBottom) so the
|
|
138
|
+
// badge never overlaps the row above: LegendList's non-sticky item cells
|
|
139
|
+
// don't carry a zIndex, so cross-row paint order follows recycled view-slot
|
|
140
|
+
// order, not visual position — any real overlap can end up hidden behind
|
|
141
|
+
// the row above depending on scroll history.
|
|
142
|
+
top: -4,
|
|
138
143
|
right: -8,
|
|
139
144
|
width: 24,
|
|
140
145
|
height: 20,
|
|
@@ -4,7 +4,12 @@ import { create } from 'zustand';
|
|
|
4
4
|
export interface PinnedMessageStore {
|
|
5
5
|
messages: MessageItem[];
|
|
6
6
|
totalCount: number;
|
|
7
|
-
|
|
7
|
+
remainingPinCount: number;
|
|
8
|
+
setMessages: (
|
|
9
|
+
messages: MessageItem[],
|
|
10
|
+
totalCount: number,
|
|
11
|
+
remainingPinCount: number
|
|
12
|
+
) => void;
|
|
8
13
|
addMessage: (message: MessageItem) => void;
|
|
9
14
|
removeMessage: (clientMsgID: string) => void;
|
|
10
15
|
reset: () => void;
|
|
@@ -14,25 +19,34 @@ export const usePinnedMessageStore = create<PinnedMessageStore>()(
|
|
|
14
19
|
(set, get) => ({
|
|
15
20
|
messages: [],
|
|
16
21
|
totalCount: 0,
|
|
22
|
+
remainingPinCount: 0,
|
|
17
23
|
|
|
18
|
-
setMessages: (messages, totalCount) => {
|
|
19
|
-
set({ messages, totalCount });
|
|
24
|
+
setMessages: (messages, totalCount, remainingPinCount) => {
|
|
25
|
+
set({ messages, totalCount, remainingPinCount });
|
|
20
26
|
},
|
|
21
27
|
|
|
22
28
|
addMessage: (message) => {
|
|
23
29
|
const messages = [message, ...get().messages];
|
|
24
|
-
set({
|
|
30
|
+
set({
|
|
31
|
+
messages,
|
|
32
|
+
totalCount: messages.length,
|
|
33
|
+
remainingPinCount: Math.max(0, get().remainingPinCount - 1),
|
|
34
|
+
});
|
|
25
35
|
},
|
|
26
36
|
|
|
27
37
|
removeMessage: (clientMsgID) => {
|
|
28
38
|
const messages = get().messages.filter(
|
|
29
39
|
(msg) => msg.clientMsgID !== clientMsgID
|
|
30
40
|
);
|
|
31
|
-
set({
|
|
41
|
+
set({
|
|
42
|
+
messages,
|
|
43
|
+
totalCount: messages.length,
|
|
44
|
+
remainingPinCount: get().remainingPinCount + 1,
|
|
45
|
+
});
|
|
32
46
|
},
|
|
33
47
|
|
|
34
48
|
reset: () => {
|
|
35
|
-
set({ messages: [], totalCount: 0 });
|
|
49
|
+
set({ messages: [], totalCount: 0, remainingPinCount: 0 });
|
|
36
50
|
},
|
|
37
51
|
})
|
|
38
52
|
);
|
|
@@ -46,7 +46,7 @@ const i18nKey: Record<string, string> = {
|
|
|
46
46
|
'chat:pinned_link_subtitle': 'Từ {{sender}}',
|
|
47
47
|
'chat:pin_limit_title': 'Không thể ghim thêm tin nhắn',
|
|
48
48
|
'chat:pin_limit_desc':
|
|
49
|
-
'Chỉ được ghim tối đa
|
|
49
|
+
'Chỉ được ghim tối đa {{maxCount}} tin nhắn. Vui lòng bỏ ghim một tin nhắn hiện có trước khi ghim tin nhắn mới.',
|
|
50
50
|
'chat:pin_limit_primary': 'Đã hiểu',
|
|
51
51
|
'chat:pinned_messages_title': 'Tin nhắn đã ghim',
|
|
52
52
|
'chat:pinned_messages_hint':
|