@droppii-org/chat-mobile 0.2.68 → 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 (61) 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 +33 -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/query-keys.js +6 -0
  10. package/lib/module/hooks/query-keys.js.map +1 -1
  11. package/lib/module/index.js +1 -1
  12. package/lib/module/index.js.map +1 -1
  13. package/lib/module/screens/SearchConversation/TabResult.js +1 -1
  14. package/lib/module/screens/SearchConversation/TabResult.js.map +1 -1
  15. package/lib/module/screens/chat-detail/ChatComposer.js +29 -1
  16. package/lib/module/screens/chat-detail/ChatComposer.js.map +1 -1
  17. package/lib/module/screens/chat-detail/ChatDetail.js +20 -2
  18. package/lib/module/screens/chat-detail/ChatDetail.js.map +1 -1
  19. package/lib/module/screens/chat-detail/types.js.map +1 -1
  20. package/lib/module/screens/inbox/CommunityTab.js +55 -0
  21. package/lib/module/screens/inbox/CommunityTab.js.map +1 -0
  22. package/lib/module/screens/inbox/Inbox.js +51 -40
  23. package/lib/module/screens/inbox/Inbox.js.map +1 -1
  24. package/lib/module/translation/resources/i18n.js +8 -1
  25. package/lib/module/translation/resources/i18n.js.map +1 -1
  26. package/lib/typescript/src/components/GroupItem/index.d.ts +8 -0
  27. package/lib/typescript/src/components/GroupItem/index.d.ts.map +1 -0
  28. package/lib/typescript/src/core/useChatListener.d.ts.map +1 -1
  29. package/lib/typescript/src/hooks/groups/useFetchPublicGroup.d.ts +213 -0
  30. package/lib/typescript/src/hooks/groups/useFetchPublicGroup.d.ts.map +1 -0
  31. package/lib/typescript/src/hooks/groups/useJoinGroup.d.ts +5 -0
  32. package/lib/typescript/src/hooks/groups/useJoinGroup.d.ts.map +1 -0
  33. package/lib/typescript/src/hooks/query-keys.d.ts +6 -0
  34. package/lib/typescript/src/hooks/query-keys.d.ts.map +1 -1
  35. package/lib/typescript/src/index.d.ts +1 -1
  36. package/lib/typescript/src/index.d.ts.map +1 -1
  37. package/lib/typescript/src/screens/chat-detail/ChatComposer.d.ts +3 -1
  38. package/lib/typescript/src/screens/chat-detail/ChatComposer.d.ts.map +1 -1
  39. package/lib/typescript/src/screens/chat-detail/ChatDetail.d.ts +1 -1
  40. package/lib/typescript/src/screens/chat-detail/ChatDetail.d.ts.map +1 -1
  41. package/lib/typescript/src/screens/chat-detail/types.d.ts +2 -1
  42. package/lib/typescript/src/screens/chat-detail/types.d.ts.map +1 -1
  43. package/lib/typescript/src/screens/inbox/CommunityTab.d.ts +6 -0
  44. package/lib/typescript/src/screens/inbox/CommunityTab.d.ts.map +1 -0
  45. package/lib/typescript/src/screens/inbox/Inbox.d.ts +2 -1
  46. package/lib/typescript/src/screens/inbox/Inbox.d.ts.map +1 -1
  47. package/lib/typescript/src/translation/resources/i18n.d.ts.map +1 -1
  48. package/package.json +1 -1
  49. package/src/components/GroupItem/index.tsx +170 -0
  50. package/src/core/useChatListener.ts +53 -1
  51. package/src/hooks/groups/useFetchPublicGroup.ts +43 -0
  52. package/src/hooks/groups/useJoinGroup.ts +46 -0
  53. package/src/hooks/query-keys.ts +8 -0
  54. package/src/index.tsx +5 -1
  55. package/src/screens/SearchConversation/TabResult.tsx +1 -1
  56. package/src/screens/chat-detail/ChatComposer.tsx +35 -0
  57. package/src/screens/chat-detail/ChatDetail.tsx +25 -1
  58. package/src/screens/chat-detail/types.ts +5 -1
  59. package/src/screens/inbox/CommunityTab.tsx +65 -0
  60. package/src/screens/inbox/Inbox.tsx +51 -39
  61. package/src/translation/resources/i18n.ts +7 -0
