@droppii-org/chat-mobile 0.2.2 → 0.2.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.
Files changed (95) hide show
  1. package/lib/module/assets/images/icon_droppii.png +0 -0
  2. package/lib/module/assets/images/index.js +2 -1
  3. package/lib/module/assets/images/index.js.map +1 -1
  4. package/lib/module/components/Avatar/SingleAvatar.js +26 -6
  5. package/lib/module/components/Avatar/SingleAvatar.js.map +1 -1
  6. package/lib/module/components/ThreadCard/NamePrefixIcon.js +20 -4
  7. package/lib/module/components/ThreadCard/NamePrefixIcon.js.map +1 -1
  8. package/lib/module/components/ThreadCard/ThreadCard.js +3 -1
  9. package/lib/module/components/ThreadCard/ThreadCard.js.map +1 -1
  10. package/lib/module/context/ChatContext.js +31 -0
  11. package/lib/module/context/ChatContext.js.map +1 -0
  12. package/lib/module/context/index.js +4 -0
  13. package/lib/module/context/index.js.map +1 -0
  14. package/lib/module/core/useChatListener.js +0 -5
  15. package/lib/module/core/useChatListener.js.map +1 -1
  16. package/lib/module/hooks/useChatMessages.js +4 -8
  17. package/lib/module/hooks/useChatMessages.js.map +1 -1
  18. package/lib/module/index.js +1 -0
  19. package/lib/module/index.js.map +1 -1
  20. package/lib/module/screens/chat-detail/ChatComposer.js +6 -5
  21. package/lib/module/screens/chat-detail/ChatComposer.js.map +1 -1
  22. package/lib/module/screens/chat-detail/ChatDetail.js +11 -1
  23. package/lib/module/screens/chat-detail/ChatDetail.js.map +1 -1
  24. package/lib/module/screens/chat-detail/ChatDetailHeader.js +43 -20
  25. package/lib/module/screens/chat-detail/ChatDetailHeader.js.map +1 -1
  26. package/lib/module/screens/inbox/MessagesTab.js +13 -0
  27. package/lib/module/screens/inbox/MessagesTab.js.map +1 -1
  28. package/lib/module/services/apis.js +2 -0
  29. package/lib/module/services/apis.js.map +1 -1
  30. package/lib/module/services/message.js +4 -2
  31. package/lib/module/services/message.js.map +1 -1
  32. package/lib/module/store/conversation.js +17 -20
  33. package/lib/module/store/conversation.js.map +1 -1
  34. package/lib/module/types/chat.js +5 -0
  35. package/lib/module/types/chat.js.map +1 -1
  36. package/lib/module/utils/conversation.js +6 -17
  37. package/lib/module/utils/conversation.js.map +1 -1
  38. package/lib/module/utils/message.js +34 -12
  39. package/lib/module/utils/message.js.map +1 -1
  40. package/lib/typescript/src/assets/images/index.d.ts +1 -0
  41. package/lib/typescript/src/assets/images/index.d.ts.map +1 -1
  42. package/lib/typescript/src/components/Avatar/SingleAvatar.d.ts +7 -6
  43. package/lib/typescript/src/components/Avatar/SingleAvatar.d.ts.map +1 -1
  44. package/lib/typescript/src/components/ThreadCard/NamePrefixIcon.d.ts.map +1 -1
  45. package/lib/typescript/src/context/ChatContext.d.ts +5 -0
  46. package/lib/typescript/src/context/ChatContext.d.ts.map +1 -0
  47. package/lib/typescript/src/context/index.d.ts +2 -0
  48. package/lib/typescript/src/context/index.d.ts.map +1 -0
  49. package/lib/typescript/src/core/useChatListener.d.ts.map +1 -1
  50. package/lib/typescript/src/hooks/useChatMessages.d.ts +1 -3
  51. package/lib/typescript/src/hooks/useChatMessages.d.ts.map +1 -1
  52. package/lib/typescript/src/index.d.ts +1 -0
  53. package/lib/typescript/src/index.d.ts.map +1 -1
  54. package/lib/typescript/src/screens/chat-detail/ChatComposer.d.ts.map +1 -1
  55. package/lib/typescript/src/screens/chat-detail/ChatDetail.d.ts +1 -1
  56. package/lib/typescript/src/screens/chat-detail/ChatDetail.d.ts.map +1 -1
  57. package/lib/typescript/src/screens/chat-detail/ChatDetailHeader.d.ts +1 -1
  58. package/lib/typescript/src/screens/chat-detail/ChatDetailHeader.d.ts.map +1 -1
  59. package/lib/typescript/src/screens/chat-detail/types.d.ts +4 -2
  60. package/lib/typescript/src/screens/chat-detail/types.d.ts.map +1 -1
  61. package/lib/typescript/src/screens/inbox/MessagesTab.d.ts.map +1 -1
  62. package/lib/typescript/src/services/apis.d.ts.map +1 -1
  63. package/lib/typescript/src/services/message.d.ts +0 -2
  64. package/lib/typescript/src/services/message.d.ts.map +1 -1
  65. package/lib/typescript/src/store/conversation.d.ts +1 -0
  66. package/lib/typescript/src/store/conversation.d.ts.map +1 -1
  67. package/lib/typescript/src/types/chat.d.ts +14 -2
  68. package/lib/typescript/src/types/chat.d.ts.map +1 -1
  69. package/lib/typescript/src/utils/conversation.d.ts +5 -4
  70. package/lib/typescript/src/utils/conversation.d.ts.map +1 -1
  71. package/lib/typescript/src/utils/message.d.ts +6 -2
  72. package/lib/typescript/src/utils/message.d.ts.map +1 -1
  73. package/package.json +2 -2
  74. package/src/assets/images/icon_droppii.png +0 -0
  75. package/src/assets/images/index.ts +1 -0
  76. package/src/build-ignore.d.ts +3 -0
  77. package/src/components/Avatar/SingleAvatar.tsx +40 -17
  78. package/src/components/ThreadCard/NamePrefixIcon.tsx +19 -7
  79. package/src/components/ThreadCard/ThreadCard.tsx +1 -1
  80. package/src/context/ChatContext.tsx +23 -0
  81. package/src/context/index.ts +1 -0
  82. package/src/core/useChatListener.ts +0 -11
  83. package/src/hooks/useChatMessages.ts +26 -38
  84. package/src/index.tsx +1 -0
  85. package/src/screens/chat-detail/ChatComposer.tsx +9 -9
  86. package/src/screens/chat-detail/ChatDetail.tsx +11 -1
  87. package/src/screens/chat-detail/ChatDetailHeader.tsx +58 -26
  88. package/src/screens/chat-detail/types.ts +4 -2
  89. package/src/screens/inbox/MessagesTab.tsx +11 -1
  90. package/src/services/apis.ts +2 -0
  91. package/src/services/message.ts +8 -7
  92. package/src/store/conversation.ts +25 -21
  93. package/src/types/chat.ts +20 -2
  94. package/src/utils/conversation.ts +6 -29
  95. package/src/utils/message.ts +36 -30
