Procházet zdrojové kódy

Added Post page and search post feature

new-app
Lazar Kostic před 3 roky
rodič
revize
c4fed67caf

+ 102
- 80
components/CustomDrawer/CustomDrawer.jsx Zobrazit soubor

@@ -1,86 +1,108 @@
import React, {useContext} from 'react';
import { View, Text, ImageBackground, Image, TouchableOpacity, StyleSheet, Linking } from 'react-native';
import { DrawerContentScrollView, DrawerItemList } from '@react-navigation/drawer';
import { AuthContext } from '../../context/AuthContext';
import Spinner from 'react-native-loading-spinner-overlay/lib';
import Loader from '../Loader';
import React, { useContext } from "react";
import {
View,
Text,
ImageBackground,
Image,
TouchableOpacity,
StyleSheet,
Linking,
} from "react-native";
import {
DrawerContentScrollView,
DrawerItemList,
} from "@react-navigation/drawer";
import { AuthContext } from "../../context/AuthContext";
import Loader from "../Loader";
import { MaterialIcons } from "@expo/vector-icons";
import { Ionicons } from "@expo/vector-icons";

const CustomDrawer = props => {
const CustomDrawer = (props) => {
const { logout, isLoading } = useContext(AuthContext);

const {logout, isLoading} = useContext(AuthContext);

return (
<View style={styles.container}>
<Loader visible={isLoading} />
<DrawerContentScrollView {...props} contentContainerStyle={styles.drawer}>
<ImageBackground source={require('../../assets/images/menu-bg.jpeg')} style={styles.backgroundImage}>
<Image source={require('../../assets/images/diligent-purple.png')} style={styles.profileImage} />
<Text style={styles.userName}>Diligent Software</Text>
</ImageBackground>
<View style={styles.drawerItems}>
<DrawerItemList {...props} />
</View>
</DrawerContentScrollView>
<View style={styles.footer}>
<TouchableOpacity onPress={() => Linking.openURL('mailto:office@dilig.net')} style={styles.footerButton}>
<View style={styles.buttonContent}>
<Text style={styles.footerButtonText}>Contact Us</Text>
</View>
</TouchableOpacity>
<TouchableOpacity onPress={logout} style={styles.footerButton}>
<View style={styles.buttonContent}>
<Text style={styles.footerButtonText}>Sign Out</Text>
</View>
</TouchableOpacity>
</View>
return (
<View style={styles.container}>
<Loader visible={isLoading} />
<DrawerContentScrollView {...props} contentContainerStyle={styles.drawer}>
<ImageBackground
source={require("../../assets/images/menu-bg.jpeg")}
style={styles.backgroundImage}
>
<Image
source={require("../../assets/images/diligent-purple.png")}
style={styles.profileImage}
/>
<Text style={styles.userName}>Diligent Software</Text>
</ImageBackground>
<View style={styles.drawerItems}>
<DrawerItemList {...props} />
</View>
)
}
</DrawerContentScrollView>
<View style={styles.footer}>
<TouchableOpacity
onPress={() => Linking.openURL("mailto:office@dilig.net")}
style={styles.footerButton}
>
<View style={styles.buttonContent}>
<Ionicons name="mail-outline" size={24} color="#333" />
<Text style={styles.footerButtonText}>Contact Us</Text>
</View>
</TouchableOpacity>
<TouchableOpacity onPress={logout} style={styles.footerButton}>
<View style={styles.buttonContent}>
<MaterialIcons name="logout" size={22} color="#333" />
<Text style={styles.footerButtonText}>Sign Out</Text>
</View>
</TouchableOpacity>
</View>
</View>
);
};

