@droppii-org/chat-mobile 0.2.67 → 0.2.69

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 (79) hide show
  1. package/lib/module/components/GroupItem/index.js +154 -0
  2. package/lib/module/components/GroupItem/index.js.map +1 -0
  3. package/lib/module/core/useChatListener.js +34 -2
  4. package/lib/module/core/useChatListener.js.map +1 -1
  5. package/lib/module/hooks/groups/useFetchPublicGroup.js +39 -0
  6. package/lib/module/hooks/groups/useFetchPublicGroup.js.map +1 -0
  7. package/lib/module/hooks/groups/useJoinGroup.js +42 -0
  8. package/lib/module/hooks/groups/useJoinGroup.js.map +1 -0
  9. package/lib/module/hooks/message/usePinMessage.js +4 -2
  10. package/lib/module/hooks/message/usePinMessage.js.map +1 -1
  11. package/lib/module/hooks/message/useSendMessage.js +7 -12
  12. package/lib/module/hooks/message/useSendMessage.js.map +1 -1
  13. package/lib/module/hooks/query-keys.js +6 -0
  14. package/lib/module/hooks/query-keys.js.map +1 -1
  15. package/lib/module/hooks/useGroupPermission.js +34 -0
  16. package/lib/module/hooks/useGroupPermission.js.map +1 -0
  17. package/lib/module/index.js +1 -1
  18. package/lib/module/index.js.map +1 -1
  19. package/lib/module/screens/SearchConversation/TabResult.js +1 -1
  20. package/lib/module/screens/SearchConversation/TabResult.js.map +1 -1
  21. package/lib/module/screens/chat-detail/ChatComposer.js +35 -14
  22. package/lib/module/screens/chat-detail/ChatComposer.js.map +1 -1
  23. package/lib/module/screens/chat-detail/ChatDetail.js +20 -2
  24. package/lib/module/screens/chat-detail/ChatDetail.js.map +1 -1
  25. package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxOverlay.js +5 -0
  26. package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxOverlay.js.map +1 -1
  27. package/lib/module/screens/chat-detail/types.js.map +1 -1
  28. package/lib/module/screens/inbox/CommunityTab.js +55 -0
  29. package/lib/module/screens/inbox/CommunityTab.js.map +1 -0
  30. package/lib/module/screens/inbox/Inbox.js +51 -40
  31. package/lib/module/screens/inbox/Inbox.js.map +1 -1
  32. package/lib/module/translation/resources/i18n.js +9 -1
  33. package/lib/module/translation/resources/i18n.js.map +1 -1
  34. package/lib/typescript/src/components/GroupItem/index.d.ts +8 -0
  35. package/lib/typescript/src/components/GroupItem/index.d.ts.map +1 -0
  36. package/lib/typescript/src/core/useChatListener.d.ts.map +1 -1
  37. package/lib/typescript/src/hooks/groups/useFetchPublicGroup.d.ts +213 -0
  38. package/lib/typescript/src/hooks/groups/useFetchPublicGroup.d.ts.map +1 -0
  39. package/lib/typescript/src/hooks/groups/useJoinGroup.d.ts +5 -0
  40. package/lib/typescript/src/hooks/groups/useJoinGroup.d.ts.map +1 -0
  41. package/lib/typescript/src/hooks/message/usePinMessage.d.ts.map +1 -1
  42. package/lib/typescript/src/hooks/message/useSendMessage.d.ts.map +1 -1
  43. package/lib/typescript/src/hooks/query-keys.d.ts +6 -0
  44. package/lib/typescript/src/hooks/query-keys.d.ts.map +1 -1
  45. package/lib/typescript/src/hooks/useGroupPermission.d.ts +6 -0
  46. package/lib/typescript/src/hooks/useGroupPermission.d.ts.map +1 -0
  47. package/lib/typescript/src/index.d.ts +1 -1
  48. package/lib/typescript/src/index.d.ts.map +1 -1
  49. package/lib/typescript/src/screens/chat-detail/ChatComposer.d.ts +3 -1
  50. package/lib/typescript/src/screens/chat-detail/ChatComposer.d.ts.map +1 -1
  51. package/lib/typescript/src/screens/chat-detail/ChatDetail.d.ts +1 -1
  52. package/lib/typescript/src/screens/chat-detail/ChatDetail.d.ts.map +1 -1
  53. package/lib/typescript/src/screens/chat-detail/messageToolbox/MessageToolboxOverlay.d.ts +1 -1
  54. package/lib/typescript/src/screens/chat-detail/messageToolbox/MessageToolboxOverlay.d.ts.map +1 -1
  55. package/lib/typescript/src/screens/chat-detail/types.d.ts +2 -1
  56. package/lib/typescript/src/screens/chat-detail/types.d.ts.map +1 -1
  57. package/lib/typescript/src/screens/inbox/CommunityTab.d.ts +6 -0
  58. package/lib/typescript/src/screens/inbox/CommunityTab.d.ts.map +1 -0
  59. package/lib/typescript/src/screens/inbox/Inbox.d.ts +2 -1
  60. package/lib/typescript/src/screens/inbox/Inbox.d.ts.map +1 -1
  61. package/lib/typescript/src/translation/resources/i18n.d.ts.map +1 -1
  62. package/package.json +1 -1
  63. package/src/components/GroupItem/index.tsx +170 -0
  64. package/src/core/useChatListener.ts +54 -1
  65. package/src/hooks/groups/useFetchPublicGroup.ts +43 -0
  66. package/src/hooks/groups/useJoinGroup.ts +46 -0
  67. package/src/hooks/message/usePinMessage.ts +8 -2
  68. package/src/hooks/message/useSendMessage.ts +11 -15
  69. package/src/hooks/query-keys.ts +8 -0
  70. package/src/hooks/useGroupPermission.ts +43 -0
  71. package/src/index.tsx +5 -1
  72. package/src/screens/SearchConversation/TabResult.tsx +1 -1
  73. package/src/screens/chat-detail/ChatComposer.tsx +50 -27
  74. package/src/screens/chat-detail/ChatDetail.tsx +25 -1
  75. package/src/screens/chat-detail/messageToolbox/MessageToolboxOverlay.tsx +8 -1
  76. package/src/screens/chat-detail/types.ts +5 -1
  77. package/src/screens/inbox/CommunityTab.tsx +65 -0
  78. package/src/screens/inbox/Inbox.tsx +51 -39
  79. package/src/translation/resources/i18n.ts +8 -0