@@ -0,0 +1,43 @@
1
+ import { useInfiniteQuery } from '@tanstack/react-query';
2
+ import { useMemo } from 'react';
3
+ import OpenIMSDK, {
4
+ PublicGroupJoinStatus,
5
+ type SearchPublicGroupInfo,
6
+ } from '@droppii/openim-rn-client-sdk';
7
+ import { PAGE_SIZE } from '../../constants';
8
+ import { groupQueryKeys } from '../query-keys';
9
+
10
+ export const useFetchPublicGroup = (keyword: string) => {
11
+ const { data, ...rest } = useInfiniteQuery({
12
+ queryKey: groupQueryKeys.publicSearch(keyword),
13
+ queryFn: ({ pageParam = 0 }) => {
14
+ return OpenIMSDK.searchPublicGroups(
15
+ keyword,
16
+ PublicGroupJoinStatus.All,
17
+ pageParam,
18
+ PAGE_SIZE
19
+ );
20
+ },
21
+ getNextPageParam: (lastPage, allPages) => {
22
+ const fetched = allPages.reduce((sum, p) => sum + p.groups.length, 0);
23
+ return fetched < lastPage.total ? fetched : undefined;
24
+ },
25
+ initialPageParam: 0,
26
+ maxPages: 1,
27
+ });
28
+
29
+ const dataFlatten: SearchPublicGroupInfo[] = useMemo(() => {
30
+ const seen = new Set<string>();
31
+ return (
32
+ data?.pages.flatMap((page) =>
33
+ page.groups.filter((g) => {
34
+ if (seen.has(g.groupID)) return false;
35
+ seen.add(g.groupID);
36
+ return true;
37
+ })
38
+ ) ?? []
39
+ );
40
+ }, [data]);
41
+
42
+ return { dataFlatten, ...rest };
43
+ };
@@ -0,0 +1,46 @@
1
+ import OpenIMSDK, { type JoinGroupParams } from '@droppii/openim-rn-client-sdk';
2
+ import { useQueryClient } from '@tanstack/react-query';
3
+ import { useMutation } from '@tanstack/react-query';
4
+ import { conversationQueryKeys, groupQueryKeys } from '../query-keys';
5
+ import { UIUtils } from '../../utils/UIUtils';
6
+ import { trans } from '../../translation';
7
+
8
+ export const useJoinGroup = () => {
9
+ const queryClient = useQueryClient();
10
+
11
+ return useMutation({
12
+ mutationKey: groupQueryKeys.join(),
13
+ mutationFn: (params: JoinGroupParams & { groupName: string }) =>
14
+ OpenIMSDK.joinGroup(params),
15
+ onSuccess: (_data, params) => {
16
+ queryClient.invalidateQueries({
17
+ queryKey: conversationQueryKeys.joinedGroup(params.groupID),
18
+ });
19
+
20
+ queryClient.setQueriesData<{
21
+ pages: {
22
+ total: number;
23
+ groups: { groupID: string; isJoined: boolean }[];
24
+ }[];
25
+ pageParams: unknown[];
26
+ }>({ queryKey: groupQueryKeys.public(), exact: false }, (old) => {
27
+ if (!old) return old;
28
+ return {
29
+ ...old,
30
+ pages: old.pages.map((page) => ({
31
+ ...page,
32
+ groups: page.groups.map((g) =>
33
+ g.groupID === params.groupID ? { ...g, isJoined: true } : g
34
+ ),
35
+ })),
36
+ };
37
+ });
38
+
39
+ UIUtils.toast.open({
40
+ title: trans('group:joined_group', {
41
+ groupName: params?.groupName || '',
42
+ }),
43
+ });
44
+ },
45
+ });
46
+ };
@@ -61,3 +61,11 @@ export const messageQueryKeys = {
61
61
  search: (query: string, conversationId?: string) =>
62
62
  [...messageQueryKeys.all, 'search', query, conversationId] as const,
63
63
  };
