Skip to content

Commit

Permalink
feat: added explore tab
Browse files Browse the repository at this point in the history
- updated activity indicator color
- refactored components
  • Loading branch information
burhanyilmaz committed Jan 14, 2024
1 parent 1d36341 commit 0fb682b
Show file tree
Hide file tree
Showing 13 changed files with 218 additions and 27 deletions.
4 changes: 4 additions & 0 deletions src/components/BlogTabHeader/BlogTabHeaderGoBack.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ import { Pressable } from 'react-native';
const BlogTabHeaderGoBack = () => {
const { dispatch } = useNavigation();

if (!__DEV__) {
return null;
}

return (
<Pressable className="ml-5" onPress={() => dispatch(StackActions.popToTop)}>
<ArrowLeft className="text-zinc-800" />
Expand Down
23 changes: 14 additions & 9 deletions src/components/Post/PostCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const CARD_IMAGE_WIDTH = (width - 40) * 0.3;
const CARD_IMAGE_HEIGHT = (width - 40) * 0.35;

type Props = {
title: string;
title?: string;
image?: string;
category?: string;
onPressPost: () => void;
Expand All @@ -25,22 +25,27 @@ type Props = {
const PostCard = ({ image, title, category, onPressPost, onPressSave }: Props) => (
<Pressable
onPress={onPressPost}
style={{ minHeight: 120 }}
className="pb-4 bg-white border-b-b1 border-zinc-100 mb-4 flex-row">
<View style={styles.cardImage} className="items-center justify-center bg-zinc-50 rounded-md">
<View
style={styles.cardImage}
className="items-center justify-center bg-zinc-50 rounded-md overflow-hidden">
{image ? (
<Image source={{ uri: image }} className="mb-2 rounded-md" style={styles.cardImage} />
) : (
<ActivityIndicator />
)}
</View>
<View className="flex-1 ml-4">
<View className="mb-2 h-5">
<Text className={text({ type: 'subhead', class: 'text-zinc-500' })}>{category}</Text>
<View className="flex-1 ml-4 justify-between">
<View>
<View className="mb-2 h-5">
<Text className={text({ type: 'subhead', class: 'text-zinc-500' })}>{category}</Text>
</View>
<Text className={text({ type: 'title4', class: 'text-zinc-900' })} numberOfLines={3}>
{title}
</Text>
</View>
<Text className={text({ type: 'title4', class: 'text-zinc-900' })} numberOfLines={3}>
{title}
</Text>
<View className="absolute bottom-0 left-0 w-full self-center justify-center items-end">
<View className="bottom-0 left-0 w-full self-center justify-center items-end">
<TouchableOpacity onPress={onPressSave}>
<Bookmark className="text-zinc-300" />
</TouchableOpacity>
Expand Down
3 changes: 2 additions & 1 deletion src/components/core/Button.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import colors from '@theme/colors';
import { text } from '@theme/text';
import { ActivityIndicator, Text, TouchableOpacity } from 'react-native';

Expand All @@ -10,7 +11,7 @@ type Props = {
const Button = ({ title, onPress, isLoading }: Props) => (
<TouchableOpacity onPress={onPress} className="px-6 py-4 bg-zinc-900 rounded-lg">
{isLoading ? (
<ActivityIndicator />
<ActivityIndicator color={colors.zinc[200]} />
) : (
<Text className={text({ type: 'subtitle', isCenter: true, class: 'text-white' })}>
{title}
Expand Down
24 changes: 15 additions & 9 deletions src/components/core/Input.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
import { TextInput } from 'react-native';
import { ReactNode } from 'react';
import { TextInput, View } from 'react-native';

type Props = {
value?: string;
placeholder?: string;
rightIcon?: ReactNode;
containerClass?: string;
onPressClose?: () => void;
onChangeText: (value: string) => void;
};

const Input = ({ value, containerClass, placeholder, onChangeText }: Props) => (
<TextInput
value={value}
placeholder={placeholder}
onChangeText={onChangeText}
className={`border-[2px] rounded-lg h-[50px] border-zinc-700 text-zinc-600 px-4 pr-10 font-Medium bg-white ${containerClass}`}
/>
const Input = ({ value, containerClass, placeholder, onChangeText, rightIcon }: Props) => (
<View className="">
<TextInput
value={value}
placeholder={placeholder}
onChangeText={onChangeText}
className={`border-[2px] rounded-lg h-[50px] border-zinc-700 text-zinc-600 px-4 pr-10 font-Medium bg-white ${containerClass}`}
/>
{rightIcon ? (
<View className="justify-center items-center absolute right-4 h-full z-50">{rightIcon}</View>
) : null}
</View>
);

export default Input;
17 changes: 17 additions & 0 deletions src/hooks/useDebounce.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { useEffect, useState } from 'react';

const useDebounce = (value: string, delay: number) => {
const [debouncedValue, setDebouncedValue] = useState(value);

useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);

return () => clearTimeout(handler);
}, [value]);

return debouncedValue;
};

export default useDebounce;
3 changes: 2 additions & 1 deletion src/navigators/BlogNavigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import BlogTabHeaderTitle from '@components/BlogTabHeader/BlogTabHeaderTitle';
import BottomTab from '@components/BottomTab';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import ExploreScreen from '@screens/Blog/ExploreScreen';
import BlogHomeScreen from '@screens/Blog/HomeScreen';
import TestScreen from '@screens/Test';

Expand Down Expand Up @@ -33,7 +34,7 @@ const BlogTab = () => (
headerTitle: props => <BlogTabHeaderTitle title={props.children} />,
}}>
<Tab.Screen name="Home" component={BlogHomeScreen} />
<Tab.Screen name="Explore" component={TestScreen} />
<Tab.Screen name="Explore" component={ExploreScreen} />
<Tab.Screen name="Saved" component={TestScreen} />
</Tab.Navigator>
);
Expand Down
49 changes: 49 additions & 0 deletions src/screens/Blog/ExploreScreen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import Input from '@components/core/Input';
import Post from '@containers/Post';
import useDebounce from '@hooks/useDebounce';
import searchStore from '@store/SearchStore';
import colors from '@theme/colors';
import { Search } from 'lucide-react-native';
import { observer } from 'mobx-react-lite';
import { useEffect } from 'react';
import { ActivityIndicator, FlatList, SafeAreaView, View } from 'react-native';

const ExploreScreen = () => {
const value = useDebounce(searchStore.term, 300);

useEffect(() => {
if (value.length > 2) {
searchStore.searchPost();
}
}, [value]);

return (
<SafeAreaView className="bg-white flex-1">
<View className="px-4 mb-4">
<Input
placeholder="Search..."
onChangeText={searchStore.setTerm}
rightIcon={
searchStore.loading ? (
<ActivityIndicator className="" color={colors.zinc[800]} />
) : (
<Search className="text-zinc-600" />
)
}
/>
</View>

<FlatList
windowSize={6}
className="px-5 pt-4"
removeClippedSubviews
initialNumToRender={6}
maxToRenderPerBatch={6}
data={searchStore.listResult || []}
renderItem={({ item }) => <Post post={item} />}
/>
</SafeAreaView>
);
};

export default observer(ExploreScreen);
9 changes: 7 additions & 2 deletions src/screens/Blog/HomeScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Post from '@containers/Post';
import postStore, { PostType } from '@store/PostStore';
import colors from '@theme/colors';
import { observer } from 'mobx-react-lite';
import { useEffect } from 'react';
import { ActivityIndicator, FlatList, ListRenderItem, View } from 'react-native';
Expand All @@ -19,7 +20,11 @@ const BlogHomeScreen = () => {
return (
<View className="bg-white flex-1">
{postStore.loading && posts.length > 0 && (
<ActivityIndicator size="large" className="absolute z-50 right-0 left-0 bottom-4" />
<ActivityIndicator
size="large"
color={colors.zinc[600]}
className="absolute z-50 right-0 left-0 bottom-4"
/>
)}
<FlatList
data={posts}
Expand All @@ -30,7 +35,7 @@ const BlogHomeScreen = () => {
maxToRenderPerBatch={6}
renderItem={RenderItem}
onEndReached={postStore.increasePage}
ListEmptyComponent={<ActivityIndicator />}
ListEmptyComponent={<ActivityIndicator size="large" color={colors.zinc[600]} />}
/>
</View>
);
Expand Down
3 changes: 2 additions & 1 deletion src/screens/Welcome/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,11 @@ const WelcomeScreen = () => {
<View className="w-full">
<Input
value={postStore.url}
containerClass="w-full mb-4"
containerClass="w-full"
onChangeText={postStore.setUrl}
placeholder="Type your wordpress site link"
/>
<View className="h-4" />
<Button
isLoading={postStore.loading}
onPress={onConvertWpIntoMobile}
Expand Down
11 changes: 10 additions & 1 deletion src/services/Api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,16 @@ class API {

getPost(pagination: number): Promise<ApiPostType[]> {
return this.instance
.url(`wp-json/wp/v2/posts?order_by=date&per_page=6&page=${pagination || 1}`)
.url(`wp-json/wp/v2/posts?order_by=date&per_page=12&page=${pagination || 1}`)
.get()
.json(result => result);
}

searchPost(term: string): Promise<ApiPostType[]> {
return this.instance
.url(
`wp-json/wp/v2/posts?search=${term}&orderby=relevance&per_page=50&search_columns=post_title`,
)
.get()
.json(result => result);
}
Expand Down
6 changes: 3 additions & 3 deletions src/store/PostStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { removeHtmlAndDecimalEntities } from '@utils/helpers';
import { ApiPostType } from '@utils/types/BlogTypes';
import { Instance, flow, t } from 'mobx-state-tree';

const Post = t
.model({
export const Post = t
.model('Post', {
title: t.string,
id: t.identifier,
mediaUrl: t.string,
Expand Down Expand Up @@ -42,7 +42,7 @@ const Post = t
}));

const PostStore = t
.model({
.model('PostStore', {
url: t.string,
page: t.number,
posts: t.map(Post),
Expand Down
80 changes: 80 additions & 0 deletions src/store/SearchStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { api } from '@services/Api';
import { removeHtmlAndDecimalEntities } from '@utils/helpers';
import { ApiPostType } from '@utils/types/BlogTypes';
import { Instance, detach, t } from 'mobx-state-tree';

import postStore, { Post } from './PostStore';

const SearchStore = t
.model('SearchStore', {
term: t.string,
results: t.map(Post),
loading: t.optional(t.boolean, false),
})
.views(self => ({
get listResult() {
return Array.from(self.results.values());
},
}))
.actions(self => ({
addPost: (post: ApiPostType) => {
self.results.set(
post.id,
Post.create({
id: post.id.toString(),
mediaUrl: post?._links?.['wp:featuredmedia']?.[0]?.href || '',
title: removeHtmlAndDecimalEntities(post?.title?.rendered || ''),
category: removeHtmlAndDecimalEntities(post?.primary_category?.name || ''),
}),
);
},

setTerm(term: string) {
self.term = term;
},

setLoading(loading: boolean) {
self.loading = loading;
},

clearResult() {
detach(self.results);
},

async searchPost() {
if (!postStore.url) {
return;
}

this.setLoading(true);
try {
const posts = await api.searchPost(self.term);
this.clearResult();
if (posts?.length > 0) {
posts.forEach(this.addPost);
}

this.setLoading(false);

return posts;
} catch {
this.setLoading(false);

return [];
}
},

clearAll() {
self.term = '';
self.loading = false;
self.results.clear();
},
}));

const searchStore = SearchStore.create({
term: '',
});

export default searchStore;

export type PostType = Instance<typeof Post>;
13 changes: 13 additions & 0 deletions src/theme/colors.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,17 @@
module.exports = {
white: '#ffffff',
black: '#000000',
zinc: {
50: '#fafafa',
100: '#f4f4f5',
200: '#e4e4e7',
300: '#d4d4d8',
400: '#a1a1aa',
500: '#71717a',
600: '#52525b',
700: '#3f3f46',
800: '#27272a',
900: '#18181b',
950: '#09090b',
},
};

0 comments on commit 0fb682b

Please sign in to comment.