const styles = StyleSheet.create({
container: {
flex: 1
},
drawer: {
backgroundColor: '#8200d6'
},
backgroundImage: {
padding: 20
},
profileImage: {
height: 80,
width: 80,
borderRadius: 40,
marginBottom: 10
},
userName: {
color: '#fff',
fontSize: 18,
marginBottom: 5,
fontFamily: 'poppins-regular'
},
drawerItems: {
flex: 1,
backgroundColor: '#fff',
paddingTop: 10,
},
footer: {
padding: 20,
borderTopWidth: 1,
borderTopColor: '#ccc'
},
footerButton: {
paddingVertical: 15
},
buttonContent: {
flexDirection: 'row',
alignItems: 'center'
},
footerButtonText: {
fontSize: 15,
marginLeft: 5,
fontFamily: 'poppins-regular'
}
})
container: {
flex: 1,
},
drawer: {
backgroundColor: "#8200d6",
},
backgroundImage: {
padding: 20,
},
profileImage: {
height: 80,
width: 80,
borderRadius: 40,
marginBottom: 10,
},
userName: {
color: "#fff",
fontSize: 18,
marginBottom: 5,
fontFamily: "poppins-regular",
},
drawerItems: {
flex: 1,
backgroundColor: "#fff",
paddingTop: 10,
},
footer: {
padding: 20,
borderTopWidth: 1,
borderTopColor: "#ccc",
},
footerButton: {
paddingVertical: 15,
},
buttonContent: {
flexDirection: "row",
alignItems: "center",
},
footerButtonText: {
fontSize: 15,
marginLeft: 5,
fontFamily: "poppins-regular",
},
});

export default CustomDrawer;
export default CustomDrawer;

+ 82
- 0
components/ListItem/ListItem.jsx Zobrazit soubor

@@ -0,0 +1,82 @@
import React from "react";
import { View, Text, Image, TouchableOpacity, StyleSheet } from "react-native";
import { globalStyles } from "../../styles/global";
import { windowWidth } from "../../utils/Dimensions";

const ListItem = ({ photo, title, onPress, publishedAt }) => {
const formatDate = (date) => {
const tempDate = new Date(date);
const fDate =
tempDate.getDate() +
"/" +
(tempDate.getMonth() + 1) +
"/" +
tempDate.getFullYear();

return fDate;
};

return (
<View style={styles.cardContainer}>
<View style={styles.imageContainer}>
<Image
style={styles.image}
source={{ uri: `http://localhost:1337${photo.small.url}` }}
/>
<View style={{ width: windowWidth - 220 }}>
<Text
style={{
color: "#333",
fontFamily: "poppins-regular",
fontSize: 14,
textTransform: "uppercase",
}}
>
{title}
</Text>
<Text
style={{
color: "#333",
fontFamily: "poppins-regular",
fontSize: 14,
textTransform: "uppercase",
}}
>
Published At {formatDate(publishedAt)}
</Text>
</View>
</View>
<TouchableOpacity style={globalStyles.button} onPress={onPress}>
<Text style={styles.buttonText}>Read More</Text>
</TouchableOpacity>
</View>
);
};

const styles = StyleSheet.create({
cardContainer: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
marginBottom: 20,
},
imageContainer: {
flexDirection: "row",
alignItems: "center",
flex: 1,
},
image: {
width: 55,
height: 55,
borderRadius: 10,
marginRight: 8,
},
buttonText: {
color: "#fff",
textAlign: "center",
fontFamily: "poppins-regular",
fontSize: 14,
},
});

export default ListItem;

+ 10
- 4
context/AuthContext.js Zobrazit soubor

@@ -2,7 +2,7 @@ import AsyncStorage from "@react-native-async-storage/async-storage";
import * as Google from "expo-auth-session/providers/google";
import React, { createContext, useEffect, useState } from "react";
import { googleApi, loginApi, registerApi } from "../service/user";
import {revokeAsync} from 'expo-auth-session'
import { revokeAsync } from "expo-auth-session";

export const AuthContext = createContext();