64
+
65
+ export const groupQueryKeys = {
66
+ all: ['groups'] as const,
67
+ public: () => [...groupQueryKeys.all, 'public'] as const,
68
+ publicSearch: (keyword: string) =>
69
+ [...groupQueryKeys.public(), keyword] as const,
70
+ join: () => [...groupQueryKeys.all, 'join'] as const,
71
+ };
package/src/index.tsx CHANGED
@@ -40,7 +40,11 @@ export type {
40
40
  MessageItem,
41
41
  ConversationItem,
42
42
  } from '@droppii/openim-rn-client-sdk';
43
- export { LoginStatus, MessageType } from '@droppii/openim-rn-client-sdk';
43
+ export {
44
+ LoginStatus,
45
+ MessageType,
46
+ GroupJoinSource,
47
+ } from '@droppii/openim-rn-client-sdk';
44
48
  export * from './screens/chat-detail';
45
49
  export type {
46
50
  DChatActionItem,
@@ -122,7 +122,7 @@ const styles = StyleSheet.create({
122
122
  tabRow: {
123
123
  flexDirection: 'row',
124
124
  borderBottomWidth: 1,
125
- borderBottomColor: KColors.border.normal,
125
+ borderBottomColor: KColors.border.light,
126
126
  },
127
127
  tabItem: {
128
128
  paddingVertical: KSpacingValue['0.5rem'],
@@ -65,6 +65,7 @@ import { openBotMenuSheet } from './BotMenuSheet';
65
65
  import { UIUtils } from '../../utils/UIUtils';
66
66
  import { GroupPermission, PeerType } from '@droppii/openim-rn-client-sdk';
67
67
  import { useGuardedAction } from '../../hooks/useGroupPermission';
68
+ import { useIsJoinedGroup } from '../../hooks/useIsJoinedGroup';
68
69
 
69
70
  interface ChatComposerInternalProps {
70
71
  isEmptyMessage?: boolean;
@@ -72,6 +73,8 @@ interface ChatComposerInternalProps {
72
73
  isComposeDisabled?: boolean;
73
74
  composerRef?: RefObject<View | null>;
74
75
  onComposerLayout?: (event: LayoutChangeEvent) => void;
76
+ onPressJoinGroup?: () => void;
77
+ isJoiningGroup?: boolean;
75
78
  }
76
79
 
77
80
  export const ChatComposer = memo(
@@ -81,6 +84,8 @@ export const ChatComposer = memo(
81
84
  isComposeDisabled = false,
82
85
  composerRef,
83
86
  onComposerLayout,
87
+ onPressJoinGroup,
88
+ isJoiningGroup = false,
84
89
  }: ChatComposerInternalProps) => {
85
90
  const insets = useSafeAreaInsets();
86
91
  const isDisableCompose = isComposeDisabled;
@@ -100,6 +105,12 @@ export const ChatComposer = memo(
100
105
  const { sendMessage } = useSendMessage();
101
106
  const compose = useChatCompose();
102
107
 
108
+ const { data: isJoinedGroup } = useIsJoinedGroup(conversation?.groupID);
109
+
110
+ const isNotInGroup = useMemo(() => {
111
+ return conversation?.groupID ? !isJoinedGroup : false;
112
+ }, [conversation?.groupID, isJoinedGroup]);
113
+
103
114
  const {
104
115
  activePanelType,
105
116
  setActivePanelType,
@@ -431,6 +442,30 @@ export const ChatComposer = memo(
431
442
  />
432
443
  </KContainer.View>
433
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
+ );
434
469
  } else {
435
470
  content = (
436
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} />
@@ -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
  });
@@ -165,6 +165,13 @@ const i18nKey: Record<string, string> = {
165
165
  'search_message:revoked_message': 'Tin nhắn đã được thu hồi',
166
166
  'chat:no_permission': 'Bạn không có quyền này',
167
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}}',
168
175
  };
169
176
 
170
177
  export default {