diff --git a/package.json b/package.json
index e6548a7c..92cd97b4 100644
--- a/package.json
+++ b/package.json
@@ -179,6 +179,7 @@
"i18next": "22.4.10",
"lodash.debounce": "^4.0.8",
"moment": "2.29.4",
+ "react-hook-form": "^7.49.3",
"react-i18next": "12.1.5",
"react-native-create-thumbnail": "^1.6.4",
"react-native-image-picker": "5.1.0",
diff --git a/src/components/ChooseCategoryModal/Components/RenderCategories.tsx b/src/components/ChooseCategoryModal/Components/RenderCategories.tsx
new file mode 100644
index 00000000..daf35dbd
--- /dev/null
+++ b/src/components/ChooseCategoryModal/Components/RenderCategories.tsx
@@ -0,0 +1,38 @@
+import { Image, Text, TouchableOpacity } from 'react-native';
+import React from 'react';
+import useImage from '../../../hooks/useImage';
+import { SvgXml } from 'react-native-svg';
+import { categoryIcon } from '../../../svg/svg-xml-list';
+import { useStyle } from '../styles';
+
+const RenderCategories = ({
+ item,
+ onSelectCategory,
+}: {
+ item: Amity.Category;
+ onSelectCategory: (id: string, name: string) => void;
+}) => {
+ const avatarURL = useImage({ fileId: item.avatarFileId });
+ const styles = useStyle();
+ return (
+ onSelectCategory(item.categoryId, item.name)}
+ style={styles.rowContainer}
+ >
+ {item.avatarFileId ? (
+
+ ) : (
+
+ )}
+
+ {item.name}
+
+ );
+};
+
+export default RenderCategories;
diff --git a/src/components/ChooseCategoryModal/index.tsx b/src/components/ChooseCategoryModal/index.tsx
index 8d5be002..4027e503 100644
--- a/src/components/ChooseCategoryModal/index.tsx
+++ b/src/components/ChooseCategoryModal/index.tsx
@@ -5,28 +5,32 @@ import {
View,
Text,
Modal,
- Image,
FlatList,
type NativeSyntheticEvent,
type NativeScrollEvent,
} from 'react-native';
import { SvgXml } from 'react-native-svg';
-import { categoryIcon, closeIcon } from '../../svg/svg-xml-list';
-import useAuth from '../../hooks/useAuth';
-import { getStyles } from './styles';
+import { closeIcon } from '../../svg/svg-xml-list';
+import { useStyle } from './styles';
import { useTheme } from 'react-native-paper';
import type { MyMD3Theme } from 'src/providers/amity-ui-kit-provider';
+import RenderCategories from './Components/RenderCategories';
interface IModal {
visible: boolean;
userId?: string;
onClose: () => void;
onSelect: (categoryId: string, categoryName: string) => void;
+ categoryId?: string;
}
-const ChooseCategoryModal = ({ visible, onClose, onSelect }: IModal) => {
+const ChooseCategoryModal = ({
+ visible,
+ onClose,
+ onSelect,
+ categoryId,
+}: IModal) => {
const theme = useTheme() as MyMD3Theme;
- const styles = getStyles();
- const { apiRegion } = useAuth();
+ const styles = useStyle();
const [categories, setCategories] =
useState>();
const { data: categoriesList, onNextPage } = categories ?? {};
@@ -40,6 +44,12 @@ const ChooseCategoryModal = ({ visible, onClose, onSelect }: IModal) => {
(data: Amity.LiveCollection) => {
if (data) {
setCategories(data);
+ if (categoryId) {
+ const currentCategoryName =
+ data.data.find((item) => item.categoryId === categoryId)
+ ?.name ?? '';
+ onSelect(categoryId, currentCategoryName);
+ }
}
}
);
@@ -50,34 +60,13 @@ const ChooseCategoryModal = ({ visible, onClose, onSelect }: IModal) => {
};
loadCategories();
- }, []);
+ }, [categoryId, onSelect]);
const onSelectCategory = (categoryId: string, categoryName: string) => {
onSelect && onSelect(categoryId, categoryName);
unSubFunc && unSubFunc();
onClose && onClose();
};
- const renderCategories = ({ item }: { item: Amity.Category }) => {
- return (
- onSelectCategory(item.categoryId, item.name)}
- style={styles.rowContainer}
- >
- {item.avatarFileId ? (
-
- ) : (
-
- )}
-
- {item.name}
-
- );
- };
const handleScroll = (event: NativeSyntheticEvent) => {
const scrollPosition = event.nativeEvent.contentOffset.y;
@@ -112,7 +101,9 @@ const ChooseCategoryModal = ({ visible, onClose, onSelect }: IModal) => {
(
+
+ )}
keyExtractor={(item) => item.categoryId}
onScroll={handleScroll}
/>
diff --git a/src/components/ChooseCategoryModal/styles.ts b/src/components/ChooseCategoryModal/styles.ts
index 90168768..c5b049dc 100644
--- a/src/components/ChooseCategoryModal/styles.ts
+++ b/src/components/ChooseCategoryModal/styles.ts
@@ -2,7 +2,7 @@ import { Platform, StyleSheet } from 'react-native';
import { useTheme } from 'react-native-paper';
import type { MyMD3Theme } from 'src/providers/amity-ui-kit-provider';
-export const getStyles = () => {
+export const useStyle = () => {
const theme = useTheme() as MyMD3Theme;
const styles = StyleSheet.create({
diff --git a/src/components/CustomTab/index.tsx b/src/components/CustomTab/index.tsx
index e940c725..840c3804 100644
--- a/src/components/CustomTab/index.tsx
+++ b/src/components/CustomTab/index.tsx
@@ -8,10 +8,11 @@ import {
View,
} from 'react-native';
import { getStyles } from './styles';
+import { TabName } from '../../enum/tabNameState';
interface ICustomTab {
- onTabChange: (tabIndex: number) => any;
- tabName: string[];
+ onTabChange: (tabName: TabName) => void;
+ tabName: TabName[];
}
const CustomTab = ({ tabName, onTabChange }: ICustomTab): ReactElement => {
const styles = getStyles();
@@ -19,9 +20,15 @@ const CustomTab = ({ tabName, onTabChange }: ICustomTab): ReactElement => {
const [indicatorAnim] = useState(new Animated.Value(0));
const [tabOneWidth, setTabOneWidth] = useState(0);
const [tabTwoWidth, setTabTwoWidth] = useState(0);
- const handleTabPress = (tabIndex: number) => {
+ const handleTabPress = ({
+ name,
+ tabIndex,
+ }: {
+ name: TabName;
+ tabIndex: number;
+ }) => {
setActiveTab(tabIndex);
- onTabChange && onTabChange(tabIndex);
+ onTabChange && onTabChange(name);
Animated.timing(indicatorAnim, {
toValue: tabIndex,
duration: 100,
@@ -53,7 +60,7 @@ const CustomTab = ({ tabName, onTabChange }: ICustomTab): ReactElement => {
handleTabPress(1)}
+ onPress={() => handleTabPress({ name: tabName[0], tabIndex: 1 })}
>
{tabName[0]}
@@ -61,7 +68,7 @@ const CustomTab = ({ tabName, onTabChange }: ICustomTab): ReactElement => {
handleTabPress(2)}
+ onPress={() => handleTabPress({ name: tabName[1], tabIndex: 2 })}
>
{tabName[1]}
diff --git a/src/components/MyCommunity/Components/CommunityList.tsx b/src/components/MyCommunity/Components/CommunityList.tsx
new file mode 100644
index 00000000..5e321de5
--- /dev/null
+++ b/src/components/MyCommunity/Components/CommunityList.tsx
@@ -0,0 +1,82 @@
+import { Image, Text, TouchableOpacity, View } from 'react-native';
+import React from 'react';
+import { SvgXml } from 'react-native-svg';
+import {
+ communityIcon,
+ officialIcon,
+ privateIcon,
+} from '../../../svg/svg-xml-list';
+import { useStyle } from '../styles';
+import useImage from '../../../hooks/useImage';
+import type { MyMD3Theme } from '../../../providers/amity-ui-kit-provider';
+import { useTheme } from 'react-native-paper';
+import { PrivacyState } from '../../../enum/privacyState';
+
+interface ICommunityItems {
+ communityId: string;
+ avatarFileId: string;
+ displayName: string;
+ isPublic: boolean;
+ isOfficial: boolean;
+}
+
+const CommunityList = ({
+ item,
+ onClickItem,
+}: {
+ item: ICommunityItems;
+ onClickItem: (id: string, name: string) => void;
+}) => {
+ const MAX_LENGTH = 6;
+ const theme = useTheme() as MyMD3Theme;
+ const styles = useStyle();
+ const avatarUrl = useImage({ fileId: item.avatarFileId });
+ const getDisplayName = ({ text, type }: { text?: string; type: string }) => {
+ if (text) {
+ const reduceLetter = type === PrivacyState.private ? 3 : 0;
+ if (text!.length > MAX_LENGTH - reduceLetter) {
+ return text!.substring(0, MAX_LENGTH) + '...';
+ }
+ return text;
+ }
+ return 'Display name';
+ };
+ return (
+ onClickItem(item.communityId, item.displayName)}
+ key={item.communityId}
+ style={styles.itemContainer}
+ >
+ {item.avatarFileId ? (
+
+ ) : (
+
+ )}
+
+ {!item.isPublic && (
+
+ )}
+
+ {getDisplayName({
+ text: item.displayName,
+ type: !item.isPublic ? PrivacyState.private : PrivacyState.public,
+ })}
+
+ {item.isOfficial && (
+
+ )}
+
+
+ );
+};
+
+export default CommunityList;
diff --git a/src/components/MyCommunity/index.tsx b/src/components/MyCommunity/index.tsx
index d2b60fb1..3aad4ada 100644
--- a/src/components/MyCommunity/index.tsx
+++ b/src/components/MyCommunity/index.tsx
@@ -1,19 +1,14 @@
-import React, { useEffect, useState } from 'react';
-import { View, Text, ScrollView, Image, TouchableOpacity } from 'react-native';
-import { getStyles } from './styles';
+import React, { useCallback, useState } from 'react';
+import { View, Text, ScrollView, TouchableOpacity } from 'react-native';
+import { useStyle } from './styles';
import { CommunityRepository } from '@amityco/ts-sdk-react-native';
-import {
- arrowOutlined,
- communityIcon,
- officialIcon,
- privateIcon,
-} from '../../svg/svg-xml-list';
+import { arrowOutlined } from '../../svg/svg-xml-list';
import { SvgXml } from 'react-native-svg';
-import { useNavigation } from '@react-navigation/native';
+import { useFocusEffect, useNavigation } from '@react-navigation/native';
import type { NativeStackNavigationProp } from '@react-navigation/native-stack';
-import useAuth from '../../hooks/useAuth';
import type { MyMD3Theme } from '../../providers/amity-ui-kit-provider';
import { useTheme } from 'react-native-paper';
+import CommunityList from './Components/CommunityList';
interface ICommunityItems {
communityId: string;
@@ -24,14 +19,9 @@ interface ICommunityItems {
}
export default function MyCommunity() {
const theme = useTheme() as MyMD3Theme;
- const styles = getStyles();
- const { apiRegion } = useAuth();
- const maxLength = 6;
+ const styles = useStyle();
const [communityItems, setCommunityItems] = useState([]);
const navigation = useNavigation>();
- const avatarFileURL = (fileId: string) => {
- return `https://api.${apiRegion}.amity.co/api/v3/files/${fileId}/download?size=medium`;
- };
const queryCommunities = () => {
const unsubscribe = CommunityRepository.getCommunities(
{ membership: 'member', limit: 8 },
@@ -52,19 +42,12 @@ export default function MyCommunity() {
);
unsubscribe();
};
- const getDisplayName = (text: string, type: string) => {
- if (text) {
- const reduceLetter = type === 'private' ? 3 : 0;
- if (text!.length > maxLength - reduceLetter) {
- return text!.substring(0, maxLength) + '...';
- }
- return text;
- }
- return 'Display name';
- };
- useEffect(() => {
- queryCommunities();
- }, []);
+
+ useFocusEffect(
+ useCallback(() => {
+ queryCommunities();
+ }, [])
+ );
const onClickItem = (communityId: string, displayName: string) => {
navigation.navigate('CommunityHome', {
@@ -95,47 +78,11 @@ export default function MyCommunity() {
contentContainerStyle={styles.scrollView}
>
{communityItems.map((item) => (
- onClickItem(item.communityId, item.displayName)}
+
- {item.avatarFileId ? (
-
- ) : (
-
- )}
-
- {!item.isPublic && (
-
- )}
-
- {getDisplayName(
- item.displayName,
- !item.isPublic ? 'private' : 'public'
- )}
-
- {item.isOfficial && (
-
- )}
-
-
+ item={item}
+ onClickItem={onClickItem}
+ />
))}
diff --git a/src/components/MyCommunity/styles.ts b/src/components/MyCommunity/styles.ts
index b2e42d32..f2a3053f 100644
--- a/src/components/MyCommunity/styles.ts
+++ b/src/components/MyCommunity/styles.ts
@@ -1,7 +1,7 @@
import { StyleSheet } from 'react-native';
import { useTheme } from 'react-native-paper';
import type { MyMD3Theme } from 'src/providers/amity-ui-kit-provider';
-export const getStyles = () => {
+export const useStyle = () => {
const theme = useTheme() as MyMD3Theme;
const styles = StyleSheet.create({
diff --git a/src/enum/imageSizeState.ts b/src/enum/imageSizeState.ts
new file mode 100644
index 00000000..97061548
--- /dev/null
+++ b/src/enum/imageSizeState.ts
@@ -0,0 +1,12 @@
+export enum ImageSizeState {
+ small = 'small',
+ medium = 'medium',
+ large = 'large',
+ full = 'full',
+}
+
+export type ImageSizeSubset =
+ | ImageSizeState.small
+ | ImageSizeState.medium
+ | ImageSizeState.large
+ | ImageSizeState.full;
diff --git a/src/enum/privacyState.ts b/src/enum/privacyState.ts
new file mode 100644
index 00000000..7dbfce2b
--- /dev/null
+++ b/src/enum/privacyState.ts
@@ -0,0 +1,4 @@
+export enum PrivacyState {
+ private = 'private',
+ public = 'public',
+}
diff --git a/src/enum/tabNameState.ts b/src/enum/tabNameState.ts
new file mode 100644
index 00000000..c15371bf
--- /dev/null
+++ b/src/enum/tabNameState.ts
@@ -0,0 +1,8 @@
+export enum TabName {
+ NewsFeed = 'NewsFeed',
+ Explorer = 'Explorer',
+ Timeline = 'Timeline',
+ Gallery = 'Gallery',
+ Communities = 'Communities',
+ Accounts = 'Accounts',
+}
diff --git a/src/hooks/useImage.ts b/src/hooks/useImage.ts
new file mode 100644
index 00000000..2b830162
--- /dev/null
+++ b/src/hooks/useImage.ts
@@ -0,0 +1,35 @@
+import { useEffect, useState } from 'react';
+import { FileRepository } from '@amityco/ts-sdk-react-native';
+import { ImageSizeState, ImageSizeSubset } from '../enum/imageSizeState';
+
+interface UseImageProps {
+ fileId: string;
+ imageSize?: ImageSizeSubset;
+}
+
+const useImage = ({
+ fileId,
+ imageSize = ImageSizeState.medium,
+}: UseImageProps) => {
+ const [imageUrl, setImageUrl] = useState(undefined);
+
+ useEffect(() => {
+ if (fileId == null) {
+ setImageUrl(undefined);
+ return;
+ }
+
+ async function run() {
+ const file = await FileRepository.getFile(fileId);
+ const newImageUrl = !file
+ ? undefined
+ : await FileRepository.fileUrlWithSize(file.data.fileUrl, imageSize);
+ setImageUrl(newImageUrl);
+ }
+ run();
+ }, [fileId, imageSize]);
+
+ return imageUrl;
+};
+
+export default useImage;
diff --git a/src/providers/Social/communities-sdk.ts b/src/providers/Social/communities-sdk.ts
index ed0c0963..d5bf387d 100644
--- a/src/providers/Social/communities-sdk.ts
+++ b/src/providers/Social/communities-sdk.ts
@@ -51,6 +51,32 @@ export function createCommunity(
});
return communityObject;
}
+
+export const updateCommunity = (
+ communityId: string,
+ communityParam: ICreateCommunity
+): Promise => {
+ const communityObject = new Promise(async (resolve, reject) => {
+ try {
+ const newCommunity = {
+ description: communityParam.description,
+ displayName: communityParam.displayName,
+ isPublic: communityParam.isPublic,
+ categoryIds: [communityParam.category],
+ avatarFileId: communityParam?.avatarFileId ?? undefined,
+ };
+ const { data: community } = await CommunityRepository.updateCommunity(
+ communityId,
+ newCommunity
+ );
+ resolve(community);
+ } catch (error) {
+ reject(error);
+ }
+ });
+ return communityObject;
+};
+
export async function checkCommunityPermission(
communityId: string,
client: Amity.Client,
diff --git a/src/redux/slices/globalfeedSlice.ts b/src/redux/slices/globalfeedSlice.ts
index d757f4fd..03566fc5 100644
--- a/src/redux/slices/globalfeedSlice.ts
+++ b/src/redux/slices/globalfeedSlice.ts
@@ -13,7 +13,14 @@ const globalFeedSlice = createSlice({
initialState,
reducers: {
updateGlobalFeed: (state, action: PayloadAction) => {
- state.postList = [...state.postList, ...action.payload];
+ const getUniqueArrayById = (arr: IPost[]) => {
+ const uniqueIds = new Set(state.postList.map((post) => post.postId));
+ return arr.filter((post) => !uniqueIds.has(post.postId));
+ };
+ state.postList = [
+ ...state.postList,
+ ...getUniqueArrayById(action.payload),
+ ];
},
updateByPostId: (
diff --git a/src/routes/RouteParamList.tsx b/src/routes/RouteParamList.tsx
index 4401dd8d..89ce13a5 100644
--- a/src/routes/RouteParamList.tsx
+++ b/src/routes/RouteParamList.tsx
@@ -36,6 +36,9 @@ export type RootStackParamList = {
EditProfile: {
userId: string;
};
+ EditCommunity: {
+ communityId: string;
+ };
AllMyCommunity: undefined;
CreateCommunity: undefined;
PendingPosts: { communityId: string; isModerator: boolean };
diff --git a/src/routes/SocialNavigator.tsx b/src/routes/SocialNavigator.tsx
index f026da38..b61a9b2d 100644
--- a/src/routes/SocialNavigator.tsx
+++ b/src/routes/SocialNavigator.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable react/no-unstable-nested-components */
import { NavigationContainer } from '@react-navigation/native';
import * as React from 'react';
import {
@@ -26,9 +27,11 @@ import type { MyMD3Theme } from '../providers/amity-ui-kit-provider';
import { useTheme } from 'react-native-paper';
import { Image, TouchableOpacity } from 'react-native';
import { SvgXml } from 'react-native-svg';
-import { searchIcon } from '../svg/svg-xml-list';
-import { getStyles } from '../routes/style';
+import { closeIcon, searchIcon } from '../svg/svg-xml-list';
+import { useStyles } from '../routes/style';
import BackButton from '../components/BackButton';
+import CloseButton from '../components/CloseButton';
+import EditCommunity from '../screens/EditCommunity/EditCommunity';
export default function SocialNavigator() {
const Stack = createNativeStackNavigator();
@@ -40,7 +43,7 @@ export default function SocialNavigator() {
const onClickSearch = (navigation: NativeStackNavigationProp) => {
navigation.navigate('CommunitySearch');
};
- const styles = getStyles();
+ const styles = useStyles();
return (
{isConnected && (
@@ -131,10 +134,57 @@ export default function SocialNavigator() {
-
-
-
+ ;
+ }) => ({
+ headerLeft: () => (
+ {
+ navigation.goBack();
+ }}
+ style={styles.btnWrap}
+ >
+
+
+ ),
+ })}
+ />
+
+ ,
+ }}
+ />
+ ;
+ }) => ({
+ headerLeft: () => ,
+ title: 'Edit Profile',
+ headerTitleAlign: 'center',
+ })}
+ />
{
- // eslint-disable-next-line react-hooks/rules-of-hooks
- const theme = useTheme() as MyMD3Theme;
+export const useStyles = () => {
const styles = StyleSheet.create({
btnWrap: {
padding: 5,
@@ -13,9 +9,6 @@ export const getStyles = () => {
width: 16,
height: 12,
},
- saveText: {
- color: theme.colors.primary,
- },
});
return styles;
};
diff --git a/src/screens/AllMyCommunity/index.tsx b/src/screens/AllMyCommunity/index.tsx
index 4b672210..26aebf1d 100644
--- a/src/screens/AllMyCommunity/index.tsx
+++ b/src/screens/AllMyCommunity/index.tsx
@@ -1,5 +1,11 @@
/* eslint-disable react-hooks/exhaustive-deps */
-import React, { useEffect, useMemo, useRef, useState } from 'react';
+import React, {
+ useCallback,
+ useEffect,
+ useMemo,
+ useRef,
+ useState,
+} from 'react';
import {
View,
Text,
@@ -13,20 +19,14 @@ import {
import debounce from 'lodash.debounce';
import { getStyles } from './styles';
import { SvgXml } from 'react-native-svg';
-import {
- circleCloseIcon,
- closeIcon,
- plusIcon,
- searchIcon,
-} from '../../svg/svg-xml-list';
-import { useNavigation } from '@react-navigation/native';
+import { circleCloseIcon, plusIcon, searchIcon } from '../../svg/svg-xml-list';
import { CommunityRepository } from '@amityco/ts-sdk-react-native';
import type { ISearchItem } from '../../components/SearchItem';
import SearchItem from '../../components/SearchItem';
import { useTheme } from 'react-native-paper';
import type { MyMD3Theme } from '../../providers/amity-ui-kit-provider';
-export default function AllMyCommunity() {
+export default function AllMyCommunity({ navigation }) {
const theme = useTheme() as MyMD3Theme;
const styles = getStyles();
LogBox.ignoreAllLogs(true);
@@ -34,31 +34,13 @@ export default function AllMyCommunity() {
const [searchType] = useState('community');
const [communities, setCommunities] =
useState>();
- const navigation = useNavigation();
const [searchList, setSearchList] = useState([]);
const scrollViewRef = useRef(null);
const { data: communitiesArr = [], onNextPage } = communities ?? {};
- const goBack = () => {
- navigation.goBack();
- };
const onClickCreateCommunity = () => {
navigation.navigate('CreateCommunity');
};
- navigation.setOptions({
- // eslint-disable-next-line react/no-unstable-nested-components
- headerLeft: () => (
-
-
-
- ),
- headerRight: () => (
-
-
-
- ),
- headerTitle: 'My Community',
- });
const handleChange = (text: string) => {
setSearchTerm(text);
@@ -109,11 +91,24 @@ export default function AllMyCommunity() {
return debounce(handleChange, 500);
}, []);
+ const headerRight = useCallback(
+ () => (
+
+
+
+ ),
+ []
+ );
+
useEffect(() => {
+ navigation.setOptions({
+ headerRight: () => headerRight(),
+ headerTitle: 'My Community',
+ });
return () => {
debouncedResults.cancel();
};
- });
+ }, []);
const clearButton = () => {
setSearchTerm('');
diff --git a/src/screens/CommunityHome/index.tsx b/src/screens/CommunityHome/index.tsx
index ab580930..0315d0a0 100644
--- a/src/screens/CommunityHome/index.tsx
+++ b/src/screens/CommunityHome/index.tsx
@@ -7,11 +7,10 @@ import {
} from '@amityco/ts-sdk-react-native';
import React, {
type MutableRefObject,
- useEffect,
useRef,
useState,
- useLayoutEffect,
useCallback,
+ useMemo,
} from 'react';
import {
View,
@@ -34,9 +33,11 @@ import { MyMD3Theme } from '../../providers/amity-ui-kit-provider';
import { IPost } from '../../components/Social/PostList';
import { amityPostsFormatter } from '../../util/postDataFormatter';
import { checkCommunityPermission } from '../../providers/Social/communities-sdk';
-import { useNavigation } from '@react-navigation/native';
+import { useFocusEffect, useNavigation } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import FloatingButton from '../../components/FloatingButton';
+import useImage from '../../hooks/useImage';
+import { TabName } from '../../enum/tabNameState';
export type FeedRefType = {
handleLoadMore: () => void;
@@ -54,37 +55,36 @@ export default function CommunityHome({ route }: any) {
const [isJoin, setIsJoin] = useState(true);
const [communityData, setCommunityData] =
useState>();
-
+ const avatarUrl = useImage({ fileId: communityData?.data.avatarFileId });
const feedRef: MutableRefObject =
useRef(null);
const scrollViewRef = useRef(null);
-
const [pendingPosts, setPendingPosts] = useState([]);
const [isShowPendingArea, setIsShowPendingArea] = useState(false);
const [isUserHasPermission, setIsUserHasPermission] =
useState(false);
const [postSetting, setPostSetting] = useState('');
+ const disposers: Amity.Unsubscriber[] = useMemo(() => [], []);
+ const isSubscribed = useRef(false);
+ const subscribePostTopic = useCallback(
+ (targetType: string) => {
+ if (isSubscribed.current) return;
- const disposers: Amity.Unsubscriber[] = [];
- let isSubscribed = false;
-
- const subscribePostTopic = (targetType: string) => {
- if (isSubscribed) return;
-
- if (targetType === 'community') {
- disposers.push(
- subscribeTopic(
- getCommunityTopic(communityData?.data, SubscriptionLevels.POST),
- () => {
- // use callback to handle errors with event subscription
- }
- )
- );
- isSubscribed = true;
- }
- };
-
- const getPendingPosts = async () => {
+ if (targetType === 'community') {
+ disposers.push(
+ subscribeTopic(
+ getCommunityTopic(communityData?.data, SubscriptionLevels.POST),
+ () => {
+ // use callback to handle errors with event subscription
+ }
+ )
+ );
+ isSubscribed.current = true;
+ }
+ },
+ [communityData?.data, disposers]
+ );
+ const getPendingPosts = useCallback(async () => {
const unsubscribe = PostRepository.getPosts(
{
targetId: communityId,
@@ -112,13 +112,15 @@ export default function CommunityHome({ route }: any) {
) {
setIsUserHasPermission(true);
}
- };
+ }, [apiRegion, client, communityId, disposers, subscribePostTopic]);
- useEffect(() => {
- if (postSetting === 'ADMIN_REVIEW_POST_REQUIRED') {
- setIsShowPendingArea(true);
- }
- }, [postSetting]);
+ useFocusEffect(
+ useCallback(() => {
+ if (postSetting === 'ADMIN_REVIEW_POST_REQUIRED') {
+ setIsShowPendingArea(true);
+ }
+ }, [postSetting])
+ );
const loadCommunity = useCallback(async () => {
try {
@@ -141,14 +143,16 @@ export default function CommunityHome({ route }: any) {
console.error('Failed to load communities:', error);
}
}, [communityId]);
- useLayoutEffect(() => {
- getPendingPosts();
- loadCommunity();
- return () => {
- disposers.forEach((fn) => fn());
- };
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [getPendingPosts, loadCommunity]);
+
+ useFocusEffect(
+ useCallback(() => {
+ getPendingPosts();
+ loadCommunity();
+ return () => {
+ disposers.forEach((fn) => fn());
+ };
+ }, [disposers, getPendingPosts, loadCommunity])
+ );
const handleScroll = (event: NativeSyntheticEvent) => {
const { layoutMeasurement, contentOffset, contentSize } = event.nativeEvent;
@@ -196,8 +200,8 @@ export default function CommunityHome({ route }: any) {
);
};
- const handleTab = (index: number) => {
- console.log('index: ', index);
+ const handleTab = (tabName: TabName) => {
+ console.log('index: ', tabName); //this func not implmented yet
};
const handleClickPendingArea = () => {
@@ -238,7 +242,7 @@ export default function CommunityHome({ route }: any) {
const onEditProfileTap = () => {
navigation.navigate('EditCommunity', {
- communityId: communityId,
+ communityData,
});
};
@@ -254,9 +258,9 @@ export default function CommunityHome({ route }: any) {
}
{isJoin && isShowPendingArea ? pendingPostArea() : }
-
+
diff --git a/src/screens/CommunitySearch/index.tsx b/src/screens/CommunitySearch/index.tsx
index 6ecd51b7..d59da55e 100644
--- a/src/screens/CommunitySearch/index.tsx
+++ b/src/screens/CommunitySearch/index.tsx
@@ -1,4 +1,3 @@
-/* eslint-disable react-hooks/exhaustive-deps */
import React, { useEffect, useState } from 'react';
import {
View,
@@ -22,13 +21,19 @@ import type { ISearchItem } from '../../components/SearchItem';
import SearchItem from '../../components/SearchItem';
import { useTheme } from 'react-native-paper';
import type { MyMD3Theme } from '../../providers/amity-ui-kit-provider';
+import { TabName } from '../../enum/tabNameState';
+
+enum searchTypeEnum {
+ user = 'user',
+ community = 'community',
+}
export default function CommunitySearch() {
const theme = useTheme() as MyMD3Theme;
LogBox.ignoreAllLogs(true);
const styles = getStyles();
const [searchTerm, setSearchTerm] = useState('');
- const [searchType, setSearchType] = useState('community');
+ const [searchType, setSearchType] = useState(searchTypeEnum.community);
const [communities, setCommunities] =
useState>();
const [usersObject, setUsersObject] =
@@ -58,7 +63,7 @@ export default function CommunitySearch() {
} else if (searchTerm.length > 0 && searchType === 'user') {
searchAccounts(searchTerm);
}
- }, [searchTerm]);
+ }, [searchTerm, searchType]);
const searchCommunities = (text: string) => {
const unsubscribe = CommunityRepository.getCommunities(
@@ -90,7 +95,7 @@ export default function CommunitySearch() {
};
useEffect(() => {
- if (communitiesArr.length > 0 && searchType === 'community') {
+ if (communitiesArr.length > 0 && searchType === searchTypeEnum.community) {
const searchItem: ISearchItem[] = communitiesArr.map((item) => {
return {
targetId: item?.communityId,
@@ -105,7 +110,7 @@ export default function CommunitySearch() {
}, [communitiesArr, searchType]);
useEffect(() => {
- if (userArr && userArr.length > 0 && searchType === 'user') {
+ if (userArr && userArr.length > 0 && searchType === searchTypeEnum.user) {
const searchUsers: ISearchItem[] = userArr.map((item) => {
return {
targetId: item?.userId,
@@ -125,14 +130,14 @@ export default function CommunitySearch() {
const cancelSearch = () => {
navigation.goBack();
};
- const handleTabChange = (index: number) => {
- if (index === 1) {
- setSearchType('community');
+ const handleTabChange = (tabName: TabName) => {
+ if (tabName === TabName.Communities) {
+ setSearchType(searchTypeEnum.community);
if (searchTerm.length > 0) {
searchCommunities(searchTerm);
}
- } else if (index === 2) {
- setSearchType('user');
+ } else if (tabName === TabName.Accounts) {
+ setSearchType(searchTypeEnum.user);
if (searchTerm.length > 0) {
searchAccounts(searchTerm);
}
@@ -164,7 +169,7 @@ export default function CommunitySearch() {
diff --git a/src/screens/CreateCommunity/index.tsx b/src/screens/CreateCommunity/index.tsx
index c59dd207..091c75fe 100644
--- a/src/screens/CreateCommunity/index.tsx
+++ b/src/screens/CreateCommunity/index.tsx
@@ -1,5 +1,5 @@
import * as React from 'react';
-import { useEffect, useState } from 'react';
+import { useCallback, useEffect, useState } from 'react';
// import { useTranslation } from 'react-i18next';
import {
@@ -37,6 +37,7 @@ import { ActivityIndicator, useTheme } from 'react-native-paper';
import type { MyMD3Theme } from '../../providers/amity-ui-kit-provider';
import * as ImagePicker from 'expo-image-picker';
import { uploadImageFile } from '../../providers/file-provider';
+import { PrivacyState } from '../../enum/privacyState';
export default function CreateCommunity() {
const styles = getStyles();
@@ -88,19 +89,19 @@ export default function CreateCommunity() {
// setImage(result.assets[0]?.uri);
// }
// };
- const uploadFile = async () => {
+ const uploadFile = useCallback(async () => {
const file: Amity.File[] = await uploadImageFile(image);
if (file) {
setImageFileId(file[0].fileId);
setUploadingImage(false);
}
- };
+ }, [image]);
useEffect(() => {
if (image) {
setUploadingImage(true);
uploadFile();
}
- }, [image]);
+ }, [image, uploadFile]);
const pickImage = async () => {
let result = await ImagePicker.launchImageLibraryAsync({
@@ -142,17 +143,12 @@ export default function CreateCommunity() {
setSelectedUserList(removedUser);
};
- useEffect(() => {
- if (isCreating && !uploadingImage) {
- onCreateCommunity();
- }
- }, [uploadingImage]);
-
- const onCreateCommunity = async () => {
+ const onCreateCommunity = useCallback(async () => {
setIsCreating(true);
if (!uploadingImage) {
const userIds: string[] = selectedUserList.map((item) => item.userId);
- const isPublic: boolean = selectedId === 'private' ? false : true;
+ const isPublic: boolean =
+ selectedId === PrivacyState.private ? false : true;
const communityParam: ICreateCommunity = {
displayName: communityName,
description: aboutText,
@@ -169,7 +165,16 @@ export default function CreateCommunity() {
});
}
}
- };
+ }, [
+ aboutText,
+ categoryId,
+ communityName,
+ imageFileId,
+ navigation,
+ selectedId,
+ selectedUserList,
+ uploadingImage,
+ ]);
return (
setSelectedId('public')}
+ onPress={() => setSelectedId(PrivacyState.public)}
style={styles.listItem}
>
@@ -270,17 +275,21 @@ export default function CreateCommunity() {
setSelectedId(value)}
- value={'public'}
- selected={selectedId === 'public'}
- color={selectedId === 'public' ? theme.colors.primary : '#444'}
+ value={PrivacyState.public}
+ selected={selectedId === PrivacyState.public}
+ color={
+ selectedId === PrivacyState.public
+ ? theme.colors.primary
+ : '#444'
+ }
size={17}
/>
setSelectedId('private')}
+ onPress={() => setSelectedId(PrivacyState.private)}
style={styles.listItem}
>
@@ -295,16 +304,16 @@ export default function CreateCommunity() {
setSelectedId(value)}
- value={'private'}
- selected={selectedId === 'private'}
- color={selectedId === 'private' ? '#1054DE' : '#444'}
+ value={PrivacyState.private}
+ selected={selectedId === PrivacyState.private}
+ color={selectedId === PrivacyState.private ? '#1054DE' : '#444'}
size={17}
/>
- {selectedId === 'private' && (
+ {selectedId === PrivacyState.private && (
diff --git a/src/screens/CreatePost/index.tsx b/src/screens/CreatePost/index.tsx
index a88e7bae..9d92096d 100644
--- a/src/screens/CreatePost/index.tsx
+++ b/src/screens/CreatePost/index.tsx
@@ -1,7 +1,7 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { useNavigation } from '@react-navigation/native';
import type { NativeStackNavigationProp } from '@react-navigation/native-stack';
-import React, { useEffect, useState } from 'react';
+import React, { useCallback, useEffect, useState } from 'react';
import {
TouchableOpacity,
View,
@@ -83,33 +83,36 @@ const CreatePost = ({ route }: any) => {
const videoRef = React.useRef(null);
const { client, apiRegion } = useAuth();
- const getCommunityDetail = () => {
+ const getCommunityDetail = useCallback(() => {
if (targetType === 'community') {
CommunityRepository.getCommunity(targetId, setCommunityObject);
}
- };
+ }, [targetId, targetType]);
useEffect(() => {
getCommunityDetail();
- }, [targetId]);
-
- const checkMention = (inputString: string) => {
- // Check if "@" is at the first letter
- const startsWithAt = /^@/.test(inputString);
-
- // Check if "@" is inside the sentence without any letter before "@"
- const insideWithoutLetterBefore = /[^a-zA-Z]@/.test(inputString);
-
- const atSigns = inputString.match(/@/g);
- const atSignsNumber = atSigns ? atSigns.length : 0;
- if (
- (startsWithAt || insideWithoutLetterBefore) &&
- atSignsNumber > mentionNames.length
- ) {
- setIsShowMention(true);
- } else {
- setIsShowMention(false);
- }
- };
+ }, [getCommunityDetail]);
+
+ const checkMention = useCallback(
+ (inputString: string) => {
+ // Check if "@" is at the first letter
+ const startsWithAt = /^@/.test(inputString);
+
+ // Check if "@" is inside the sentence without any letter before "@"
+ const insideWithoutLetterBefore = /[^a-zA-Z]@/.test(inputString);
+
+ const atSigns = inputString.match(/@/g);
+ const atSignsNumber = atSigns ? atSigns.length : 0;
+ if (
+ (startsWithAt || insideWithoutLetterBefore) &&
+ atSignsNumber > mentionNames.length
+ ) {
+ setIsShowMention(true);
+ } else {
+ setIsShowMention(false);
+ }
+ },
+ [mentionNames.length]
+ );
useEffect(() => {
if (isShowMention) {
const substringBeforeCursor = inputMessage.substring(0, cursorIndex);
@@ -122,11 +125,11 @@ const CreatePost = ({ route }: any) => {
setCurrentSearchUserName(searchText);
}
}
- }, [cursorIndex]);
+ }, [cursorIndex, inputMessage, isShowMention]);
useEffect(() => {
checkMention(inputMessage);
- }, [inputMessage]);
+ }, [checkMention, inputMessage]);
const playVideoFullScreen = async (fileUrl: string) => {
if (videoRef) {
@@ -140,48 +143,8 @@ const CreatePost = ({ route }: any) => {
}
};
const goBack = () => {
- setTimeout(() => {
- navigation.goBack();
- }, 300);
+ navigation.goBack();
};
- navigation.setOptions({
- // eslint-disable-next-line react/no-unstable-nested-components
- header: () => (
-
-
-
-
-
-
- {targetName}
-
- 0 ||
- displayImages.length > 0 ||
- displayVideos.length > 0
- ? false
- : true
- }
- onPress={handleCreatePost}
- >
- 0 ||
- displayImages.length > 0 ||
- displayVideos.length > 0
- ? styles.postText
- : [styles.postText, styles.disabled]
- }
- >
- Post
-
-
-
-
- ),
- headerTitle: '',
- });
const handleCreatePost = async () => {
const mentionUserIds: string[] = mentionNames.map((item) => item.targetId);
if (displayImages.length > 0) {
@@ -482,7 +445,7 @@ const CreatePost = ({ route }: any) => {
setCursorIndex(event.nativeEvent.selection.start);
};
- const RenderTextWithMention = () => {
+ const renderTextWithMention = () => {
if (mentionsPosition.length === 0) {
return {inputMessage};
}
@@ -529,10 +492,43 @@ const CreatePost = ({ route }: any) => {
});
setMentionNames(checkMentionNames);
setMentionsPosition(checkMentionPosition);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
}, [inputMessage]);
return (
+
+
+
+
+
+
+ {targetName}
+
+ 0 ||
+ displayImages.length > 0 ||
+ displayVideos.length > 0
+ ? false
+ : true
+ }
+ onPress={handleCreatePost}
+ >
+ 0 ||
+ displayImages.length > 0 ||
+ displayVideos.length > 0
+ ? styles.postText
+ : [styles.postText, styles.disabled]
+ }
+ >
+ Post
+
+
+
+
{
onSelectionChange={handleSelectionChange}
/>
{mentionNames.length > 0 && (
-
- {/* {renderTextWithMention()} */}
-
-
+ {renderTextWithMention()}
)}
{/* */}
diff --git a/src/screens/EditCommunity/EditCommunity.tsx b/src/screens/EditCommunity/EditCommunity.tsx
new file mode 100644
index 00000000..ecdfac94
--- /dev/null
+++ b/src/screens/EditCommunity/EditCommunity.tsx
@@ -0,0 +1,418 @@
+import * as React from 'react';
+import { useCallback, useEffect, useState } from 'react';
+import {
+ View,
+ Text,
+ TouchableOpacity,
+ Image,
+ TextInput,
+ ScrollView,
+ Pressable,
+ FlatList,
+} from 'react-native';
+import { SvgXml } from 'react-native-svg';
+import {
+ arrowOutlined,
+ closeIcon,
+ plusIcon,
+ privateIcon,
+ publicIcon,
+} from '../../svg/svg-xml-list';
+import { useStyles } from './styles';
+import ChooseCategoryModal from '../../components/ChooseCategoryModal';
+import { RadioButton } from 'react-native-radio-buttons-group';
+import AddMembersModal from '../../components/AddMembersModal';
+import type { UserInterface } from 'src/types/user.interface';
+import useAuth from '../../hooks/useAuth';
+import { useTheme } from 'react-native-paper';
+import type { MyMD3Theme } from '../../providers/amity-ui-kit-provider';
+import * as ImagePicker from 'expo-image-picker';
+import { uploadImageFile } from '../../providers/file-provider';
+import { getAvatarURL } from '../../util/apiUtil';
+import { updateCommunity } from '../../providers/Social/communities-sdk';
+import { PrivacyState } from '../../enum/privacyState';
+import { useForm, Controller } from 'react-hook-form';
+
+const EditCommunity = ({ navigation, route }) => {
+ const styles = useStyles();
+ const theme = useTheme() as MyMD3Theme;
+ const {
+ communityData: { data },
+ }: { communityData: { data: Amity.RawCommunity } } = route.params;
+ const { apiRegion } = useAuth();
+ const {
+ control,
+ handleSubmit,
+ formState: { errors, defaultValues },
+ watch,
+ } = useForm({
+ defaultValues: {
+ community_name: data.displayName,
+ community_description: data.description,
+ },
+ });
+ const [image, setImage] = useState('');
+ const [categoryName, setCategoryName] = useState('');
+ const [categoryId, setCategoryId] = useState(data.categoryIds[0]);
+ const [categoryModal, setCategoryModal] = useState(false);
+ const [addMembersModal, setAddMembersModal] = useState(false);
+ const [isPublic, setisPublic] = useState(data.isPublic);
+ const [selectedUserList, setSelectedUserList] = useState([]);
+ const [loading, setLoading] = useState(false);
+ const [imageFileId, setImageFileId] = useState(data.avatarFileId);
+
+ const MAX_COMMUNITY_NAME_LENGTH = 30;
+ const MAX_ABOUT_TEXT_LENGTH = 180;
+
+ const onPressUpdateCommunity = useCallback(
+ async ({
+ community_name,
+ community_description,
+ }: {
+ community_name: string;
+ community_description: string;
+ }) => {
+ const communityDetail = {
+ isPublic: isPublic,
+ description: community_description,
+ displayName: community_name,
+ category: categoryId,
+ avatarFileId: imageFileId,
+ };
+ try {
+ setLoading(true);
+ await updateCommunity(data.communityId, communityDetail);
+ } catch (error) {
+ console.log(error);
+ } finally {
+ setLoading(false);
+ navigation.navigate({
+ name: 'CommunityHome',
+ params: {
+ communityId: data.communityId,
+ communityName: communityDetail.displayName,
+ },
+ merge: true,
+ });
+ }
+ },
+ [categoryId, data, imageFileId, isPublic, navigation]
+ );
+
+ useEffect(() => {
+ navigation.setOptions({
+ headerRight: () => (
+
+ Save
+
+ ),
+ });
+ }, [
+ handleSubmit,
+ loading,
+ navigation,
+ onPressUpdateCommunity,
+ styles.saveText,
+ ]);
+
+ useEffect(() => {
+ data.avatarFileId && setImage(getAvatarURL(apiRegion, data.avatarFileId));
+ }, [apiRegion, data.avatarFileId]);
+
+ const uploadFile = useCallback(async () => {
+ try {
+ const file: Amity.File[] = await uploadImageFile(image);
+ if (file) {
+ setImageFileId(file[0].fileId);
+ }
+ } catch (error) {
+ console.log(error);
+ } finally {
+ setLoading(false);
+ }
+ }, [image]);
+
+ useEffect(() => {
+ if (image) {
+ setLoading(true);
+ uploadFile();
+ }
+ }, [image, uploadFile]);
+
+ const pickImage = async () => {
+ let result = await ImagePicker.launchImageLibraryAsync({
+ mediaTypes: ImagePicker.MediaTypeOptions.Images,
+ allowsEditing: false,
+ quality: 1,
+ });
+
+ if (!result.canceled && result.assets && result.assets.length > 0) {
+ setImage(result.assets[0]?.uri);
+ }
+ };
+
+ const handleSelectCategory = (categoryId: string, categoryName: string) => {
+ setCategoryId(categoryId);
+ setCategoryName(categoryName);
+ };
+
+ const handleAddMembers = (users: UserInterface[]) => {
+ setSelectedUserList(users);
+ };
+
+ const displayName = (user: string) => {
+ const maxLength = 10;
+ if (user) {
+ if (user!.length > maxLength) {
+ return user!.substring(0, maxLength) + '..';
+ }
+ return user!;
+ }
+ return 'Display name';
+ };
+
+ const onDeleteUserPressed = (user: UserInterface) => {
+ const removedUser = selectedUserList.filter((item) => item !== user);
+ setSelectedUserList(removedUser);
+ };
+
+ return (
+
+
+
+ {image ? (
+
+ ) : (
+
+ )}
+
+ Upload Image
+
+
+
+
+
+
+ Community name *
+
+
+ {watch('community_name')
+ ? `${
+ watch('community_name').length
+ } / ${MAX_COMMUNITY_NAME_LENGTH}`
+ : `0/ ${MAX_COMMUNITY_NAME_LENGTH}`}
+
+
+ (
+
+ )}
+ rules={{ required: 'Community name is required!' }}
+ />
+ {errors.community_name && (
+
+ {errors.community_name.message?.toString()}
+
+ )}
+
+
+
+
+ About
+
+ {watch('community_description')
+ ? `${
+ watch('community_description').length
+ } / ${MAX_ABOUT_TEXT_LENGTH}`
+ : `0/ ${MAX_ABOUT_TEXT_LENGTH}`}
+
+
+
+ (
+
+ )}
+ />
+
+
+
+
+ Category *
+
+
+ setCategoryModal(true)}
+ style={styles.categoryContainer}
+ >
+
+ {categoryName.length > 0 ? categoryName : 'Select Category'}
+
+
+
+
+
+ setisPublic(true)}
+ style={styles.listItem}
+ >
+
+
+
+
+
+ Public
+
+ Anyone can join, view, and search the posts in this community.
+
+
+ setisPublic(true)}
+ value={PrivacyState.public}
+ selected={isPublic}
+ color={isPublic ? theme.colors.primary : '#444'}
+ size={17}
+ />
+
+
+ setisPublic(false)}
+ style={styles.listItem}
+ >
+
+
+
+
+
+ Private
+
+ Only members invited by the moderators can join, view, and
+ search the posts in this community.
+
+
+ setisPublic(false)}
+ value={PrivacyState.private}
+ selected={!isPublic}
+ color={!isPublic ? '#1054DE' : '#444'}
+ size={17}
+ />
+
+
+ {!isPublic && (
+
+
+
+ Add members *
+
+
+
+ {selectedUserList.length > 0 && (
+ (
+
+
+
+
+
+ {displayName(item.displayName)}
+
+ onDeleteUserPressed(item)}
+ >
+
+
+
+ )}
+ keyExtractor={(item) => item.userId.toString()}
+ numColumns={2}
+ />
+ )}
+
+ setAddMembersModal(true)}
+ style={styles.addIcon}
+ >
+
+
+
+
+
+
+ )}
+
+
+ setCategoryModal(false)}
+ visible={categoryModal}
+ categoryId={categoryId}
+ />
+ setAddMembersModal(false)}
+ visible={addMembersModal}
+ initUserList={selectedUserList}
+ />
+
+ );
+};
+
+export default EditCommunity;
diff --git a/src/screens/EditCommunity/styles.ts b/src/screens/EditCommunity/styles.ts
new file mode 100644
index 00000000..952feb59
--- /dev/null
+++ b/src/screens/EditCommunity/styles.ts
@@ -0,0 +1,197 @@
+import { StyleSheet } from 'react-native';
+import { useTheme } from 'react-native-paper';
+import type { MyMD3Theme } from 'src/providers/amity-ui-kit-provider';
+
+export const useStyles = () => {
+ const theme = useTheme() as MyMD3Theme;
+ const styles = StyleSheet.create({
+ container: {
+ paddingBottom: 320,
+ backgroundColor: theme.colors.screenBackground,
+ },
+ uploadContainer: {
+ width: '100%',
+ height: '35%',
+ alignItems: 'center',
+ justifyContent: 'center',
+ backgroundColor: '#f5f5f5',
+ },
+ defaultImage: {
+ width: '100%',
+ height: '100%',
+ backgroundColor: '#898e9e',
+ },
+ image: {
+ width: '100%',
+ height: '100%',
+ },
+ button: {
+ position: 'absolute',
+ backgroundColor: 'transparent',
+ borderColor: 'white',
+ borderWidth: 1,
+ paddingVertical: 10,
+ paddingHorizontal: 20,
+ borderRadius: 5,
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ buttonText: {
+ color: 'white',
+ fontWeight: 'bold',
+ },
+ btnWrap: {
+ padding: 10,
+ },
+ allInputContainer: {
+ paddingVertical: 24,
+ backgroundColor: theme.colors.background,
+ },
+ inputContainer: {
+ paddingHorizontal: 16,
+ justifyContent: 'center',
+ marginBottom: 24,
+ },
+
+ inputTitle: {
+ fontSize: 17,
+ fontWeight: '600',
+ color: theme.colors.base,
+ },
+ requiredField: {
+ color: 'red',
+ },
+ inputField: {
+ borderBottomWidth: 1,
+ borderBottomColor: theme.colors.border,
+ borderRadius: 5,
+ marginTop: 5,
+ paddingVertical: 16,
+ color: theme.colors.base,
+ },
+ inputLengthMeasure: {
+ alignSelf: 'flex-end',
+ marginTop: 5,
+ color: 'grey',
+ },
+ placeHolderText: {
+ color: '#A5A9B5',
+ },
+ titleRow: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ },
+ categoryContainer: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ paddingVertical: 16,
+ borderBottomWidth: 1,
+ borderBottomColor: theme.colors.border,
+ },
+ addIcon: {
+ marginHorizontal: 6,
+ },
+ arrowIcon: {
+ opacity: 0.75,
+ },
+ listItem: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ paddingBottom: 24,
+ paddingHorizontal: 16,
+ },
+ avatar: {
+ width: 40,
+ height: 40,
+ borderRadius: 72,
+ marginRight: 12,
+ backgroundColor: '#EBECEF',
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ itemText: {
+ fontSize: 15,
+ fontWeight: '600',
+ color: theme.colors.base,
+ },
+ dotIcon: {
+ width: 16,
+ height: 12,
+ },
+ categoryText: {
+ fontSize: 13,
+ color: theme.colors.baseShade1,
+ marginTop: 4,
+ },
+ optionDescription: {
+ width: '70%',
+ },
+ radioGroup: {
+ borderBottomWidth: 1,
+ borderBottomColor: theme.colors.border,
+ marginBottom: 24,
+ },
+ createButton: {
+ flexDirection: 'row',
+ backgroundColor: theme.colors.primary,
+ justifyContent: 'center',
+ alignItems: 'center',
+ paddingVertical: 10,
+ marginHorizontal: 16,
+ borderRadius: 4,
+ marginBottom: 24,
+ },
+ createText: {
+ fontWeight: '600',
+ fontSize: 15,
+ color: '#FFFFFF',
+ },
+ addUsersContainer: {
+ marginVertical: 6,
+ borderBottomWidth: 1,
+ borderBottomColor: theme.colors.border,
+ paddingBottom: 24,
+ },
+ userItemWrap: {
+ flexDirection: 'row',
+ backgroundColor: '#EBECEF',
+ borderRadius: 24,
+ padding: 6,
+ height: 40,
+ flex: 1,
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ margin: 6,
+ maxWidth: '45%',
+ },
+ avatarImageContainer: {
+ overflow: 'hidden',
+ borderRadius: 40,
+ width: 30,
+ height: 30,
+ marginRight: 5,
+ },
+ avatarImage: {
+ width: 30,
+ height: 30,
+ },
+ avatarRow: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ },
+ loading: {
+ marginLeft: 6,
+ },
+ saveText: {
+ color: theme.colors.primary,
+ },
+ errorText: {
+ color: theme.colors.error,
+ },
+ });
+
+ return styles;
+};
diff --git a/src/screens/Feed/index.tsx b/src/screens/Feed/index.tsx
index cd5f55db..c5b20c3c 100644
--- a/src/screens/Feed/index.tsx
+++ b/src/screens/Feed/index.tsx
@@ -1,7 +1,6 @@
import React, {
forwardRef,
useCallback,
- useEffect,
useImperativeHandle,
useState,
} from 'react';
@@ -26,6 +25,7 @@ import { amityPostsFormatter } from '../../util/postDataFormatter';
import { useDispatch, useSelector } from 'react-redux';
import { RootState } from '../../redux/store';
import feedSlice from '../../redux/slices/feedSlice';
+import { useFocusEffect } from '@react-navigation/native';
interface IFeed {
targetId: string;
@@ -84,20 +84,22 @@ function Feed({ targetId, targetType }: IFeed, ref: React.Ref) {
subscribePostTopic(targetType, targetId);
}
);
- setUnSubPageFunc(() => unsubscribe);
+ setUnSubPageFunc(() => unsubscribe());
}, [subscribePostTopic, targetId, targetType]);
const handleLoadMore = () => {
if (hasNextPage) {
onNextPage && onNextPage();
}
};
- useEffect(() => {
- getFeed();
- return () => {
- unSubFunc && unSubFunc();
- dispatch(clearFeed());
- };
- }, [clearFeed, dispatch, getFeed, unSubFunc]);
+ useFocusEffect(
+ useCallback(() => {
+ getFeed();
+ return () => {
+ unSubFunc && unSubFunc();
+ dispatch(clearFeed());
+ };
+ }, [clearFeed, dispatch, getFeed, unSubFunc])
+ );
const getPostList = useCallback(async () => {
if (posts.length > 0) {
@@ -106,9 +108,11 @@ function Feed({ targetId, targetType }: IFeed, ref: React.Ref) {
}
}, [dispatch, posts, updateFeed]);
- useEffect(() => {
- posts && getPostList();
- }, [posts, getPostList]);
+ useFocusEffect(
+ useCallback(() => {
+ posts && getPostList();
+ }, [posts, getPostList])
+ );
useImperativeHandle(ref, () => ({
handleLoadMore,
diff --git a/src/screens/GlobalFeed/index.tsx b/src/screens/GlobalFeed/index.tsx
index cbfd7ccd..ae7ffc00 100644
--- a/src/screens/GlobalFeed/index.tsx
+++ b/src/screens/GlobalFeed/index.tsx
@@ -1,4 +1,4 @@
-import React, { useCallback, useEffect, useRef, useState } from 'react';
+import React, { useCallback, useRef, useState } from 'react';
// import { useTranslation } from 'react-i18next';
@@ -17,6 +17,7 @@ import { amityPostsFormatter } from '../../util/postDataFormatter';
import { useDispatch, useSelector } from 'react-redux';
import globalFeedSlice from '../../redux/slices/globalfeedSlice';
import { RootState } from 'src/redux/store';
+import { useFocusEffect } from '@react-navigation/native';
export default function GlobalFeed() {
const { postList } = useSelector((state: RootState) => state.globalFeed);
@@ -25,7 +26,7 @@ export default function GlobalFeed() {
const dispatch = useDispatch(); // ()=> dispatch(updateGlobalFeed())
const styles = getStyles();
- const { client, isConnected } = useAuth();
+ const { isConnected } = useAuth();
const [postData, setPostData] = useState();
const { data: posts = [], nextPage } = postData ?? {};
@@ -44,20 +45,24 @@ export default function GlobalFeed() {
getGlobalFeedList(nextPage);
}
};
- useEffect(() => {
- if (isConnected) {
- getGlobalFeedList();
- }
- }, [client, isConnected]);
+ useFocusEffect(
+ useCallback(() => {
+ if (isConnected) {
+ getGlobalFeedList();
+ }
+ }, [isConnected])
+ );
const getPostList = useCallback(async () => {
if (posts.length > 0) {
const formattedPostList = await amityPostsFormatter(posts);
dispatch(updateGlobalFeed(formattedPostList));
}
}, [dispatch, posts, updateGlobalFeed]);
- useEffect(() => {
- posts && getPostList();
- }, [posts, getPostList]);
+ useFocusEffect(
+ useCallback(() => {
+ posts && getPostList();
+ }, [getPostList, posts])
+ );
const onDeletePost = async (postId: string) => {
const isDeleted = await deletePostById(postId);
diff --git a/src/screens/Home/index.tsx b/src/screens/Home/index.tsx
index cae96a19..5dbe72da 100644
--- a/src/screens/Home/index.tsx
+++ b/src/screens/Home/index.tsx
@@ -9,8 +9,6 @@ import {
Animated,
Modal,
Pressable,
- type StyleProp,
- type ImageStyle,
LogBox,
} from 'react-native';
import { SvgXml } from 'react-native-svg';
@@ -24,14 +22,14 @@ import CreatePostModal from '../../components/CreatePostModal';
import CustomTab from '../../components/CustomTab';
import { useTheme } from 'react-native-paper';
import type { MyMD3Theme } from '../../providers/amity-ui-kit-provider';
+import { TabName } from '../../enum/tabNameState';
LogBox.ignoreAllLogs(true);
export default function Home() {
// const { t, i18n } = useTranslation();
const styles = getStyles();
const { client } = useAuth();
const theme = useTheme() as MyMD3Theme;
-
- const [activeTab, setActiveTab] = useState(1);
+ const [activeTab, setActiveTab] = useState(TabName.NewsFeed);
const [isVisible, setIsVisible] = useState(false);
const [createPostModalVisible, setCreatePostModalVisible] = useState(false);
@@ -39,7 +37,6 @@ export default function Home() {
const openCreatePostModal = () => {
setCreatePostModalVisible(true);
};
-
const closeCreatePostModal = () => {
setCreatePostModalVisible(false);
closeModal();
@@ -67,29 +64,6 @@ export default function Home() {
}
}, [isVisible, slideAnimation]);
- const renderTabComponent = () => {
- let globalFeedStyle: StyleProp | StyleProp[] =
- styles.visible;
- styles.visible;
- let exploreStyle: StyleProp | StyleProp[] =
- styles.invisible;
- styles.visible;
- if (activeTab === 2) {
- globalFeedStyle = styles.invisible;
- exploreStyle = styles.visible;
- }
- return (
-
-
-
-
-
-
-
-
-
- );
- };
const modalStyle = {
transform: [
{
@@ -100,18 +74,22 @@ export default function Home() {
},
],
};
- const handleTabChange = (index: number) => {
- setActiveTab(index);
- };
return (
- {/* {renderTabView()} */}
- {renderTabComponent()}
-
+ {activeTab === TabName.NewsFeed ? (
+
+
+
+
+ ) : (
+
+
+
+ )}
{
// Set the headerRight component to a TouchableOpacity
navigation.setOptions({
- headerLeft: () => ,
headerRight: () => (
{
@@ -83,29 +82,8 @@ export default function UserProfile({ route }: any) {
/>
),
- title: '',
});
- }, [navigation]);
- useEffect(() => {
- navigation.setOptions({
- // Header options...
- headerRight: () => (
- {
- navigation.navigate('UserProfileSetting', {
- userId: userId,
- follow: followStatus,
- });
- }}
- >
-
-
- ),
- });
- }, [followStatus]);
+ }, [followStatus, navigation, styles.dotIcon, userId]);
useEffect(() => {
const unsubscribeFollow = UserRepository.Relationship.getFollowInfo(
userId,
@@ -123,7 +101,7 @@ export default function UserProfile({ route }: any) {
if (value && !value.loading) {
setUser(value.data);
} else {
- console.log('user profile query error ' + JSON.stringify(user));
+ console.log('user profile query error ' + JSON.stringify(value));
}
});
unsubscribeFollow();
@@ -149,7 +127,7 @@ export default function UserProfile({ route }: any) {
// Return the function to unsubscribe from the event so it gets removed on unmount
return unsubscribe;
- }, [navigation]);
+ }, [navigation, userId]);
const editProfileButton = () => {
return (
{
- console.log('index: ', index);
+ const handleTab = (tabName: TabName) => {
+ console.log('index: ', tabName); //this func not implmented yet
};
const handleScroll = (event: NativeSyntheticEvent) => {
const { layoutMeasurement, contentOffset, contentSize } = event.nativeEvent;
@@ -247,7 +225,10 @@ export default function UserProfile({ route }: any) {
)}
-
+
{/*
{
+ return `https://api.${apiRegion}.amity.co/api/v3/files/${fileId}/download?size=medium`;
+};
diff --git a/yarn.lock b/yarn.lock
index 4b68851a..e3daddf3 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -8800,6 +8800,11 @@ react-freeze@^1.0.0:
resolved "https://registry.npmjs.org/react-freeze/-/react-freeze-1.0.3.tgz"
integrity sha512-ZnXwLQnGzrDpHBHiC56TXFXvmolPeMjTn1UOm610M4EXGzbEDR7oOIyS2ZiItgbs6eZc4oU/a0hpk8PrcKvv5g==
+react-hook-form@^7.49.3:
+ version "7.49.3"
+ resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.49.3.tgz#576a4567f8a774830812f4855e89f5da5830435c"
+ integrity sha512-foD6r3juidAT1cOZzpmD/gOKt7fRsDhXXZ0y28+Al1CHgX+AY1qIN9VSIIItXRq1dN68QrRwl1ORFlwjBaAqeQ==
+
react-i18next@12.1.5:
version "12.1.5"
resolved "https://registry.npmjs.org/react-i18next/-/react-i18next-12.1.5.tgz"