@@ -6,7 +6,8 @@ import {
6
6
  KLabel,
7
7
  KColors,
8
8
  } from '@droppii/libs';
9
- import { Avatar } from '../../components/Avatar';
9
+ import { AvatarSection } from '../../components/ThreadCard/AvatarSection';
10
+ import { NamePrefixIcon } from '../../components/ThreadCard/NamePrefixIcon';
10
11
  import type { ChatDetailHeaderProps } from './types';
11
12
 
12
13
  const ChatDetailHeader = memo(
@@ -15,6 +16,9 @@ const ChatDetailHeader = memo(
15
16
  subtitle,
16
17
  avatarUri,
17
18
  avatarFullName,
19
+ chatType,
20
+ chatCategory,
21
+ applicationType,
18
22
  showAddMember = false,
19
23
  onBack,
20
24
  onPressSearch,
@@ -58,7 +62,7 @@ const ChatDetailHeader = memo(
58
62
 
59
63
  const leftNode = useMemo(
60
64
  () => (
61
- <KContainer.View row alignItems marginR="0.25rem">
65
+ <KContainer.View row alignItems flex marginR="0.25rem">
62
66
  <KContainer.Touchable onPress={onBack} height={40} width={40} center>
63
67
  <KImage.VectorIcons
64
68
  name="arrow-left-o"
@@ -67,41 +71,69 @@ const ChatDetailHeader = memo(
67
71
  />
68
72
  </KContainer.Touchable>
69
73
 
70
- <Avatar
71
- source={avatarUri}
72
- fullName={avatarFullName ?? title}
73
- size="md"
74
+ <KContainer.Touchable
75
+ row
76
+ alignItems
77
+ flex
74
78
  onPress={onPressAvatar}
75
- />
79
+ activeOpacity={0.7}
80
+ >
81
+ <AvatarSection
82
+ avatar={avatarUri ?? null}
83
+ fullName={avatarFullName ?? title}
84
+ chatCategory={chatCategory}
85
+ applicationType={applicationType}
86
+ chatType={chatType}
87
+ />
88
+
89
+ <KContainer.View flex marginL="0.5rem">
90
+ <KContainer.View row alignItems>
91
+ <NamePrefixIcon
92
+ chatCategory={chatCategory}
93
+ chatType={chatType}
94
+ />
95
+ <KLabel.Text
96
+ typo="TextLgBold"
97
+ color={KColors.gray.dark}
98
+ numberOfLines={1}
99
+ flex
100
+ >
101
+ {title}
102
+ </KLabel.Text>
103
+ </KContainer.View>
104
+ {!!subtitle && (
105
+ <KLabel.Text
106
+ typo="TextSmNormal"
107
+ color={KColors.palette.gray.w500}
108
+ numberOfLines={1}
109
+ >
110
+ {subtitle}
111
+ </KLabel.Text>
112
+ )}
113
+ </KContainer.View>
114
+ </KContainer.Touchable>
76
115
  </KContainer.View>
77
116
  ),
78
- [avatarFullName, avatarUri, onBack, onPressAvatar, title]
117
+ [
118
+ avatarFullName,
119
+ avatarUri,
120
+ chatCategory,
121
+ applicationType,
122
+ chatType,
123
+ onBack,
124
+ onPressAvatar,
125
+ subtitle,
126
+ title,
127
+ ]
79
128
  );
80
129
 
81
- const subTitleNode = useMemo(() => {
82
- if (!subtitle) {
83
- return null;
84
- }
85
-
86
- return (
87
- <KLabel.Text
88
- typo="TextSmNormal"
89
- color={KColors.palette.gray.w500}
90
- numberOfLines={1}
91
- >
92
- {subtitle}
93
- </KLabel.Text>
94
- );
95
- }, [subtitle]);
96
-
97
130
  return (
98
131
  <KNavigation.Header
99
132
  theme="light"
100
133
  alignment="left"
101
134
  size="small"
102
135
  leftNode={leftNode}
103
- title={title}
104
- subTitle={subTitleNode}
136
+ disableCenterSpace
105
137
  initRightButtons={rightButtons}
106
138
  shadow
107
139
  />
@@ -1,6 +1,6 @@
1
1
  import type { ReactNode } from 'react';
2
- import type { DChatType } from '../../types/chat';
3
- import type { DMessageItem } from '../../types/chat';
2
+ import type { DChatType, DChatCategory } from '../../types/chat';
3
+ import type { DConversationItem, DMessageItem } from '../../types/chat';
4
4
 
5
5
  export type DChatActionIconProvider = 'MaterialCommunityIcons' | 'DroppiiNew';
6
6
 
@@ -23,6 +23,8 @@ export interface ChatDetailHeaderProps {
23
23
  avatarUri?: string | null;
24
24
  avatarFullName?: string;
25
25
  chatType?: DChatType;
26
+ chatCategory?: DChatCategory;
27
+ applicationType?: DConversationItem['applicationType'];
26
28
  showAddMember?: boolean;
27
29
  onBack?: () => void;
28
30
  onPressSearch?: () => void;
@@ -1,5 +1,5 @@
1
1
  import { memo, useCallback } from 'react';
2
- import { GestureResponderEvent } from 'react-native';
2
+ import { GestureResponderEvent, ActivityIndicator } from 'react-native';
3
3
  import { KContainer, KDivider, KLabel, KColors } from '@droppii/libs';
4
4
  import { useConversationList } from '../../hooks/useConversationList';
5
5
  import { ThreadCard } from '../../components/ThreadCard';
@@ -36,6 +36,12 @@ export const MessagesTab = memo(
36
36
  [handleThreadPress]
37
37
  );
38
38
 
39
+ const LoadingComponent = (
40
+ <KContainer.View center flex paddingV="3rem">
41
+ <ActivityIndicator size="large" color={KColors.palette.primary.w400} />
42
+ </KContainer.View>
43
+ );
44
+
39
45
  const EmptyComponent = isLoading ? null : (
40
46
  <KContainer.View center flex paddingV="3rem">
41
47
  <KLabel.Text typo="TextSmNormal" color={KColors.palette.gray.w400}>
@@ -44,6 +50,10 @@ export const MessagesTab = memo(
44
50
  </KContainer.View>
45
51
  );
46
52
 
53
+ if (isLoading && data.length === 0) {
54
+ return LoadingComponent;
55
+ }
56
+
47
57
  return (
48
58
  <KContainer.FlatList
49
59
  data={data}
@@ -6,6 +6,7 @@ import type {
6
6
  import OpenIMSDK, { LoginStatus } from '@droppii/openim-rn-client-sdk';
7
7
  import type { BaseResponse, GetOpenIMTokenResponse } from '../types/auth';
8
8
  import { Platform } from 'react-native';
9
+ import { useConversationStore } from '../store';
9
10
 
10
11
  let apiInstance: AxiosInstance | null = null;
11
12
 
@@ -38,6 +39,7 @@ export namespace ChatAPI {
38
39
  };
39
40
 
40
41
  export const logout = async (operationID?: string) => {
42
+ useConversationStore.getState().reset();
41
43
  return OpenIMSDK.logout(operationID);
42
44
  };
43
45
 
@@ -4,6 +4,8 @@ import OpenIMSDK, {
4
4
  type MessageItem,
5
5
  } from '@droppii/openim-rn-client-sdk';
6
6
  import type { DMessageItem } from '../types/chat';
7
+ import { useConversationStore } from '../store';
8
+ import { getReceiverId } from '../utils/message';
7
9
 
8
10
  const DEFAULT_PAGE_SIZE = 20;
9
11
 
@@ -28,21 +30,20 @@ export namespace ChatMessageAPI {
28
30
  // ...(options?.startClientMsgID
29
31
  // ? { lastMinSeq: options.lastMinSeq ?? 0 }
30
32
  // : {}),
31
- } as Parameters<
32
- typeof OpenIMSDK.getAdvancedHistoryMessageList
33
- >[0]) as Promise<HistoryMessageResult>;
33
+ }) as Promise<HistoryMessageResult>;
34
34
  }
35
35
 
36
36
  export async function sendTextMessage(params: {
37
37
  text: string;
38
- recvID?: string;
39
- groupID?: string;
40
38
  }): Promise<DMessageItem> {
39
+ const currentConversation = useConversationStore
40
+ .getState()
41
+ .getCurrentConversation();
42
+
41
43
  const message = await OpenIMSDK.createTextMessage(params.text);
42
44
 
43
45
  const sentMessage = await OpenIMSDK.sendMessage({
44
- recvID: params.recvID ?? '',
45
- groupID: params.groupID ?? '',
46
+ ...getReceiverId(currentConversation),
46
47
  message,
47
48
  });
48
49
 
@@ -1,6 +1,6 @@
1
1
  import { create } from 'zustand';
2
2
  import {
3
- dConversationCompare,
3
+ sortConversation,
4
4
  mergeOpenIMIntoConversation,
5
5
  } from '../utils/conversation';
6
6
  import type { DConversationItem } from '../types/chat';
@@ -27,6 +27,7 @@ export interface ConversationStore {
27
27
  mergeOpenIMConversations(conversations: ConversationItem[]): boolean;
28
28
  removeConversation(id: ConversationID): void;
29
29
  updateLastMessage(id: ConversationID, message: MessageItem): void;
30
+ reset(): void;
30
31
  }
31
32
 
32
33
  export const useConversationStore = create<ConversationStore>()((set, get) => ({
@@ -43,20 +44,17 @@ export const useConversationStore = create<ConversationStore>()((set, get) => ({
43
44
  return get().map[id];
44
45
  },
45
46
  updateConversations(conversations) {
46
- const newMap = conversations.reduce(
47
- (pre, curr) => {
48
- pre[curr.conversationId] = curr;
49
- return pre;
50
- },
51
- { ...get().map }
47
+ set(
48
+ sortConversation(
49
+ conversations.reduce(
50
+ (pre, curr) => {
51
+ pre[curr.conversationId] = curr;
52
+ return pre;
53
+ },
54
+ { ...get().map }
55
+ )
56
+ )
52
57
  );
53
- const newList = Object.keys(newMap).sort((a, b) =>
54
- dConversationCompare(newMap[a]!, newMap[b]!)
55
- );
56
- set({
57
- map: newMap,
58
- list: newList,
59
- });
60
58
  },
61
59
  mergeOpenIMConversations(conversations) {
62
60
  const currentMap = get().map;
@@ -78,10 +76,7 @@ export const useConversationStore = create<ConversationStore>()((set, get) => ({
78
76
  }
79
77
 
80
78
  if (changed) {
81
- const newList = Object.keys(nextMap).sort((a, b) =>
82
- dConversationCompare(nextMap[a]!, nextMap[b]!)
83
- );
84
- set({ map: nextMap, list: newList });
79
+ set(sortConversation(nextMap));
85
80
  }
86
81
 
87
82
  if (hasUnknown) {
@@ -102,11 +97,20 @@ export const useConversationStore = create<ConversationStore>()((set, get) => ({
102
97
  updateLastMessage(id, message) {
103
98
  const conversation = get().map[id];
104
99
  if (conversation == null) return;
105
- set({
106
- map: {
100
+
101
+ set(
102
+ sortConversation({
107
103
  [id]: { ...conversation, lastMessage: message },
108
104
  ...get().map,
109
- },
105
+ })
106
+ );
107
+ },
108
+ reset() {
109
+ set({
110
+ currentConversationId: undefined,
111
+ list: [],
112
+ map: {},
113
+ newConversationSignal: 0,
110
114
  });
111
115
  },
112
116
  }));
package/src/types/chat.ts CHANGED
@@ -4,11 +4,29 @@ import {
4
4
  type MessageReceiveOptType,
5
5
  type GroupAtType,
6
6
  } from '@droppii/openim-rn-client-sdk';
7
+ import type { PropsWithChildren } from 'react';
7
8
 
8
- export interface ChatProviderProps {
9
- children: React.ReactNode;
9
+ export const EventProvider = {
10
+ ga: 'ga',
11
+ appsflyer: 'appsflyer',
12
+ netcore: 'netcore',
13
+ } as const;
14
+
15
+ type LogGA = (
16
+ event: string,
17
+ params?: any,
18
+ provider?: `${keyof typeof EventProvider}`
19
+ ) => void;
20
+
21
+ export interface ChatContextType {
22
+ logGA?: LogGA;
10
23
  }
11
24
 
25
+ export type ChatProviderProps = PropsWithChildren<{
26
+ enabled?: boolean;
27
+ logGA?: LogGA;
28
+ }>;
29
+
12
30
  export enum DChatApplicationType {
13
31
  BIZ = 'BIZ',
14
32
  MALL = 'MALL',
@@ -2,11 +2,7 @@ import {
2
2
  SessionType,
3
3
  type ConversationItem,
4
4
  } from '@droppii/openim-rn-client-sdk';
5
- import {
6
- type DConversationItem,
7
- type DMessageItem,
8
- DChatCategory,
9
- } from '../types/chat';
5
+ import { type DConversationItem, type DMessageItem } from '../types/chat';
10
6
 
11
7
  export const conversationCompare = (
12
8
  a: ConversationItem,
@@ -79,28 +75,9 @@ export const getConversationID = (
79
75
  return [prefix, senderID, receiverID].filter(Boolean).join('_');
80
76
  };
81
77
 
82
- export const isBotCrmChat = (chatCategory: DChatCategory) =>
83
- chatCategory === DChatCategory.BIZ_BOT_CRM ||
84
- chatCategory === DChatCategory.BIZ_BOT_PDP;
85
-
86
- export const resolveDisplayName = (
87
- item: DConversationItem,
88
- t: (key: string) => string
89
- ) => {
90
- if (isBotCrmChat(item.chatCategory)) {
91
- const botName = item.peer.botName?.trim();
92
- if (botName) return botName;
93
- return t('thread_card_bot_crm_name');
94
- }
95
-
96
- const fullName = item.peer.fullName?.trim();
97
- if (fullName) return fullName;
98
- const username = item.peer.username?.trim();
99
- if (username) return username;
100
- return t('thread_card_fallback_name');
101
- };
102
-
103
- export const resolveAvatarUrl = (item: DConversationItem) => {
104
- if (isBotCrmChat(item.chatCategory)) return null;
105
- return item.peer.avatar;
78
+ export const sortConversation = (map: Record<string, DConversationItem>) => {
79
+ const list = Object.keys(map).sort((a, b) =>
80
+ dConversationCompare(map[a]!, map[b]!)
81
+ );
82
+ return { map, list };
106
83
  };
@@ -1,5 +1,9 @@
1
1
  import { SessionType, type MessageItem } from '@droppii/openim-rn-client-sdk';
2
- import type { DMessageItem } from '../types/chat';
2
+ import {
3
+ DChatType,
4
+ type DConversationItem,
5
+ type DMessageItem,
6
+ } from '../types/chat';
3
7
  import { getConversationID } from './conversation';
4
8
 
5
9
  const getMessageTime = (message: DMessageItem) =>
@@ -96,41 +100,43 @@ export const hasNewHistoryMessages = (
96
100
 
97
101
  export const belongsToConversation = (
98
102
  message: MessageItem,
99
- conversationId: string,
100
- groupID?: string
103
+ conversationId: string
101
104
  ): boolean => {
102
- const runtimeConversationId = (
103
- message as MessageItem & {
104
- conversationID?: string;
105
+ switch (message.sessionType) {
106
+ case SessionType.Single: {
107
+ const forwardId = getConversationID(
108
+ message.sessionType,
109
+ message.sendID,
110
+ message.recvID
111
+ );
112
+ const reverseId = getConversationID(
113
+ message.sessionType,
114
+ message.recvID,
115
+ message.sendID
116
+ );
117
+ return conversationId === forwardId || conversationId === reverseId;
105
118
  }
106
- ).conversationID;
107
119
 
108
- if (runtimeConversationId === conversationId) {
109
- return true;
110
- }
120
+ case SessionType.Group: {
121
+ return conversationId === `sg_${message.groupID}`;
122
+ }
111
123
 
112
- if (groupID) {
113
- return (
114
- message.groupID === groupID ||
115
- conversationId === `sg_${groupID}` ||
116
- conversationId.endsWith(`_${groupID}`)
117
- );
124
+ default:
125
+ return false;
118
126
  }
127
+ };
119
128
 
120
- if (message.sessionType !== SessionType.Single) {
121
- return false;
122
- }
129
+ export const getReceiverId = (conversation?: DConversationItem) => {
130
+ if (conversation == null) return { groupID: '', recvID: '' };
123
131
 
124
- const forwardId = getConversationID(
125
- message.sessionType,
126
- message.sendID,
127
- message.recvID
128
- );
129
- const reverseId = getConversationID(
130
- message.sessionType,
131
- message.recvID,
132
- message.sendID
133
- );
132
+ const isGroupChat = conversation.chatType === DChatType.GROUP;
133
+ if (isGroupChat) {
134
+ const isBot = conversation.chatCategory?.includes('BOT');
135
+ const groupID = isBot
136
+ ? conversation.conversationId?.slice(3) // Remove sg_
137
+ : conversation.groupID;
138
+ return { groupID: groupID ?? '', recvID: '' };
139
+ }
134
140
 
135
- return conversationId === forwardId || conversationId === reverseId;
141
+ return { groupID: '', recvID: conversation.peer.id ?? '' };
136
142
  };