@@ -63,12 +63,9 @@ import { ChatCurrentQuotedMessage } from './ChatCurrentQuotedMessage';
63
63
  import { useSendMessage } from '../../hooks/message/useSendMessage';
64
64
  import { openBotMenuSheet } from './BotMenuSheet';
65
65
  import { UIUtils } from '../../utils/UIUtils';
66
- import {
67
- GroupPermission,
68
- PeerType,
69
- SessionType,
70
- } from '@droppii/openim-rn-client-sdk';
71
- import { useConversationStore } from '../../store';
66
+ import { GroupPermission, PeerType } from '@droppii/openim-rn-client-sdk';
67
+ import { useGuardedAction } from '../../hooks/useGroupPermission';
68
+ import { useIsJoinedGroup } from '../../hooks/useIsJoinedGroup';
72
69
 
73
70
  interface ChatComposerInternalProps {
74
71
  isEmptyMessage?: boolean;
@@ -76,6 +73,8 @@ interface ChatComposerInternalProps {
76
73
  isComposeDisabled?: boolean;
77
74
  composerRef?: RefObject<View | null>;
78
75
  onComposerLayout?: (event: LayoutChangeEvent) => void;
76
+ onPressJoinGroup?: () => void;
77
+ isJoiningGroup?: boolean;
79
78
  }
80
79
 
81
80
  export const ChatComposer = memo(
@@ -85,6 +84,8 @@ export const ChatComposer = memo(
85
84
  isComposeDisabled = false,
86
85
  composerRef,
87
86
  onComposerLayout,
87
+ onPressJoinGroup,
88
+ isJoiningGroup = false,
88
89
  }: ChatComposerInternalProps) => {
89
90
  const insets = useSafeAreaInsets();
90
91
  const isDisableCompose = isComposeDisabled;
@@ -103,7 +104,12 @@ export const ChatComposer = memo(
103
104
  const { mutateAsync: closeSupportSession } = useCloseSupportSession();
104
105
  const { sendMessage } = useSendMessage();
105
106
  const compose = useChatCompose();
106
- const groupPermissions = useChatDetailContext().groupPermissions;
107
+
108
+ const { data: isJoinedGroup } = useIsJoinedGroup(conversation?.groupID);
109
+
110
+ const isNotInGroup = useMemo(() => {
111
+ return conversation?.groupID ? !isJoinedGroup : false;
112
+ }, [conversation?.groupID, isJoinedGroup]);
107
113
 
108
114
  const {
109
115
  activePanelType,
@@ -137,27 +143,20 @@ export const ChatComposer = memo(
137
143
  onAttachmentOpenChange: setIsAttachmentOpen,
138
144
  });
139
145
 
140
- const handleSend = useCallback(async () => {
141
- const currentConversation = useConversationStore
142
- .getState()
143
- .getCurrentConversation();
144
- if (
145
- currentConversation?.conversationType === SessionType.Group &&
146
- !groupPermissions?.includes(GroupPermission.SendMessage)
147
- ) {
148
- UIUtils.toast.open({
149
- title: trans('chat:no_send_permission'),
150
- theme: 'info',
151
- });
152
- return;
153
- }
154
- if (compose.isUploading || !compose.canSend) return;
155
- try {
156
- await compose.send();
157
- } catch (error) {
158
- if (__DEV__) console.error('[ChatComposer] Failed to send:', error);
146
+ const handleSend = useGuardedAction(
147
+ GroupPermission.SendMessage,
148
+ useCallback(async () => {
149
+ if (compose.isUploading || !compose.canSend) return;
150
+ try {
151
+ await compose.send();
152
+ } catch (error) {
153
+ if (__DEV__) console.error('[ChatComposer] Failed to send:', error);
154
+ }
155
+ }, [compose]),
156
+ {
157
+ deniedMessage: trans('chat:no_send_permission'),
159
158
  }
160
- }, [compose, groupPermissions]);
159
+ );
161
160
 
162
161
  const handleInputFocus = useCallback(() => {
163
162
  setIsInputFocused(true);
@@ -443,6 +442,30 @@ export const ChatComposer = memo(
443
442
  />
444
443
  </KContainer.View>
445
444
  );
445
+ } else if (isNotInGroup) {
446
+ content = (
447
+ <KContainer.View
448
+ paddingH="0.75rem"
449
+ justifyContent="center"
450
+ brTW={1}
451
+ brTC={KColors.border.light}
452
+ >
453
+ <KLabel.Text
454
+ typo="TextSmNormal"
455
+ textAlign="center"
456
+ paddingV={'0.75rem'}
457
+ >
458
+ {trans('group:not_in_group')}
459
+ </KLabel.Text>
460
+ <KButton.Solid
461
+ size="md"
462
+ label={trans('group:join')}
463
+ stretch
464
+ loading={isJoiningGroup}
465
+ onPress={onPressJoinGroup}
466
+ />
467
+ </KContainer.View>
468
+ );
446
469
  } else {
447
470
  content = (
448
471
  <KContainer.View background={'#F9F9FA'}>
@@ -30,10 +30,14 @@ import {
30
30
  useGetConversationDetail,
31
31
  useGetConversationTarget,
32
32
  } from '../../hooks/conversation/useGetConversationDetail';
33
- import type { MessageItem } from '@droppii/openim-rn-client-sdk';
33
+ import {
34
+ GroupJoinSource,
35
+ type MessageItem,
36
+ } from '@droppii/openim-rn-client-sdk';
34
37
  import { useMessages } from '../../hooks/message/useMessages';
35
38
  import { ChatEvent, type GroupRemovedPayload } from '../../types/events';
36
39
  import { useSetIsShowFLashMessage } from '../../context/global';
40
+ import { useJoinGroup } from '../../hooks/groups/useJoinGroup';
37
41
 
38
42
  const ChatDetail = memo(
39
43
  ({
@@ -50,6 +54,7 @@ const ChatDetail = memo(
50
54
  onPressUrl,
51
55
  customMessageItemFactory,
52
56
  pannelActions,
57
+ groupJoinSource,
53
58
  }: ChatDetailProps) => {
54
59
  const navigation = useNavigation();
55
60
  const mediaViewerRef = useRef<MediaViewerModalHandle>(null);
@@ -94,6 +99,8 @@ const ChatDetail = memo(
94
99
  hasMoreNew,
95
100
  } = useMessages({ conversationId, startClientMsgId: targetMessageId });
96
101
 
102
+ const { mutate: joinGroup, isPending: isJoiningGroup } = useJoinGroup();
103
+
97
104
  const setShowFLashMessage = useSetIsShowFLashMessage();
98
105
 
99
106
  const { pinnedMessages, pinMessage, unpinMessage } =
@@ -164,6 +171,21 @@ const ChatDetail = memo(
164
171
  onPressMenu?.(conversationId);
165
172
  }, [conversationId, onPressMenu]);
166
173
 
174
+ const handlePressJoinGroup = useCallback(() => {
175
+ if (!conversation?.groupID) return;
176
+ joinGroup({
177
+ groupID: conversation?.groupID,
178
+ reqMsg: '',
179
+ joinSource: groupJoinSource || GroupJoinSource.Search,
180
+ groupName: conversation?.showName || '',
181
+ });
182
+ }, [
183
+ groupJoinSource,
184
+ conversation?.groupID,
185
+ joinGroup,
186
+ conversation?.showName,
187
+ ]);
188
+
167
189
  return (
168
190
  <KContainer.Page flex edges={['bottom']}>
169
191
  <ChatDetailHeader
@@ -207,6 +229,8 @@ const ChatDetail = memo(
207
229
  isEmptyMessage={messages?.length === 0}
208
230
  pannelActions={pannelActions}
209
231
  isComposeDisabled={isComposeDisabled}
232
+ onPressJoinGroup={handlePressJoinGroup}
233
+ isJoiningGroup={isJoiningGroup}
210
234
  />
211
235
 
212
236
  <MediaViewerModal ref={mediaViewerRef} />
@@ -20,7 +20,11 @@ import { MessageToolboxWrapper } from './MessageToolboxWrapper';
20
20
  import type { ReactNode } from 'react';
21
21
  import { useSendMessage } from '../../../hooks/message/useSendMessage';
22
22
  import { useChatDetailContext } from '../../../context';
23
- import type { MessageItem } from '@droppii/openim-rn-client-sdk';
23
+ import {
24
+ GroupPermission,
25
+ type MessageItem,
26
+ } from '@droppii/openim-rn-client-sdk';
27
+ import { useHasGroupPermission } from '../../../hooks/useGroupPermission';
24
28
 
25
29
  interface MessageToolboxOverlayProps {
26
30
  currentUserId?: string;
@@ -51,6 +55,7 @@ export const MessageToolboxOverlay = forwardRef<
51
55
  [message, currentUserId]
52
56
  );
53
57
  const { retryMessage, revokeMessage } = useSendMessage();
58
+ const isPinAllowed = useHasGroupPermission(GroupPermission.PinMessage);
54
59
 
55
60
  const hide = useCallback(() => {
56
61
  setVisible(false);
@@ -173,6 +178,8 @@ export const MessageToolboxOverlay = forwardRef<
173
178
  toolbox={
174
179
  <MessageToolboxMenu
175
180
  {...toolboxItems}
181
+ showPinMessage={toolboxItems.showPinMessage && isPinAllowed}
182
+ showUnpinMessage={toolboxItems.showUnpinMessage && isPinAllowed}
176
183
  onCopyMessage={handleCopyMessage}
177
184
  onCopyMessageLink={handleCopyMessageLink}
178
185
  onPinMessage={handlePinMessage}
@@ -1,7 +1,10 @@
1
1
  import type { ReactNode } from 'react';
2
2
  import type { MediaItem } from '../../hooks/useMediaViewer';
3
3
  import type { MeasuredRect } from './messageToolbox/types';
4
- import type { MessageItem } from '@droppii/openim-rn-client-sdk';
4
+ import type {
5
+ GroupJoinSource,
6
+ MessageItem,
7
+ } from '@droppii/openim-rn-client-sdk';
5
8
 
6
9
  export type DChatActionIconProvider = 'MaterialCommunityIcons' | 'DroppiiNew';
7
10
 
@@ -100,4 +103,5 @@ export interface ChatDetailProps extends Omit<
100
103
  onPressUrl?: (url: string) => void;
101
104
  customMessageItemFactory?: (item: MessageItem) => ReactNode;
102
105
  pannelActions?: DChatPannelAction[];
106
+ groupJoinSource?: GroupJoinSource;
103
107
  }
@@ -0,0 +1,65 @@
1
+ import { KContainer, KSpacingValue } from '@droppii/libs';
2
+ import { memo, useCallback } from 'react';
3
+ import { ActivityIndicator, StyleSheet } from 'react-native';
4
+ import type { SearchPublicGroupInfo } from '@droppii/openim-rn-client-sdk';
5
+ import { useFetchPublicGroup } from '../../hooks/groups/useFetchPublicGroup';
6
+ import { GroupItem } from '../../components/GroupItem';
7
+
8
+ interface CommunityTabProps {
9
+ onPressGroup?: (groupId: string) => void;
10
+ }
11
+ export const CommunityTab = memo(({ onPressGroup }: CommunityTabProps) => {
12
+ const {
13
+ dataFlatten,
14
+ isFetching,
15
+ hasNextPage,
16
+ fetchNextPage,
17
+ isFetchingNextPage,
18
+ isRefetching,
19
+ refetch,
20
+ } = useFetchPublicGroup('');
21
+
22
+ const renderItem = useCallback(
23
+ ({ item }: { item: SearchPublicGroupInfo }) => (
24
+ <GroupItem item={item} onPress={onPressGroup} />
25
+ ),
26
+ [onPressGroup]
27
+ );
28
+
29
+ const keyExtractor = useCallback(
30
+ (item: SearchPublicGroupInfo) => item.groupID,
31
+ []
32
+ );
33
+
34
+ const onEndReached = useCallback(() => {
35
+ if (hasNextPage && !isFetchingNextPage) {
36
+ fetchNextPage();
37
+ }
38
+ }, [hasNextPage, isFetchingNextPage, fetchNextPage]);
39
+
40
+ return (
41
+ <KContainer.Page flex>
42
+ {isFetching && !isFetchingNextPage && !isRefetching ? (
43
+ <KContainer.View flex center>
44
+ <ActivityIndicator />
45
+ </KContainer.View>
46
+ ) : (
47
+ <KContainer.FlatList
48
+ data={dataFlatten}
49
+ renderItem={renderItem}
50
+ keyExtractor={keyExtractor}
51
+ onEndReached={onEndReached}
52
+ refreshing={isRefetching}
53
+ onRefresh={refetch}
54
+ contentContainerStyle={styles.contentContainer}
55
+ />
56
+ )}
57
+ </KContainer.Page>
58
+ );
59
+ });
60
+
61
+ const styles = StyleSheet.create({
62
+ contentContainer: {
63
+ paddingHorizontal: KSpacingValue['0.75rem'],
64
+ },
65
+ });
@@ -1,38 +1,56 @@
1
1
  import { memo, useCallback, useState } from 'react';
2
2
  import { StyleSheet } from 'react-native';
3
3
  import { TabView } from 'react-native-tab-view';
4
- import { KContainer, KImage, KLabel, KColors } from '@droppii/libs';
4
+ import {
5
+ KContainer,
6
+ KImage,
7
+ KLabel,
8
+ KColors,
9
+ KRadiusValue,
10
+ KSpacingValue,
11
+ } from '@droppii/libs';
5
12
  import { MessagesTab } from './MessagesTab';
13
+ import { trans } from '../../translation';
14
+ import { CommunityTab } from './CommunityTab';
6
15
 
7
16
  interface InboxProps {
8
17
  onPressThread?: (item: string) => void;
9
18
  onPressSearch?: () => void;
10
19
  onPressEdit?: () => void;
11
20
  onPressContact?: () => void;
21
+ onPressGroup?: (groupId: string) => void;
12
22
  }
13
23
 
14
24
  type TabRoute = { key: string; title: string };
15
25
 
26
+ const TabRoutes: TabRoute[] = [
27
+ { key: 'messages', title: trans('messages') },
28
+ { key: 'community', title: trans('community') },
29
+ ];
30
+
16
31
  const Inbox = memo(
17
32
  ({
18
33
  onPressThread,
19
34
  onPressSearch,
20
35
  onPressEdit,
21
36
  onPressContact,
37
+ onPressGroup,
22
38
  }: InboxProps) => {
23
39
  const [index, setIndex] = useState(0);
24
- const [routes] = useState<TabRoute[]>([
25
- { key: 'messages', title: 'Tin nhắn' },
26
- ]);
40
+ const [routes] = useState<TabRoute[]>(TabRoutes);
27
41
 
28
42
  const renderScene = useCallback(
29
43
  ({ route }: { route: TabRoute }) => {
30
- if (route.key === 'messages') {
31
- return <MessagesTab onPressThread={onPressThread} />;
44
+ switch (route.key) {
45
+ case 'messages':
46
+ return <MessagesTab onPressThread={onPressThread} />;
47
+ case 'community':
48
+ return <CommunityTab onPressGroup={onPressGroup} />;
49
+ default:
50
+ return null;
32
51
  }
33
- return null;
34
52
  },
35
- [onPressThread]
53
+ [onPressThread, onPressGroup]
36
54
  );
37
55
 
38
56
  const renderTabBar = useCallback(
@@ -46,24 +64,18 @@ const Inbox = memo(
46
64
  <KContainer.Touchable
47
65
  key={route.key}
48
66
  onPress={() => setIndex(i)}
49
- paddingV="0.75rem"
50
- marginR="1rem"
51
67
  style={styles.tabItem}
52
68
  activeOpacity={0.7}
53
69
  >
54
70
  <KLabel.Text
55
71
  typo="TextNmBold"
56
72
  color={
57
- isActive
58
- ? KColors.palette.primary.w400
59
- : KColors.palette.gray.w500
73
+ isActive ? KColors.primary.normal : KColors.gray.light
60
74
  }
61
75
  >
62
76
  {route.title}
63
77
  </KLabel.Text>
64
- {isActive && (
65
- <KContainer.View style={styles.activeIndicator} />
66
- )}
78
+ {isActive && <KContainer.View style={styles.indicator} />}
67
79
  </KContainer.Touchable>
68
80
  );
69
81
  })}
@@ -77,10 +89,9 @@ const Inbox = memo(
77
89
  activeOpacity={0.7}
78
90
  >
79
91
  <KImage.VectorIcons
80
- name="magnify"
81
- provider="MaterialCommunityIcons"
92
+ name="search-o"
82
93
  size={24}
83
- color={KColors.palette.primary.w400}
94
+ color={KColors.primary.normal}
84
95
  />
85
96
  </KContainer.Touchable>
86
97
  <KContainer.Touchable
@@ -89,10 +100,9 @@ const Inbox = memo(
89
100
  activeOpacity={0.7}
90
101
  >
91
102
  <KImage.VectorIcons
92
- name="square-edit-outline"
93
- provider="MaterialCommunityIcons"
103
+ name="plus-circle-o"
94
104
  size={24}
95
- color={KColors.palette.primary.w400}
105
+ color={KColors.primary.normal}
96
106
  />
97
107
  </KContainer.Touchable>
98
108
  <KContainer.Touchable
@@ -101,10 +111,9 @@ const Inbox = memo(
101
111
  activeOpacity={0.7}
102
112
  >
103
113
  <KImage.VectorIcons
104
- name="account-group-outline"
105
- provider="MaterialCommunityIcons"
114
+ name="user-three-o"
106
115
  size={24}
107
- color={KColors.palette.primary.w400}
116
+ color={KColors.primary.normal}
108
117
  />
109
118
  </KContainer.Touchable>
110
119
  </KContainer.View>
@@ -114,13 +123,13 @@ const Inbox = memo(
114
123
  );
115
124
 
116
125
  return (
117
- <KContainer.Page flex edges={['top', 'bottom']}>
126
+ <KContainer.Page flex edges={['top']}>
118
127
  <TabView
119
128
  navigationState={{ index, routes }}
120
129
  renderScene={renderScene}
121
130
  renderTabBar={renderTabBar}
122
131
  onIndexChange={setIndex}
123
- swipeEnabled={routes.length > 1}
132
+ lazy
124
133
  />
125
134
  </KContainer.Page>
126
135
  );
@@ -133,25 +142,28 @@ export default Inbox;
133
142
 
134
143
  const styles = StyleSheet.create({
135
144
  tabBar: {
136
- borderBottomWidth: 0.5,
137
- borderBottomColor: KColors.palette.gray.w100,
138
- paddingHorizontal: 12,
145
+ borderBottomWidth: 1,
146
+ borderBottomColor: KColors.border.light,
147
+ paddingTop: 2,
139
148
  },
140
149
  tabItem: {
141
- position: 'relative',
142
- paddingTop: 2,
143
- paddingBottom: 0,
150
+ height: 40,
151
+ marginHorizontal: KSpacingValue['0.75rem'],
152
+ justifyContent: 'center',
153
+ alignItems: 'center',
144
154
  },
145
- activeIndicator: {
155
+ indicator: {
156
+ height: 3,
157
+ backgroundColor: KColors.primary.normal,
158
+ width: '100%',
159
+ borderRadius: KRadiusValue.x,
146
160
  position: 'absolute',
147
- bottom: 0,
161
+ bottom: -1,
148
162
  left: 0,
149
163
  right: 0,
150
- height: 3,
151
- borderRadius: 4,
152
- backgroundColor: KColors.palette.primary.w400,
153
164
  },
154
165
  actionGroup: {
155
- gap: 4,
166
+ gap: KSpacingValue['0.25rem'],
167
+ paddingRight: KSpacingValue['0.5rem'],
156
168
  },
157
169
  });
@@ -163,7 +163,15 @@ const i18nKey: Record<string, string> = {
163
163
  'search_conversation:empty_hint': 'Bạn có thể tìm kiếm theo liên hệ',
164
164
  'view_all': 'Xem tất cả',
165
165
  'search_message:revoked_message': 'Tin nhắn đã được thu hồi',
166
+ 'chat:no_permission': 'Bạn không có quyền này',
166
167
  'chat:no_send_permission': 'Bạn không có quyền gửi tin nhắn',
168
+ 'community': 'Cộng đồng',
169
+ 'group:member_count': '{{count}} thành viên',
170
+ 'group:access': 'Truy cập',
171
+ 'group:join': 'Tham gia',
172
+ 'group:not_in_group': 'Bạn cần tham gia nhóm để gửi tin nhắn',
173
+ 'group:cannot_send_message': 'Bạn không thể gửi tin nhắn vào nhóm này',
174
+ 'group:joined_group': 'Đã tham gia nhóm {{groupName}}',
167
175
  };
168
176
 
169
177
  export default {