@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.
Files changed (48) hide show
  1. package/lib/module/core/index.js +2 -0
  2. package/lib/module/core/index.js.map +1 -1
  3. package/lib/module/core/useChatListener.js +7 -2
  4. package/lib/module/core/useChatListener.js.map +1 -1
  5. package/lib/module/core/useNetworkStatusSync.js +14 -0
  6. package/lib/module/core/useNetworkStatusSync.js.map +1 -0
  7. package/lib/module/hooks/message/usePinMessage.js +12 -8
  8. package/lib/module/hooks/message/usePinMessage.js.map +1 -1
  9. package/lib/module/screens/chat-detail/ChatComposer.js +87 -69
  10. package/lib/module/screens/chat-detail/ChatComposer.js.map +1 -1
  11. package/lib/module/screens/chat-detail/ChatDetail.js +39 -34
  12. package/lib/module/screens/chat-detail/ChatDetail.js.map +1 -1
  13. package/lib/module/screens/chat-detail/ChatListLegend.js +16 -8
  14. package/lib/module/screens/chat-detail/ChatListLegend.js.map +1 -1
  15. package/lib/module/screens/chat-detail/hooks/useChatComposerAnimation.js +7 -65
  16. package/lib/module/screens/chat-detail/hooks/useChatComposerAnimation.js.map +1 -1
  17. package/lib/module/screens/chat-detail/legend/LegendChatMessage.js +6 -1
  18. package/lib/module/screens/chat-detail/legend/LegendChatMessage.js.map +1 -1
  19. package/lib/module/store/pinnedMessage.js +10 -5
  20. package/lib/module/store/pinnedMessage.js.map +1 -1
  21. package/lib/module/translation/resources/i18n.js +1 -1
  22. package/lib/module/translation/resources/i18n.js.map +1 -1
  23. package/lib/typescript/src/core/index.d.ts.map +1 -1
  24. package/lib/typescript/src/core/useChatListener.d.ts.map +1 -1
  25. package/lib/typescript/src/core/useNetworkStatusSync.d.ts +2 -0
  26. package/lib/typescript/src/core/useNetworkStatusSync.d.ts.map +1 -0
  27. package/lib/typescript/src/hooks/message/usePinMessage.d.ts.map +1 -1
  28. package/lib/typescript/src/screens/chat-detail/ChatComposer.d.ts +5 -1
  29. package/lib/typescript/src/screens/chat-detail/ChatComposer.d.ts.map +1 -1
  30. package/lib/typescript/src/screens/chat-detail/ChatDetail.d.ts.map +1 -1
  31. package/lib/typescript/src/screens/chat-detail/ChatListLegend.d.ts +15 -1
  32. package/lib/typescript/src/screens/chat-detail/ChatListLegend.d.ts.map +1 -1
  33. package/lib/typescript/src/screens/chat-detail/hooks/useChatComposerAnimation.d.ts +2 -3
  34. package/lib/typescript/src/screens/chat-detail/hooks/useChatComposerAnimation.d.ts.map +1 -1
  35. package/lib/typescript/src/store/pinnedMessage.d.ts +2 -1
  36. package/lib/typescript/src/store/pinnedMessage.d.ts.map +1 -1
  37. package/package.json +4 -2
  38. package/src/core/index.ts +2 -0
  39. package/src/core/useChatListener.ts +14 -1
  40. package/src/core/useNetworkStatusSync.ts +13 -0
  41. package/src/hooks/message/usePinMessage.ts +16 -8
  42. package/src/screens/chat-detail/ChatComposer.tsx +132 -99
  43. package/src/screens/chat-detail/ChatDetail.tsx +41 -37
  44. package/src/screens/chat-detail/ChatListLegend.tsx +36 -7
  45. package/src/screens/chat-detail/hooks/useChatComposerAnimation.ts +11 -106
  46. package/src/screens/chat-detail/legend/LegendChatMessage.tsx +6 -1
  47. package/src/store/pinnedMessage.ts +20 -6
  48. package/src/translation/resources/i18n.ts +1 -1
@@ -1,22 +1,15 @@
1
- import { useCallback, useEffect, useRef } from 'react';
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
- attachmentHeightSV.value = withTiming(0, {
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
- isAttachmentOpen,
129
- isKeyboardVisible,
130
- attachmentHeightSV,
131
- onAttachmentOpenChange,
132
- ]);
51
+ },
52
+ [attachmentHeightSV, onAttachmentOpenChange]
53
+ );
133
54
 
134
55
  const accessorySlotStyle = useAnimatedStyle(() => {
135
- if (isKeyboardToAttachmentSwitchSV.value) {
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 && keyboardHeight > 0) {
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
- top: -8,
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
- setMessages: (messages: MessageItem[], totalCount: number) => void;
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({ messages, totalCount: messages.length });
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({ messages, totalCount: messages.length });
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 5 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.',
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':