@@ -47,6 +47,7 @@ export const AuthProvider = ({ children }) => {
if (res.user) {
setUserInfo(res);
AsyncStorage.setItem("userInfo", JSON.stringify(res));
AsyncStorage.setItem("jwt-token", res.jwt);
setIsLoading(false);
} else {
callback(res);
@@ -70,6 +71,7 @@ export const AuthProvider = ({ children }) => {
if (res.user) {
setUserInfo(res);
AsyncStorage.setItem("userInfo", JSON.stringify(res));
AsyncStorage.setItem("jwt-token", res.jwt);
setIsLoading(false);
} else {
callback(res);
@@ -85,12 +87,16 @@ export const AuthProvider = ({ children }) => {
const logout = async () => {
setIsLoading(true);
AsyncStorage.removeItem("userInfo");
AsyncStorage.removeItem("jwt-token");
setUserInfo({});
setIsLoading(false);

if (userInfo.user.provider === 'google') {
await revokeAsync({token: response.authentication.accessToken}, {revocationEndpoint: 'https://oauth2.googleapis.com/revoke'})
if (userInfo.user.provider === "google") {
await revokeAsync(
{ token: response.authentication.accessToken },
{ revocationEndpoint: "https://oauth2.googleapis.com/revoke" }
);
}
setIsLoading(false);
};

const isLoggedIn = async () => {

+ 8
- 0
navigation/TabNavigator.js Zobrazit soubor

@@ -6,6 +6,7 @@ import { getFocusedRouteNameFromRoute } from "@react-navigation/native";
import HomeScreen from "../screens/HomeScreen";
import Ionicons from "@expo/vector-icons/Ionicons";
import FavoriteScreen from "../screens/FavoriteScreen";
import PostDetailsScreen from "../screens/PostDetailsScreen";

const Tab = createBottomTabNavigator();
const Stack = createNativeStackNavigator();
@@ -18,6 +19,13 @@ const HomeStack = () => {
component={HomeScreen}
options={{ headerShown: false }}
/>
<Stack.Screen
name="PostDetails"
component={PostDetailsScreen}
options={({route}) => ({
title: route.params.title
})}
/>
</Stack.Navigator>
);
};

+ 1
- 0
package.json Zobrazit soubor

@@ -23,6 +23,7 @@
"expo-status-bar": "~1.4.2",
"expo-web-browser": "~12.0.0",
"formik": "^2.2.9",
"lodash.filter": "^4.6.0",
"react": "18.1.0",
"react-dom": "18.1.0",
"react-native": "0.70.5",

+ 148
- 45
screens/HomeScreen.jsx Zobrazit soubor

@@ -1,48 +1,151 @@
import React from 'react';
import { View, Text, SafeAreaView, ScrollView, ImageBackground, TextInput, TouchableOpacity, StyleSheet } from 'react-native';
import Feather from '@expo/vector-icons/Feather';

import { windowWidth } from '../utils/Dimensions';

const HomeScreen = ({navigation}) => {
return (
<SafeAreaView style={{flex: 1, backgroundColor: '#fff'}}>
<ScrollView style={{padding: 20}}>
<View style={styles.wrapper}>
<Text style={{fontSize: 18}}>
Hello Diligent
</Text>
<TouchableOpacity onPress={() => navigation.openDrawer()}>
<ImageBackground source={require('../assets/images/diligent-purple.png')} style={styles.imageBackground} imageStyle={{borderRadius:25}} />
</TouchableOpacity>
</View>
<View style={styles.search}>
<Feather name='search' size={20} color='#C6C6C6' style={{marginRight: 5}} />
<TextInput placeholder='Search' placeholderTextColor='#C6C6C6' />
</View>
</ScrollView>
</SafeAreaView>
)
}
import React, { useContext, useEffect, useState } from "react";
import {
View,
Text,
SafeAreaView,
ScrollView,
ImageBackground,
TextInput,
TouchableOpacity,
StyleSheet,
} from "react-native";
import Feather from "@expo/vector-icons/Feather";
import { postsApi } from "../service/post";
import { AuthContext } from "../context/AuthContext";
import ListItem from "../components/ListItem/ListItem";
import filter from "lodash.filter";
import { globalStyles } from "../styles/global";

const styles = StyleSheet.create({
wrapper: {
flexDirection: 'row',
justifyContent: 'space-between',
marginBottom: 20
},
imageBackground: {
width: 35,
height: 35
},
search: {
flexDirection: 'row',
borderColor: '#C6C6C6',
borderWidth: 1,
borderRadius: 8,
paddingHorizontal: 10,
paddingVertical: 8
const HomeScreen = ({ navigation }) => {
const [posts, setPosts] = useState([]);
const { userInfo } = useContext(AuthContext);

const [query, setQuery] = useState("");
const [filteredData, setFilteredData] = useState(posts);

const contains = (name, search) => {
if (name.includes(search)) {
return true;
}
return false;
};

const searchFilter = (text) => {
const formatedText = text.toLowerCase();

const data = filter(posts, (item) => {
return contains(item?.attributes.title.toLowerCase(), formatedText);
});
setFilteredData(data);
setQuery(formatedText);
};

const fetchAll = () => {
if (userInfo.jwt) {
postsApi(userInfo.jwt)
.then((res) => setPosts(res.data))
.catch((e) => console.log(e));
}
};

useEffect(() => {
fetchAll();
}, []);

useEffect(() => {
if (posts) {
setFilteredData(posts);
}
})
}, [posts])
return (
<SafeAreaView style={{ flex: 1, backgroundColor: "#fff" }}>
<ScrollView style={{ padding: 20 }}>
<View style={styles.wrapper}>
<Text style={{ fontSize: 18 }}>Hello Diligent</Text>
<TouchableOpacity onPress={() => navigation.openDrawer()}>
<ImageBackground
source={require("../assets/images/diligent-purple.png")}
style={styles.imageBackground}
imageStyle={{ borderRadius: 25 }}
/>
</TouchableOpacity>
</View>
<View style={styles.search}>
<Feather
name="search"
size={20}
color="#C6C6C6"
style={{ marginRight: 5 }}
/>
<TextInput
onChangeText={(text) => searchFilter(text)}
autoCapitalize="none"
autoCorrect={false}
placeholder="Search"
value={query}
placeholderTextColor="#C6C6C6"
/>
</View>
<Text>
{filteredData.length === 0 && (
<View>
<Text style={globalStyles.boldText}>No Results Found</Text>
</View>
)}
</Text>
{query.length === 0
? posts.map((post) => (
<ListItem
key={post.id}
title={post.attributes.title}
photo={post.attributes.profileImage.data.attributes.formats}
publishedAt={post.attributes.publishedAt}
onPress={() =>
navigation.navigate("PostDetails", {
title: post.attributes.title,
id: post.id,
})
}
/>
))
: filteredData.map((post) => (
<ListItem
key={post.id}
title={post.attributes.title}
photo={post.attributes.profileImage.data.attributes.formats}
publishedAt={post.attributes.publishedAt}
onPress={() =>
navigation.navigate("PostDetails", {
title: post.attributes.title,
id: post.id,
})
}
/>
))}
</ScrollView>
</SafeAreaView>
);
};

const styles = StyleSheet.create({
wrapper: {
flexDirection: "row",
justifyContent: "space-between",
marginBottom: 20,
},
imageBackground: {
width: 35,
height: 35,
},
search: {
flexDirection: "row",
borderColor: "#C6C6C6",
borderWidth: 1,
borderRadius: 8,
paddingHorizontal: 10,
paddingVertical: 8,
marginBottom: 20,
},
});

export default HomeScreen;
export default HomeScreen;

+ 1
- 1
screens/OnboardingScreen.jsx Zobrazit soubor

@@ -28,7 +28,7 @@ const styles = StyleSheet.create({
},
headText: {
fontWeight: 'bold',
fontSize: 30,
fontSize: 70,
color: '#20315f',
fontFamily: 'poppins-semibold'
},

+ 54
- 0
screens/PostDetailsScreen.jsx Zobrazit soubor

@@ -0,0 +1,54 @@
import React, { useContext, useEffect, useState } from "react";
import { View, Text, Image, StyleSheet } from "react-native";
import { AuthContext } from "../context/AuthContext";
import { singlePostApi } from "../service/post";
import { globalStyles } from "../styles/global";
import { windowWidth } from "../utils/Dimensions";

const PostDetailsScreen = ({ navigation, route }) => {
const { userInfo } = useContext(AuthContext);
const [post, setPost] = useState({});

const fetchPost = async () => {
if (userInfo.jwt && route.params.id) {
singlePostApi(userInfo.jwt, route.params?.id)
.then((res) => setPost(res.data))
.catch((e) => console.log(e));
}
};

useEffect(() => {
fetchPost();
}, []);

return (
<View style={{ flex: 1 }}>
<Image
style={styles.image}
source={{
uri: `http://localhost:1337${post?.attributes?.profileImage.data.attributes.url}`,
}}
/>
<Text style={[globalStyles.boldText, styles.title]}>
{post?.attributes?.title}
</Text>
<Text style={[globalStyles.regularText ,styles.description]}>{post?.attributes?.description}</Text>
</View>
);
};

const styles = StyleSheet.create({
image: {
width: windowWidth,
height: 300,
},
title: {
marginTop: 20,
textAlign: "center",
},
description: {
marginHorizontal: 20
}
});

export default PostDetailsScreen;

+ 4
- 1
service/apiEndpoints.js Zobrazit soubor

@@ -1,3 +1,6 @@
export default {
login: 'api/auth/local'
login: 'api/auth/local',
register: 'api/auth/local/register',
googleAuth: 'api/auth/google/callback?access_token=',
fetchPosts: 'api/posts?populate=*',
}

+ 30
- 0
service/post.js Zobrazit soubor

@@ -0,0 +1,30 @@
import { API_ENDPOINT } from "./endpointDef";
import apiEndpoints from "./apiEndpoints";

export const postsApi = async (jwt) => {
const result = await fetch(`${API_ENDPOINT}${apiEndpoints.fetchPosts}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
'Authorization': "Bearer " + jwt,
},
});

const res = await result.json();

return res;
};

export const singlePostApi = async (jwt, postId) => {
const result = await fetch(`${API_ENDPOINT}api/posts/${postId}?populate=*`, {
method: "GET",
headers: {
"Content-Type": "application/json",
'Authorization': "Bearer " + jwt,
},
});
const res = await result.json();
return res;
};

+ 2
- 2
service/user.js Zobrazit soubor

@@ -16,7 +16,7 @@ export const loginApi = async (payload) => {
};

export const registerApi = async (payload) => {
const result = await fetch(API_ENDPOINT + 'api/auth/local/register', {
const result = await fetch(`${API_ENDPOINT}${apiEndpoints.register}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
@@ -30,7 +30,7 @@ export const registerApi = async (payload) => {
};

export const googleApi = async (accessToken) => {
const result = await fetch('http://localhost:1337/' + `api/auth/google/callback?access_token=${accessToken}`);
const result = await fetch(`${API_ENDPOINT}${apiEndpoints.googleAuth}${accessToken}`);

const res = await result.json();


+ 6
- 0
styles/global.js Zobrazit soubor

@@ -41,4 +41,10 @@ export const globalStyles = StyleSheet.create({
flex: 1,
justifyContent: "center",
},
button: {
backgroundColor: primary,
padding: 10,
width: 100,
borderRadius: 10
}
});

+ 0
- 0
utils/api.js Zobrazit soubor


+ 5
- 0
yarn.lock Zobrazit soubor

@@ -4770,6 +4770,11 @@ lodash.debounce@^4.0.8:
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==

lodash.filter@^4.6.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/lodash.filter/-/lodash.filter-4.6.0.tgz#668b1d4981603ae1cc5a6fa760143e480b4c4ace"
integrity sha512-pXYUy7PR8BCLwX5mgJ/aNtyOvuJTdZAo9EQFUvMIYugqmJxnrYaANvTbgndOzHSCSR0wnlBBfRXJL5SbWxo3FQ==

lodash.isequal@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"

Načítá se…
Zrušit
Uložit