-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Feature][Search Screen] 검색 페이지 기능 구현. (#262)
* style: searchicon added to main screen * noop search text input * feedlist in searchscreen rudimentary implementation done * search challenge ui implemented * styles added * search user screen implemented
- Loading branch information
Showing
14 changed files
with
463 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
export default `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> | ||
<g clip-path="url(#clip0_10404_24817)"> | ||
<path d="M20.6894 18.992L16.6246 14.9272C17.5819 13.6584 18.1563 12.0861 18.1563 10.378C18.1563 6.19934 14.7559 2.7998 10.5782 2.7998C6.39954 2.80069 3 6.20023 3 10.3788C3 14.5566 6.39954 17.957 10.5782 17.957C12.2854 17.957 13.8586 17.3826 15.1273 16.4253L19.1921 20.4901C19.3986 20.6966 19.6695 20.7998 19.9403 20.7998C20.2112 20.7998 20.4821 20.6966 20.6894 20.4892C21.1023 20.0763 21.1023 19.4058 20.6894 18.992ZM5.11754 10.3788C5.11754 7.36752 7.56683 4.91823 10.5782 4.91823C13.5886 4.91823 16.0379 7.36752 16.0379 10.378C16.0379 13.3884 13.5886 15.8377 10.5782 15.8377C7.56683 15.8386 5.11754 13.3893 5.11754 10.3788Z" fill="#121314" fill-opacity="0.8"/> | ||
</g> | ||
<defs> | ||
<clipPath id="clip0_10404_24817"> | ||
<rect width="24" height="24" fill="white"/> | ||
</clipPath> | ||
</defs> | ||
</svg> | ||
`; |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
import { ListRenderItemInfo, StyleSheet, Text, View } from 'react-native'; | ||
import React, { useContext } from 'react'; | ||
import { useInfiniteQuery } from '@tanstack/react-query'; | ||
import { SearchContext } from './SearchScreen'; | ||
import httpClient from '../../utils/api/BaseHttpClient'; | ||
import { SafeAreaView } from 'react-native-safe-area-context'; | ||
import { FlatList } from 'react-native-gesture-handler'; | ||
import CurrentChallengeProfile from '../../components/profile/ChallengeProfileCurrent'; | ||
import { ActivityIndicator } from 'react-native-paper'; | ||
|
||
export interface SearchChallenge { | ||
id: number; | ||
insightCnt: number; | ||
interestName: string; | ||
introduction: string; | ||
name: string; | ||
} | ||
|
||
const fetchSearchChallenges = async ({ searchText = 'test', page, limit }) => { | ||
const urlSearchParams = new URLSearchParams(); | ||
urlSearchParams.append('keyword', searchText); | ||
urlSearchParams.append('searchType', 'CHALLENGE'); | ||
urlSearchParams.append('limit', limit.toString()); | ||
if (page > 0) { | ||
urlSearchParams.append('cursor', page.toString()); | ||
} | ||
|
||
const queryString = urlSearchParams.toString(); | ||
const requestUrl = `https://api-keewe.com/api/v1/search?${queryString}`; | ||
console.log('Request URL:', requestUrl); // Debugging: Log the full request URL | ||
|
||
try { | ||
const response = await httpClient.get<SearchChallenge[]>(requestUrl); | ||
return response.data; | ||
} catch (error) { | ||
console.error('search insight screen', error); | ||
throw error; | ||
} | ||
}; | ||
|
||
const SearchChallengeScreen = () => { | ||
const { searchText } = useContext(SearchContext); | ||
|
||
const { data, fetchNextPage, hasNextPage, isFetchingNextPage, status } = useInfiniteQuery({ | ||
queryKey: ['searchScreen', 'challenge', searchText], | ||
queryFn: ({ pageParam = 0 }) => | ||
fetchSearchChallenges({ searchText, page: pageParam, limit: 10 }), | ||
getNextPageParam: (lastPage) => { | ||
return lastPage[lastPage.length - 1]?.id; | ||
}, | ||
}); | ||
|
||
const onEndReached = () => { | ||
if (hasNextPage) { | ||
fetchNextPage(); | ||
} | ||
}; | ||
|
||
const renderItem = ({ item, index }: ListRenderItemInfo<SearchChallenge>) => { | ||
console.log('renderItem', item); | ||
return ( | ||
<CurrentChallengeProfile | ||
key={index} | ||
name={item.name} | ||
challengeId={item.id} | ||
interest={item.interestName} | ||
challengeDescription={item.name} | ||
insightNumber={String(item.insightCnt)} | ||
participate={false} | ||
/> | ||
); | ||
}; | ||
|
||
if (status === 'loading') { | ||
return ( | ||
<View style={styles.centered}> | ||
<ActivityIndicator size="large" /> | ||
</View> | ||
); | ||
} | ||
|
||
if (status === 'error') { | ||
return ( | ||
<View style={styles.centered}> | ||
<Text>Failed to load challenges.</Text> | ||
</View> | ||
); | ||
} | ||
|
||
const flatListData = data?.pages.flatMap((page) => page) || []; | ||
|
||
return ( | ||
<SafeAreaView style={{ flex: 1, backgroundColor: 'white' }}> | ||
<FlatList | ||
data={flatListData} | ||
renderItem={(info) => renderItem(info)} | ||
keyExtractor={(item, index) => `challenge-${item.id}-${index}`} | ||
onEndReached={onEndReached} | ||
onEndReachedThreshold={0.5} | ||
ListFooterComponent={isFetchingNextPage ? <ActivityIndicator size="large" /> : null} | ||
style={styles.list} | ||
/> | ||
</SafeAreaView> | ||
); | ||
}; | ||
|
||
export default SearchChallengeScreen; | ||
|
||
const styles = StyleSheet.create({ | ||
centered: { | ||
flex: 1, | ||
justifyContent: 'center', | ||
alignItems: 'center', | ||
}, | ||
list: { | ||
paddingBottom: '100%', | ||
marginBottom: 80, | ||
}, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
import { StyleSheet, Text, View } from 'react-native'; | ||
import React, { useContext, useEffect, useRef, useState } from 'react'; | ||
import { SearchContext } from './SearchScreen'; | ||
import { useInfiniteQuery, useQueryClient } from '@tanstack/react-query'; | ||
import httpClient from '../../utils/api/BaseHttpClient'; | ||
import { RefreshControl, ScrollView } from 'react-native-gesture-handler'; | ||
import { SafeAreaView } from 'react-native-safe-area-context'; | ||
import { IOScrollView } from 'react-native-intersection-observer'; | ||
import FeedList from '../Feed/FeedList'; | ||
import { useTheme } from 'react-native-paper'; | ||
import { useScrollToTop } from '@react-navigation/native'; | ||
import { notificationKeys } from '../../utils/api/notification/notification'; | ||
import GoToUploadButton from '../../components/buttons/GoToUploadButton'; | ||
import { useInfiniteFeed } from '../../utils/hooks/feedInifiniteScroll/useInfiniteFeed'; | ||
interface fetchSearchInsightsParams { | ||
searchText: string; | ||
page: number; | ||
limit: number; | ||
} | ||
|
||
const fetchSearchInsights = async ({ | ||
searchText = 'test', | ||
page, | ||
limit, | ||
}: fetchSearchInsightsParams) => { | ||
const urlSearchParams = new URLSearchParams(); | ||
urlSearchParams.append('keyword', searchText); | ||
urlSearchParams.append('searchType', 'INSIGHT'); | ||
urlSearchParams.append('limit', limit.toString()); | ||
if (page > 0) { | ||
urlSearchParams.append('cursor', page.toString()); | ||
} | ||
|
||
const queryString = urlSearchParams.toString(); | ||
const requestUrl = `https://api-keewe.com/api/v1/search?${queryString}`; | ||
console.log('Request URL:', requestUrl); // Debugging: Log the full request URL | ||
|
||
try { | ||
const response = await httpClient.get<InsightData[]>(requestUrl); | ||
return response.data; | ||
} catch (error) { | ||
console.error('search insight screen', error); | ||
throw error; | ||
} | ||
}; | ||
|
||
const SearchInsightScreen = () => { | ||
const { searchText } = useContext(SearchContext); | ||
|
||
const { | ||
data: feedList, | ||
fetchNextPage, | ||
isLoading: feedListIsLoading, | ||
} = useInfiniteQuery<InsightData[] | undefined>({ | ||
queryKey: ['search', searchText], | ||
queryFn: ({ pageParam = 0 }) => fetchSearchInsights({ searchText, page: pageParam, limit: 10 }), | ||
}); | ||
|
||
const scrollViewRef = useRef(null); | ||
|
||
if (feedListIsLoading) { | ||
return ( | ||
<View> | ||
<Text>loading</Text> | ||
</View> | ||
); | ||
} | ||
if (!feedList) { | ||
return ( | ||
<View> | ||
<Text>feedList is undefined</Text> | ||
</View> | ||
); | ||
} | ||
|
||
return ( | ||
<SafeAreaView style={{ flex: 1, backgroundColor: 'white' }}> | ||
<IOScrollView ref={scrollViewRef}> | ||
<FeedList | ||
scrollViewRef={scrollViewRef} | ||
feedList={feedList} | ||
fetchNextPage={fetchNextPage} | ||
feedListIsLoading={feedListIsLoading} | ||
/> | ||
</IOScrollView> | ||
<GoToUploadButton /> | ||
</SafeAreaView> | ||
); | ||
}; | ||
|
||
export default SearchInsightScreen; | ||
|
||
const styles = StyleSheet.create({}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import React, { createContext, useLayoutEffect, useState } from 'react'; | ||
import { ScrollView, Text, TextInput } from 'react-native'; | ||
import { | ||
MaterialTopTabView, | ||
createMaterialTopTabNavigator, | ||
} from '@react-navigation/material-top-tabs'; | ||
import SearchInsightScreen from './SearchInsightScreen'; | ||
import SearchUserScreen from './SearchUserScreen'; | ||
import SearchChallengeScreen from './SearchChallengeScreen'; | ||
import { useTheme } from 'react-native-paper'; | ||
// type SearchType = 'INSIGHT' | 'USER' | 'CHALLENGE'; | ||
|
||
const Tab = createMaterialTopTabNavigator(); | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-empty-function | ||
export const SearchContext = createContext({ searchText: '', setSearchText: (text: string) => {} }); | ||
|
||
const SearchScreen = ({ navigation }) => { | ||
const [searchText, setSearchText] = useState(''); | ||
const theme = useTheme(); | ||
useLayoutEffect(() => { | ||
navigation.setOptions({ | ||
headerTitle: () => ( | ||
<TextInput value={searchText} onChangeText={setSearchText} placeholder="Search" /> | ||
), | ||
}); | ||
}, []); | ||
|
||
return ( | ||
<SearchContext.Provider value={{ searchText, setSearchText }}> | ||
<Tab.Navigator | ||
initialRouteName="게시물" | ||
screenOptions={{ | ||
tabBarActiveTintColor: 'black', | ||
tabBarIndicatorStyle: { backgroundColor: theme.colors.brand.primary.main }, | ||
tabBarLabelStyle: { fontSize: 14, fontWeight: 'bold' }, | ||
tabBarStyle: { backgroundColor: 'white' }, | ||
}} | ||
> | ||
<Tab.Screen name="게시물" component={SearchInsightScreen} /> | ||
<Tab.Screen name="챌린지" component={SearchChallengeScreen} /> | ||
<Tab.Screen name="사용자" component={SearchUserScreen} /> | ||
</Tab.Navigator> | ||
</SearchContext.Provider> | ||
); | ||
}; | ||
|
||
export default SearchScreen; |
Oops, something went wrong.