Explorar el Código

Themes, Languages, Path Alias

master
Lazar Kostic hace 2 años
padre
commit
24e46a3f9a

+ 21
- 6
App.js Ver fichero

@@ -3,19 +3,32 @@ import React, { useEffect } from "react";
import { NavigationContainer } from "@react-navigation/native";
import { Provider, useDispatch } from "react-redux";
import store from "./store";
import { ThemeProvider } from "@Styles";
import '@i18n'

import { useFonts } from "expo-font";
import RootNavigation from "./navigation/RootNavigation";
import { getData } from "./service/asyncStorage";
import { JWT_REFRESH_TOKEN, JWT_TOKEN } from "./constants/localStorage";
import { fetchUserSuccess } from "./store/actions/login/loginActions";
import { getData, getObjectData } from "./service/asyncStorage";
import {
JWT_REFRESH_TOKEN,
JWT_TOKEN,
LANGUAGE,
} from "@Constants/localStorage";
import { fetchUserSuccess } from "@Store/actions/login/loginActions";
import { addHeaderToken } from "./request";
import { useTranslation } from "react-i18next";

function App() {
const { i18n } = useTranslation();
const dispatch = useDispatch();
const getToken = async () => {
const initialSetup = async () => {
const token = await getData(JWT_TOKEN);
const refreshToken = await getData(JWT_REFRESH_TOKEN);
const language = await getObjectData(LANGUAGE);

if (language !== null) {
await i18n.changeLanguage(language.code);
}

if (token) {
addHeaderToken(token);
@@ -26,7 +39,7 @@ function App() {
};

useEffect(() => {
getToken();
initialSetup();
}, []);

return (
@@ -48,7 +61,9 @@ const AppWrapper = () => {

return (
<Provider store={store}>
<App />
<ThemeProvider>
<App />
</ThemeProvider>
</Provider>
);
};

+ 21
- 1
babel.config.js Ver fichero

@@ -3,7 +3,27 @@ module.exports = function(api) {
return {
presets: ['babel-preset-expo'],
plugins: [
'react-native-reanimated/plugin'
'react-native-reanimated/plugin',
[
"module-resolver",
{
alias: {
"@Navigation": "./navigation",
"@Components": "./components",
"@Screens": "./screens",
"@Assets": "./assets",
"@Store": './store',
"@Styles": './styles',
"@Utils": "./utils",
"@Schemas": "./schemas",
"@InitialValues": "./initialValues",
"@Constants": "./constants",
"@Service": "./service",
"@i18n": "./i18n",
"@Request": "./request"
}
}
]
]
};
};

+ 32
- 10
components/CustomDrawer/CustomDrawer.jsx Ver fichero

@@ -17,17 +17,21 @@ import { Ionicons } from "@expo/vector-icons";
import { useDispatch } from "react-redux";
import { logoutUser } from "../../store/actions/login/loginActions";
import useAuthHook from "../../hooks/useAuthHook";
import { useTheme } from "@Styles";
import { useTranslation } from "react-i18next";

const CustomDrawer = (props) => {
const { colors } = useTheme();
const { t } = useTranslation();

const {logoutAuthProvider} = useAuthHook()
const { logoutAuthProvider } = useAuthHook();

const dispatch = useDispatch();

const handleLogout = async () => {
logoutAuthProvider()
logoutAuthProvider();
dispatch(logoutUser());
}
};

return (
<View style={styles.container}>
@@ -42,24 +46,42 @@ const CustomDrawer = (props) => {
/>
<Text style={styles.userName}>Diligent Software</Text>
</ImageBackground>
<View style={styles.drawerItems}>
<View
style={[styles.drawerItems, { backgroundColor: colors.background }]}
>
<DrawerItemList {...props} />
</View>
</DrawerContentScrollView>
<View style={styles.footer}>
<View style={[styles.footer, { backgroundColor: colors.background }]}>
<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>
<Ionicons
name="mail-outline"
size={24}
color={colors.iconsPrimary}
/>
<Text
style={[styles.footerButtonText, { color: colors.textPrimary }]}
>
{t("sidebar.contact")}
</Text>
</View>
</TouchableOpacity>
<TouchableOpacity onPress={handleLogout} style={styles.footerButton}>
<View style={styles.buttonContent}>
<MaterialIcons name="logout" size={22} color="#333" />
<Text style={styles.footerButtonText}>Sign Out</Text>
<MaterialIcons
name="logout"
size={22}
color={colors.iconsPrimary}
/>
<Text
style={[styles.footerButtonText, { color: colors.textPrimary }]}
>
{t("sidebar.signout")}
</Text>
</View>
</TouchableOpacity>
</View>
@@ -72,6 +94,7 @@ const styles = StyleSheet.create({
flex: 1,
},
drawer: {
flex: 1,
backgroundColor: "#8200d6",
},
backgroundImage: {
@@ -91,7 +114,6 @@ const styles = StyleSheet.create({
},
drawerItems: {
flex: 1,
backgroundColor: "#fff",
paddingTop: 10,
},
footer: {

+ 6
- 4
components/InputField.jsx Ver fichero

@@ -7,6 +7,7 @@ import {
StyleSheet,
} from "react-native";
import { globalStyles } from "../styles/global";
import { useTheme } from "@Styles";

const InputField = ({
label,
@@ -18,8 +19,9 @@ const InputField = ({
onChangeText,
text,
name,
handleBlur
handleBlur,
}) => {
const { colors } = useTheme();
return (
<View style={styles.textField}>
{icon}
@@ -29,7 +31,7 @@ const InputField = ({
placeholder={label}
placeholderTextColor="#C6C6C6"
keyboardType={keyboardType}
style={styles.textInput}
style={[styles.textInput, { color: colors.textPrimary }]}
secureTextEntry={true}
value={text}
onChangeText={onChangeText}
@@ -38,12 +40,12 @@ const InputField = ({
) : (
<TextInput
name={name}
autoCapitalize='none'
autoCapitalize="none"
autoCorrect={false}
placeholder={label}
placeholderTextColor="#C6C6C6"
keyboardType={keyboardType}
style={styles.textInput}
style={[styles.textInput, { color: colors.textPrimary }]}
value={text}
onChangeText={onChangeText}
onBlur={handleBlur}

+ 15
- 0
components/Layout/Layout.jsx Ver fichero

@@ -0,0 +1,15 @@
import React from "react";
import { View } from "react-native";
import { useTheme } from "@Styles";

const Layout = ({ children }) => {
const { colors } = useTheme();

return (
<View style={{ flex: 1, backgroundColor: colors.background }}>
{children}
</View>
);
};

export default Layout

+ 9
- 5
components/ListItem/ListItem.jsx Ver fichero

@@ -1,9 +1,13 @@
import React from "react";
import { View, Text, Image, TouchableOpacity, StyleSheet } from "react-native";
import { globalStyles } from "../../styles/global";
import { windowWidth } from "../../utils/Dimensions";
import { globalStyles } from "@Styles/global";
import { windowWidth } from "@Utils/Dimensions";
import { useTheme } from "@Styles";
import { useTranslation } from "react-i18next";

const ListItem = ({ photo, title, onPress, publishedAt }) => {
const { colors } = useTheme();
const { t } = useTranslation();
const formatDate = (date) => {
const tempDate = new Date(date);
const fDate =
@@ -26,7 +30,7 @@ const ListItem = ({ photo, title, onPress, publishedAt }) => {
<View style={{ width: windowWidth - 220 }}>
<Text
style={{
color: "#333",
color: colors.textPrimary,
fontFamily: "poppins-regular",
fontSize: 14,
textTransform: "uppercase",
@@ -36,7 +40,7 @@ const ListItem = ({ photo, title, onPress, publishedAt }) => {
</Text>
<Text
style={{
color: "#333",
color: colors.textPrimary,
fontFamily: "poppins-regular",
fontSize: 14,
textTransform: "uppercase",
@@ -47,7 +51,7 @@ const ListItem = ({ photo, title, onPress, publishedAt }) => {
</View>
</View>
<TouchableOpacity style={globalStyles.button} onPress={onPress}>
<Text style={styles.buttonText}>Read More</Text>
<Text style={styles.buttonText}>{t('common.readMore')}</Text>
</TouchableOpacity>
</View>
);

+ 5
- 3
constants/localStorage.js Ver fichero

@@ -1,3 +1,5 @@
export const JWT_TOKEN = 'JwtToken';
export const JWT_REFRESH_TOKEN = 'JwtRefreshToken';
export const ACCESS_TOKEN = "AccessToken";
export const JWT_TOKEN = "JwtToken";
export const JWT_REFRESH_TOKEN = "JwtRefreshToken";
export const ACCESS_TOKEN = "AccessToken";
export const THEME = "Theme";
export const LANGUAGE = "Language";

+ 27
- 0
i18n/index.js Ver fichero

@@ -0,0 +1,27 @@
import i18n from "i18next";
import { initReactI18next } from "react-i18next";

import srTranslations from "./resources/sr";
import enTranslation from "./resources/en";

i18n.use(initReactI18next).init({
compatibilityJSON: "v3",
lng: "sr",
fallbackLng: "sr",
resources: {
sr: {
translation: srTranslations,
},
en: {
translation: enTranslation,
},
},
interpolation: {
escapeValue: false,
},
react: {
useSuspense: true,
},
});

export default i18n;

+ 46
- 0
i18n/resources/en.js Ver fichero

@@ -0,0 +1,46 @@
export default {
login: {
signIn: "Sign In",
email: "Email",
password: "Password",
login: "Login",
orLoginWith: "Or login with ...",
needAccount: "Need Account?",
signUp: "Sign up",
},
register: {
register: "Sign Up",
orSignUpWithMail: "Or, sign up with email ...",
username: "Username",
email: "Email",
password: "Password",
forgot: "Forgot?",
confirmPassword: "Confirm Password",
signUp: "Register",
alreadyRegistered: "Already Registered?",
signIn: "Sign In",
successRegisterAccount:
"Successfully registered account, now you can log in",
},
common: {
letsBegin: "Let's begin",
theme: "Theme",
language: "Language",
light: "Light",
dark: "Dark",
serbian: "Serbian",
english: "English",
readMore: "Read More",
hello: "Hello",
search: "Search...",
noResults: "No Results",
success: "Success",
},
sidebar: {
home: "Home",
profile: "Profile",
settings: "Settings",
contact: "Contact Us",
signout: "Sign Out",
},
};

+ 46
- 0
i18n/resources/sr.js Ver fichero

@@ -0,0 +1,46 @@
export default {
login: {
signIn: "Prijava",
email: "E-mail",
password: "Lozinka",
login: "Ulogujte se",
orLoginWith: "Ili se ulogujte preko ...",
needAccount: "Nemate profil?",
signUp: "Registrujte se",
},
register: {
register: "Registracija",
orSignUpWithMail: "Ili, registracija sa e-mailom ...",
username: "Korisničko ime",
email: "E-mail",
password: "Lozinka",
forgot: "Zaboravljena?",
confirmPassword: "Potvrda Lozinke",
signUp: "Registrujte se",
alreadyRegistered: "Već ste registrovani?",
signIn: "Ulogujte se",
successRegisterAccount:
"Uspešno ste registrovali nalog, sada možete da se ulogujete",
},
common: {
letsBegin: "Počnimo",
theme: "Tema",
language: "Jezik",
light: "Svetla",
dark: "Tamna",
serbian: "Srpski",
english: "Engleski",
readMore: "Opširnije",
hello: "Zdravo",
search: "Pretražite...",
noResults: "Nema rezultata",
success: "Uspešno",
},
sidebar: {
home: "Početna",
profile: "Profil",
settings: "Podešavanja",
contact: "Kontakt",
signout: "Izloguj se",
},
};

+ 59
- 39
navigation/AppStack.js Ver fichero

@@ -1,45 +1,65 @@
import React from 'react';
import { createDrawerNavigator } from '@react-navigation/drawer';
import React from "react";
import { createDrawerNavigator } from "@react-navigation/drawer";

import CustomDrawer from '../components/CustomDrawer/CustomDrawer';
import TabNavigator from './TabNavigator';
import Ionicons from '@expo/vector-icons/Ionicons';
import SettingsScreen from '../screens/SettingsScreen';
import ProfileScreen from '../screens/ProfileScreen';
import CustomDrawer from "@Components/CustomDrawer/CustomDrawer";
import TabNavigator from "./TabNavigator";
import Ionicons from "@expo/vector-icons/Ionicons";
import SettingsScreen from "@Screens/SettingsScreen";
import ProfileScreen from "@Screens/ProfileScreen";
import { useTheme } from "@Styles";
import { useTranslation } from "react-i18next";

const Drawer = createDrawerNavigator();

const AppStack = () => {
return (
<Drawer.Navigator drawerContent={props => <CustomDrawer {...props} />}
screenOptions={{
headerShown: false,
drawerActiveBackgroundColor: '#aa18ea',
drawerActiveTintColor: '#fff',
drawerInactiveTintColor: '#333',
drawerLabelStyle: {
marginLeft: 5,
fontSize: 16
}
}}
>
<Drawer.Screen name='Home' component={TabNavigator} options={{
drawerIcon: ({color}) => (
<Ionicons name='home-outline' size={22} color={color} />
)
}} />
<Drawer.Screen name='Profile' component={ProfileScreen} options={{
drawerIcon: ({color}) => (
<Ionicons name='person-outline' size={22} color={color} />
)
}} />
<Drawer.Screen name='Settings' component={SettingsScreen} options={{
drawerIcon: ({color}) => (
<Ionicons name='settings-outline' size={22} color={color} />
)
}} />
</Drawer.Navigator>
)
}
const { colors } = useTheme();
const { t } = useTranslation();
return (
<Drawer.Navigator
drawerContent={(props) => <CustomDrawer {...props} />}
screenOptions={{
headerShown: false,
drawerActiveBackgroundColor: "#aa18ea",
drawerActiveTintColor: "#fff",
drawerInactiveTintColor: colors.textPrimary,
drawerLabelStyle: {
marginLeft: 5,
fontSize: 16,
},
}}
>
<Drawer.Screen
name="Home"
component={TabNavigator}
options={{
title:t("sidebar.home"),
drawerIcon: ({ color }) => (
<Ionicons name="home-outline" size={22} color={color} />
),
}}
/>
<Drawer.Screen
name='Profile'
component={ProfileScreen}
options={{
title:t("sidebar.profile"),
drawerIcon: ({ color }) => (
<Ionicons name="person-outline" size={22} color={color} />
),
}}
/>
<Drawer.Screen
name='Settings'
component={SettingsScreen}
options={{
title: t("sidebar.settings"),
drawerIcon: ({ color }) => (
<Ionicons name="settings-outline" size={22} color={color} />
),
}}
/>
</Drawer.Navigator>
);
};

export default AppStack;
export default AppStack;

+ 22
- 2
navigation/RootNavigation.js Ver fichero

@@ -1,13 +1,33 @@
import React from "react";
import AppStack from "./AppStack";
import AuthStack from "./AuthStack";
import { SafeAreaView } from "react-native";
import { useSelector } from "react-redux";
import { selectTokens } from "../store/selectors/loginSelectors";
import { selectTokens } from "@Store/selectors/loginSelectors";
import { StatusBar } from "expo-status-bar";
import { useTheme } from "@Styles";

const RootNavigation = () => {
const { isDark, colors } = useTheme();
const tokens = useSelector(selectTokens);

return !tokens.JwtToken ? <AuthStack /> : <AppStack />;
return !tokens.JwtToken ? (
<SafeAreaView style={{ flex: 1, backgroundColor: colors.background }}>
<StatusBar
backgroundColor={colors.background}
style={isDark ? "light" : "dark"}
/>
<AuthStack />
</SafeAreaView>
) : (
<SafeAreaView style={{ flex: 1, backgroundColor: colors.background }}>
<StatusBar
backgroundColor={colors.background}
style={isDark ? "light" : "dark"}
/>
<AppStack />
</SafeAreaView>
);
};

export default RootNavigation;

+ 9
- 5
navigation/TabNavigator.js Ver fichero

@@ -3,15 +3,17 @@ import { createNativeStackNavigator } from "@react-navigation/native-stack";
import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
import { getFocusedRouteNameFromRoute } from "@react-navigation/native";

import HomeScreen from "../screens/HomeScreen";
import HomeScreen from "@Screens/HomeScreen";
import Ionicons from "@expo/vector-icons/Ionicons";
import FavoriteScreen from "../screens/FavoriteScreen";
import PostDetailsScreen from "../screens/PostDetailsScreen";
import FavoriteScreen from "@Screens/FavoriteScreen";
import PostDetailsScreen from "@Screens/PostDetailsScreen";
import { useTheme } from "@Styles";

const Tab = createBottomTabNavigator();
const Stack = createNativeStackNavigator();

const HomeStack = () => {
const { colors } = useTheme();
return (
<Stack.Navigator>
<Stack.Screen
@@ -22,8 +24,10 @@ const HomeStack = () => {
<Stack.Screen
name="PostDetails"
component={PostDetailsScreen}
options={({route}) => ({
title: route.params.title
options={({ route }) => ({
headerStyle: { backgroundColor: colors.background },
headerTintColor: colors.textPrimary,
title: route.params.title,
})}
/>
</Stack.Navigator>

+ 839
- 936
package-lock.json
La diferencia del archivo ha sido suprimido porque es demasiado grande
Ver fichero


+ 4
- 2
package.json Ver fichero

@@ -7,7 +7,7 @@
"eject": "expo eject"
},
"dependencies": {
"@react-native-async-storage/async-storage": "^1.17.11",
"@react-native-async-storage/async-storage": "~1.17.3",
"@react-native-community/datetimepicker": "6.5.2",
"@react-native-masked-view/masked-view": "0.2.8",
"@react-navigation/bottom-tabs": "^6.4.3",
@@ -22,12 +22,14 @@
"expo-status-bar": "~1.4.2",
"expo-web-browser": "~12.0.0",
"formik": "^2.2.9",
"i18next": "^22.4.13",
"jwt-decode": "^3.1.2",
"lodash.filter": "^4.6.0",
"lodash.isempty": "^4.4.0",
"react": "18.1.0",
"react-dom": "18.1.0",
"react-native": "0.70.5",
"react-i18next": "^12.2.0",
"react-native": "0.70.8",
"react-native-gesture-handler": "~2.8.0",
"react-native-indicators": "^0.17.0",
"react-native-loading-spinner-overlay": "^3.0.1",

+ 20
- 15
screens/FavoriteScreen.jsx Ver fichero

@@ -1,20 +1,25 @@
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import React from "react";
import { View, Text, StyleSheet } from "react-native";
import Layout from "@Components/Layout/Layout";
import { useTheme } from "@Styles";

const FavoriteScreen = () => {
return (
<View style={styles.container}>
<Text>Favorite</Text>
</View>
)
}
const { colors } = useTheme();
return (
<Layout>
<View style={styles.container}>
<Text style={{color: colors.textPrimary}}>Favorite</Text>
</View>
</Layout>
);
};

const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center'
}
})
container: {
flex: 1,
justifyContent: "center",
alignItems: "center",
},
});

export default FavoriteScreen;
export default FavoriteScreen;

+ 20
- 9
screens/HomeScreen.jsx Ver fichero

@@ -2,7 +2,6 @@ import React, { useEffect, useState } from "react";
import {
View,
Text,
SafeAreaView,
ScrollView,
ImageBackground,
TextInput,
@@ -10,12 +9,17 @@ import {
StyleSheet,
} from "react-native";
import Feather from "@expo/vector-icons/Feather";
import ListItem from "../components/ListItem/ListItem";
import ListItem from "@Components/ListItem/ListItem";
import filter from "lodash.filter";
import { globalStyles } from "../styles/global";
import { getRequest } from "../request";
import { globalStyles } from "@Styles/global";
import { getRequest } from "@Request/index";
import Layout from "@Components/Layout/Layout";
import { useTheme } from "@Styles";
import { useTranslation } from "react-i18next";

const HomeScreen = ({ navigation }) => {
const { colors } = useTheme();
const { t } = useTranslation();
const [posts, setPosts] = useState([]);

const [query, setQuery] = useState("");
@@ -56,10 +60,12 @@ const HomeScreen = ({ navigation }) => {
}
}, [posts]);
return (
<SafeAreaView style={{ flex: 1, backgroundColor: "#fff" }}>
<Layout>
<ScrollView style={{ padding: 20 }}>
<View style={styles.wrapper}>
<Text style={{ fontSize: 18 }}>Hello Diligent</Text>
<Text style={{ fontSize: 18, color: colors.textPrimary }}>
{t("common.hello")}, Diligent
</Text>
<TouchableOpacity onPress={() => navigation.openDrawer()}>
<ImageBackground
source={require("../assets/images/diligent-purple.png")}
@@ -79,15 +85,20 @@ const HomeScreen = ({ navigation }) => {
onChangeText={(text) => searchFilter(text)}
autoCapitalize="none"
autoCorrect={false}
placeholder="Search"
placeholder={t('common.search')}
value={query}
placeholderTextColor="#C6C6C6"
style={{ flex: 1, color: colors.textPrimary }}
/>
</View>
<Text>
{filteredData.length === 0 && (
<View>
<Text style={globalStyles.boldText}>No Results Found</Text>
<Text
style={[globalStyles.boldText, { color: colors.textPrimary }]}
>
{t('common.noResults')}
</Text>
</View>
)}
</Text>
@@ -121,7 +132,7 @@ const HomeScreen = ({ navigation }) => {
/>
))}
</ScrollView>
</SafeAreaView>
</Layout>
);
};


+ 49
- 37
screens/LoginScreen.jsx Ver fichero

@@ -1,39 +1,35 @@
import React, { useEffect } from "react";
import {
SafeAreaView,
View,
Text,
TouchableOpacity,
StyleSheet,
} from "react-native";
import { View, Text, TouchableOpacity, StyleSheet } from "react-native";
import MaterialIcons from "@expo/vector-icons/MaterialIcons";
import Ionicons from "@expo/vector-icons/Ionicons";

import LoginSVG from "../assets/images/login.svg";
import GoogleSVG from "../assets/images/google.svg";
import FacebookSVG from "../assets/images/facebook.svg";
import TwitterSVG from "../assets/images/twitter.svg";
import LoginSVG from "@Assets/images/login.svg";
import GoogleSVG from "@Assets/images/google.svg";
import FacebookSVG from "@Assets/images/facebook.svg";
import TwitterSVG from "@Assets/images/twitter.svg";

import CustomButton from "../components/Buttons/CustomButton";
import InputField from "../components/InputField";
import { globalStyles } from "../styles/global";
import Loader from "../components/Loader";
import CustomButton from "@Components/Buttons/CustomButton";
import InputField from "@Components/InputField";
import { globalStyles } from "@Styles/global";
import Loader from "@Components/Loader";
import { Formik } from "formik";
import { loginSchema } from "../schemas/loginSchema";
import { loginSchema } from "@Schemas/loginSchema";
import { useDispatch, useSelector } from "react-redux";
import { selectLoginError } from "../store/selectors/loginSelectors";
import {
clearLoginErrors,
fetchUser,
} from "../store/actions/login/loginActions";
import { selectIsLoadingByActionType } from "../store/selectors/loadingSelectors";
import { LOGIN_USER_SCOPE } from "../store/actions/login/loginActionConstants";
import { fetchAuthProvider } from "../store/actions/authProvider/authProviderActions";
import { selectLoginError } from "@Store/selectors/loginSelectors";
import { clearLoginErrors, fetchUser } from "@Store/actions/login/loginActions";
import { selectIsLoadingByActionType } from "@Store/selectors/loadingSelectors";
import { LOGIN_USER_SCOPE } from "@Store/actions/login/loginActionConstants";
import { fetchAuthProvider } from "@Store/actions/authProvider/authProviderActions";
import useAuthHook from "../hooks/useAuthHook";
import { storeData } from "../service/asyncStorage";
import { ACCESS_TOKEN } from "../constants/localStorage";
import { storeData } from "@Service/asyncStorage";
import { ACCESS_TOKEN } from "@Constants/localStorage";
import Layout from "@Components/Layout/Layout";
import { useTheme } from "@Styles";
import { useTranslation } from "react-i18next";

const LoginScreen = ({ navigation }) => {
const { colors } = useTheme();
const { t } = useTranslation();
const { response, promptAsync } = useAuthHook();
const dispatch = useDispatch();
const error = useSelector(selectLoginError);
@@ -42,7 +38,7 @@ const LoginScreen = ({ navigation }) => {

const storeToken = async (token) => {
await storeData(ACCESS_TOKEN, token);
}
};

useEffect(() => {
if (response?.type === "success") {
@@ -70,7 +66,7 @@ const LoginScreen = ({ navigation }) => {
};

return (
<SafeAreaView style={globalStyles.safeArea}>
<Layout>
<Loader visible={isLoading} />
<View style={{ paddingHorizontal: 25 }}>
<View style={{ alignItems: "center" }}>
@@ -95,13 +91,18 @@ const LoginScreen = ({ navigation }) => {
errors,
}) => (
<>
<Text style={globalStyles.boldText}>Sign In</Text>
<Text
style={[globalStyles.boldText, { color: colors.textPrimary }]}
>
{t('login.signIn')}
</Text>
<InputField
name="email"
label={"Email"}
label={t('login.email')}
keyboardType="email-address"
onChangeText={handleChange("email")}
text={values.email}
style={{ color: colors.textPrimary }}
handleBlur={handleBlur("email")}
icon={
<MaterialIcons
@@ -117,8 +118,8 @@ const LoginScreen = ({ navigation }) => {
)}
<InputField
name="password"
label={"Password"}
filedButtonLabel={"Forgot?"}
label={t('login.password')}
filedButtonLabel={t('register.forgot')}
fieldButtonFunction={() => {}}
inputType="password"
handleBlur={handleBlur("password")}
@@ -137,11 +138,18 @@ const LoginScreen = ({ navigation }) => {
<Text style={styles.errorMessage}>{errors.password}</Text>
)}
{error && <Text style={styles.errorMessage}>{error}</Text>}
<CustomButton label={"Login"} onPress={handleSubmit} />
<CustomButton label={t('login.login')} onPress={handleSubmit} />
</>
)}
</Formik>
<Text style={globalStyles.regularCenteredText}>Or login with ...</Text>
<Text
style={[
globalStyles.regularCenteredText,
{ color: colors.textPrimary },
]}
>
{t('login.orLoginWith')}
</Text>
<View style={styles.providersContainer}>
<TouchableOpacity
onPress={handleGoogleAuth}
@@ -157,13 +165,17 @@ const LoginScreen = ({ navigation }) => {
</TouchableOpacity>
</View>
<View style={styles.registerContainer}>
<Text style={globalStyles.regularText}>Need account? </Text>
<Text
style={[globalStyles.regularText, { color: colors.textPrimary }]}
>
{t('login.needAccount')}{" "}
</Text>
<TouchableOpacity onPress={() => navigation.navigate("Register")}>
<Text style={styles.registerButtonText}>Sign Up</Text>
<Text style={styles.registerButtonText}>{t('login.signUp')}</Text>
</TouchableOpacity>
</View>
</View>
</SafeAreaView>
</Layout>
);
};


+ 69
- 54
screens/OnboardingScreen.jsx Ver fichero

@@ -1,58 +1,73 @@
import React from 'react';
import { SafeAreaView, View, Text, TouchableOpacity, StyleSheet, Image } from 'react-native';
import MaterialIcons from '@expo/vector-icons/MaterialIcons';
import React from "react";
import { View, Text, TouchableOpacity, StyleSheet, Image } from "react-native";
import MaterialIcons from "@expo/vector-icons/MaterialIcons";
import Layout from "@Components/Layout/Layout";
import { useTranslation } from "react-i18next";
import { useTheme } from "@Styles";

const OnboardingScreen = ({navigation}) => {
return (
<SafeAreaView style={styles.safeArea}>
<View style={{marginTop: 20}}>
<Text style={styles.headText}>DILIGENT</Text>
</View>
<View style={styles.logo}>
<Image style={{width: 300, height: 300}} source={require('../assets/images/diligent-logo.png')} />
</View>
<TouchableOpacity style={styles.button} onPress={() => navigation.navigate('Login')}>
<Text style={styles.buttonText}>Let's begin</Text>
<MaterialIcons name='arrow-forward-ios' size={22} color='#fff' />
</TouchableOpacity>
</SafeAreaView>
)
}
const OnboardingScreen = ({ navigation }) => {
const { t } = useTranslation();
const { isDark } = useTheme();
return (
<Layout>
<View style={styles.container}>
<View style={{ marginTop: 20 }}>
<Text
style={[styles.headText, { color: isDark ? "#fff" : "#20315f" }]}
>
DILIGENT
</Text>
</View>
<View style={styles.logo}>
<Image
style={{ width: 300, height: 300 }}
source={require("../assets/images/diligent-logo.png")}
/>
</View>
<TouchableOpacity
style={styles.button}
onPress={() => navigation.navigate("Login")}
>
<Text style={styles.buttonText}>{t("common.letsBegin")}</Text>
<MaterialIcons name="arrow-forward-ios" size={22} color="#fff" />
</TouchableOpacity>
</View>
</Layout>
);
};

const styles = StyleSheet.create({
safeArea: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#fff'
},
headText: {
fontWeight: 'bold',
fontSize: 70,
color: '#20315f',
fontFamily: 'poppins-semibold'
},
logo: {
flex: 1,
justifyContent: 'center',
alignItems: 'center'
},
button: {
backgroundColor: '#AD40AF',
padding: 20,
width: '90%',
borderRadius: 10,
marginBottom: 50,
flexDirection: 'row',
justifyContent: 'space-between'
},
buttonText: {
color: 'white',
fontSize: 18,
textAlign: 'center',
fontWeight: 'bold',
fontFamily: 'poppins-regular'
}
})
container: {
flex: 1,
justifyContent: "center",
alignItems: "center",
},
headText: {
fontWeight: "bold",
fontSize: 70,
fontFamily: "poppins-semibold",
},
logo: {
flex: 1,
justifyContent: "center",
alignItems: "center",
},
button: {
backgroundColor: "#AD40AF",
padding: 20,
width: "90%",
borderRadius: 10,
marginBottom: 50,
flexDirection: "row",
justifyContent: "space-between",
},
buttonText: {
color: "white",
fontSize: 18,
textAlign: "center",
fontWeight: "bold",
fontFamily: "poppins-regular",
},
});

export default OnboardingScreen;
export default OnboardingScreen;

+ 30
- 11
screens/PostDetailsScreen.jsx Ver fichero

@@ -1,14 +1,19 @@
import React, { useContext, useEffect, useState } from "react";
import { View, Text, Image, StyleSheet } from "react-native";
import React, { useEffect, useState } from "react";
import { Text, Image, StyleSheet } from "react-native";
import { getRequest } from "../request";
import { globalStyles } from "../styles/global";
import { windowWidth } from "../utils/Dimensions";
import { globalStyles } from "@Styles/global";
import { windowWidth } from "@Utils/Dimensions";
import Layout from "@Components/Layout/Layout";
import { useTheme } from "@Styles";

const PostDetailsScreen = ({ navigation, route }) => {
const [post, setPost] = useState({});
const { colors } = useTheme();

const fetchPost = async () => {
const { data } = await getRequest(`api/posts/${route.params.id}?populate=*`);
const { data } = await getRequest(
`api/posts/${route.params.id}?populate=*`
);
if (data) {
setPost(data.data);
}
@@ -19,18 +24,32 @@ const PostDetailsScreen = ({ navigation, route }) => {
}, []);

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

@@ -44,8 +63,8 @@ const styles = StyleSheet.create({
textAlign: "center",
},
description: {
marginHorizontal: 20
}
marginHorizontal: 20,
},
});

export default PostDetailsScreen;

+ 10
- 5
screens/ProfileScreen.jsx Ver fichero

@@ -1,11 +1,16 @@
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import React from "react";
import { View, Text, StyleSheet } from "react-native";
import Layout from "@Components/Layout/Layout";
import { useTheme } from "@Styles";

const ProfileScreen = () => {
const { colors } = useTheme();
return (
<View style={styles.container}>
<Text>Profile</Text>
</View>
<Layout>
<View style={styles.container}>
<Text style={{color: colors.textPrimary}}>Profile</Text>
</View>
</Layout>
)
}


+ 54
- 41
screens/RegisterScreen.jsx Ver fichero

@@ -1,6 +1,5 @@
import React, { useEffect } from "react";
import {
SafeAreaView,
ScrollView,
View,
Text,
@@ -9,36 +8,40 @@ import {
Alert,
} from "react-native";

// import DateTimePicker from "@react-native-community/datetimepicker";
import InputField from "../components/InputField";
import InputField from "@Components/InputField";

import MaterialIcons from "@expo/vector-icons/MaterialIcons";
import Ionicons from "@expo/vector-icons/Ionicons";

import RegistrationSVG from "../assets/images/registration.svg";
import GoogleSVG from "../assets/images/google.svg";
import FacebookSVG from "../assets/images/facebook.svg";
import TwitterSVG from "../assets/images/twitter.svg";
import RegistrationSVG from "@Assets/images/registration.svg";
import GoogleSVG from "@Assets/images/google.svg";
import FacebookSVG from "@Assets/images/facebook.svg";
import TwitterSVG from "@Assets/images/twitter.svg";

import CustomButton from "../components/Buttons/CustomButton";
import { globalStyles } from "../styles/global";
import Loader from "../components/Loader";
import CustomButton from "@Components/Buttons/CustomButton";
import { globalStyles } from "@Styles/global";
import Loader from "@Components/Loader";
import { Formik } from "formik";
import { registerSchema } from "../schemas/registerSchema";
import { registerSchema } from "@Schemas/registerSchema";
import { useDispatch, useSelector } from "react-redux";
import { selectRegisterError } from "../store/selectors/registerSelectors";
import { selectIsLoadingByActionType } from "../store/selectors/loadingSelectors";
import { REGISTER_USER_SCOPE } from "../store/actions/register/registerActionConstants";
import { selectRegisterError } from "@Store/selectors/registerSelectors";
import { selectIsLoadingByActionType } from "@Store/selectors/loadingSelectors";
import { REGISTER_USER_SCOPE } from "@Store/actions/register/registerActionConstants";
import {
clearRegisterErrors,
registerUser,
} from "../store/actions/register/registerActions";
} from "@Store/actions/register/registerActions";
import useAuthHook from "../hooks/useAuthHook";
import { fetchAuthProvider } from "../store/actions/authProvider/authProviderActions";
import { ACCESS_TOKEN } from "../constants/localStorage";
import { storeData } from "../service/asyncStorage";
import { fetchAuthProvider } from "@Store/actions/authProvider/authProviderActions";
import { ACCESS_TOKEN } from "@Constants/localStorage";
import { storeData } from "@Service/asyncStorage";
import Layout from "@Components/Layout/Layout";
import { useTheme } from "@Styles";
import { useTranslation } from "react-i18next";

const RegisterScreen = ({ navigation }) => {
const { colors } = useTheme();
const { t } = useTranslation();
const { response, promptAsync } = useAuthHook();
const dispatch = useDispatch();
const error = useSelector(selectRegisterError);
@@ -49,19 +52,15 @@ const RegisterScreen = ({ navigation }) => {

const storeToken = async (token) => {
await storeData(ACCESS_TOKEN, token);
}
};

const handleApiResponseSuccess = () => {
Alert.alert(
"Success",
"Successfully registered account, now you can log in",
[
{
text: "OK",
onPress: () => navigation.navigate("Login"),
},
]
);
Alert.alert(t("common.success"), t("register.successRegisterAccount"), [
{
text: "OK",
onPress: () => navigation.navigate("Login"),
},
]);
};

const handleSignup = (values) => {
@@ -87,7 +86,7 @@ const RegisterScreen = ({ navigation }) => {
}, [response]);

return (
<SafeAreaView style={globalStyles.safeArea}>
<Layout>
<Loader visible={isLoading} />
<ScrollView
showsVerticalScrollIndicator={false}
@@ -96,7 +95,9 @@ const RegisterScreen = ({ navigation }) => {
<View style={{ alignItems: "center" }}>
<RegistrationSVG width={300} height={300} />
</View>
<Text style={globalStyles.boldText}>Sign Up</Text>
<Text style={[globalStyles.boldText, { color: colors.textPrimary }]}>
{t("register.register")}
</Text>
<View style={styles.providersContainer}>
<TouchableOpacity
onPress={handleGoogleAuth}
@@ -111,8 +112,13 @@ const RegisterScreen = ({ navigation }) => {
<TwitterSVG height={24} width={24} />
</TouchableOpacity>
</View>
<Text style={globalStyles.regularCenteredText}>
Or, sign up with email...
<Text
style={[
globalStyles.regularCenteredText,
{ color: colors.textPrimary },
]}
>
{t("register.orSignUpWithMail")}
</Text>
<Formik
initialValues={{
@@ -139,7 +145,7 @@ const RegisterScreen = ({ navigation }) => {
name={"username"}
text={values.username}
onChangeText={handleChange("username")}
label={"Username"}
label={t("register.username")}
handleBlur={handleBlur("username")}
icon={
<Ionicons
@@ -157,7 +163,7 @@ const RegisterScreen = ({ navigation }) => {
name={"email"}
text={values.email}
onChangeText={handleChange("email")}
label={"Email"}
label={t("register.email")}
handleBlur={handleBlur("email")}
icon={
<MaterialIcons
@@ -176,7 +182,7 @@ const RegisterScreen = ({ navigation }) => {
name={"password"}
text={values.password}
onChangeText={handleChange("password")}
label={"Password"}
label={t("register.password")}
handleBlur={handleBlur("password")}
icon={
<Ionicons
@@ -195,7 +201,7 @@ const RegisterScreen = ({ navigation }) => {
name={"confirmPassword"}
text={values.confirmPassword}
onChangeText={handleChange("confirmPassword")}
label={"Confirm Password"}
label={t("register.confirmPassword")}
handleBlur={handleBlur("confirmPassword")}
icon={
<Ionicons
@@ -213,18 +219,25 @@ const RegisterScreen = ({ navigation }) => {
</Text>
)}
{error && <Text style={styles.errorMessage}>{error}</Text>}
<CustomButton label={"Sign Up"} onPress={handleSubmit} />
<CustomButton
label={t("register.signUp")}
onPress={handleSubmit}
/>
</>
)}
</Formik>
<View style={styles.alreadyRegistered}>
<Text style={globalStyles.regularText}>Already Registered? </Text>
<Text
style={[globalStyles.regularText, { color: colors.textPrimary }]}
>
{t("register.alreadyRegistered")}{" "}
</Text>
<TouchableOpacity onPress={() => navigation.goBack()}>
<Text style={globalStyles.primaryBold}>Sign In</Text>
<Text style={globalStyles.primaryBold}>{t("register.signIn")}</Text>
</TouchableOpacity>
</View>
</ScrollView>
</SafeAreaView>
</Layout>
);
};


+ 170
- 24
screens/SettingsScreen.jsx Ver fichero

@@ -1,26 +1,172 @@
import React from 'react';
import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';

const SettingsScreen = ({navigation}) => {
return (
<View style={styles.container}>
<Text>Settings</Text>
<TouchableOpacity onPress={() => navigation.goBack()}>
<Text>Go Back</Text>
</TouchableOpacity>
<TouchableOpacity onPress={() => navigation.openDrawer()}>
<Text>Open drawer</Text>
</TouchableOpacity>
</View>
)
}

const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center'
import React, { useEffect, useState } from "react";
import { View, Text, TouchableOpacity } from "react-native";
import { useTheme } from "@Styles";
import { THEME, LANGUAGE } from "@Constants/localStorage";
import { storeObject, getObjectData } from "@Service/asyncStorage";
import Layout from "@Components/Layout/Layout";
import { useTranslation } from "react-i18next";

const SettingsScreen = ({ navigation }) => {
const { t, i18n } = useTranslation();
const { colors, setScheme, isDark } = useTheme();
const [selectedTheme, setSelectedTheme] = useState({});
const [selectedLanguage, setSelectedLanguage] = useState({});
const themes = [
{
id: 1,
name: t("common.light"),
type: "light",
},
{
id: 2,
name: t("common.dark"),
type: "dark",
},
];

const languages = [
{
id: 1,
name: t("common.serbian"),
code: "sr",
},
{
id: 2,
name: t("common.english"),
code: "en",
},
];

const handleLanguage = async (language) => {
setSelectedLanguage(language);
await i18n.changeLanguage(language.code);
await storeObject(LANGUAGE, language);
};

const handleTheme = async (theme) => {
setSelectedTheme(theme);
if (theme.type === "dark") {
setScheme("dark");
} else if (theme.type === "light") {
setScheme("light");
}
await storeObject(THEME, theme);
};

useEffect(() => {
async function handleThemeChange() {
const theme = await getObjectData(THEME);
if (theme !== null) {
setSelectedTheme(theme);
} else if (isDark) {
setSelectedTheme(themes[1]);
} else {
setSelectedTheme(themes[0]);
}
}
})

export default SettingsScreen;
handleThemeChange();
}, []);

useEffect(() => {
async function handleLanguageChange() {
const language = await getObjectData(LANGUAGE);
if (language !== null) {
setSelectedLanguage(language);
//await i18n.changeLanguage(language.code);
} else {
setSelectedLanguage(languages[0]);
//await i18n.changeLanguage(languages[0].code);
}
}

handleLanguageChange();
}, []);

return (
<Layout>
<Text style={{ color: colors.textPrimary, paddingLeft: 20 }}>
{t("common.theme")}
</Text>
<View
style={{
flexDirection: "row",
justifyContent: "space-between",
marginTop: 20,
marginHorizontal: 20,
paddingBottom: 20,
borderBottomWidth: 1,
borderBottomColor: colors.borderSecondary,
}}
>
{themes.map((theme) => (
<TouchableOpacity
key={theme.id}
style={{ flex: 1 }}
onPress={() => handleTheme(theme)}
>
<View
style={[
{
alignItems: "center",
borderWidth: 1,
paddingVertical: 8,
borderColor: colors.borderSecondary,
},
theme.id === 1 && { marginRight: 18 },
selectedTheme.id === theme.id && {
backgroundColor: "#aa18ea",
},
]}
>
<Text style={{ color: colors.textPrimary }}>{theme.name}</Text>
</View>
</TouchableOpacity>
))}
</View>
<Text
style={{ color: colors.textPrimary, paddingLeft: 20, marginTop: 20 }}
>
{t("common.language")}
</Text>
<View
style={{
flexDirection: "row",
justifyContent: "space-between",
marginTop: 20,
marginHorizontal: 20,
paddingBottom: 20,
borderBottomWidth: 1,
borderBottomColor: colors.borderSecondary,
}}
>
{languages.map((lang) => (
<TouchableOpacity
key={lang.id}
style={{ flex: 1 }}
onPress={() => handleLanguage(lang)}
>
<View
style={[
{
alignItems: "center",
borderWidth: 1,
paddingVertical: 8,
borderColor: colors.borderSecondary,
},
lang.id === 1 && { marginRight: 18 },
selectedLanguage.id === lang.id && {
backgroundColor: "#aa18ea",
},
]}
>
<Text style={{ color: colors.textPrimary }}>{lang.name}</Text>
</View>
</TouchableOpacity>
))}
</View>
</Layout>
);
};

export default SettingsScreen;

+ 16
- 0
service/asyncStorage.js Ver fichero

@@ -8,6 +8,15 @@ export const storeData = async (key, value) => {
}
};

export const storeObject = async (key, value) => {
try {
const jsonValue = JSON.stringify(value);
await AsyncStorage.setItem(key, jsonValue);
} catch (error) {
console.log("Error storing object");
}
};

export const getData = async (key) => {
try {
const value = await AsyncStorage.getItem(key);
@@ -17,6 +26,13 @@ export const getData = async (key) => {
}
};

export const getObjectData = async (key) => {
try {
const jsonValue = await AsyncStorage.getItem(key);
return jsonValue !== null ? JSON.parse(jsonValue) : null;
} catch (error) {}
};

export const removeData = async (key) => {
try {
await AsyncStorage.removeItem(key);

+ 61
- 0
styles/colors.js Ver fichero

@@ -0,0 +1,61 @@
const backgroundLight = "#F5F5F5";
const backgroundDark = "#292E2C";

const backgroundSecondaryLight = "#FFFFFF";
const backgroundSecondaryDark = "#303534";

const headerLight = "#FFFFFF";
const headerDark = "#292E2C";

const borderLight = "#E4E4E4";
const borderDark = "#303534";

const borderSecondaryLight = "#E4E4E4";
const borderSecondaryDark = "#3D4442";

const textPrimaryLight = "#0E0E0E";
const textPrimaryDark = "#F7FBFA";

const textSecondaryLight = "#909090";
const textSecondaryDark = "#84928E";

const iconsPrimaryLight = "#0E0E0E";
const iconsPrimaryDark = "#F7FBFA";

const popoverLight = "#FFFFFF";
const popoverDark = "#292E2C";

const realisedOrderLight = "#E0F4F0";
const realisedOrderDark = "#00563E";

const unrealisedOrderLight = "#FBE3E7";
const unrealisedOrderDark = "#781737";

export const lightColors = {
background: backgroundLight,
backgroundSecondary: backgroundSecondaryLight,
header: headerLight,
border: borderLight,
borderSecondary: borderSecondaryLight,
textPrimary: textPrimaryLight,
textSecondary: textSecondaryLight,
iconsPrimary: iconsPrimaryLight,
popover: popoverLight,
realisedOrder: realisedOrderLight,
unrealisedOrder: unrealisedOrderLight,
};

// Dark theme colors
export const darkColors = {
background: backgroundDark,
backgroundSecondary: backgroundSecondaryDark,
header: headerDark,
border: borderDark,
borderSecondary: borderSecondaryDark,
textPrimary: textPrimaryDark,
textSecondary: textSecondaryDark,
iconsPrimary: iconsPrimaryDark,
popover: popoverDark,
realisedOrder: realisedOrderDark,
unrealisedOrder: unrealisedOrderDark,
};

+ 55
- 0
styles/index.js Ver fichero

@@ -0,0 +1,55 @@
import React, { useState, useEffect, createContext, useContext } from "react";
import { useColorScheme } from "react-native";
import { THEME } from "@Constants/localStorage";
import { getObjectData } from "@Service/asyncStorage";
import { lightColors, darkColors } from "./colors";

export const ThemeContext = createContext({
isDark: false,
colors: lightColors,
setScheme: () => {},
});

export const ThemeProvider = (props) => {
// Getting the device color theme, this will also work with react-native-web
const deviceColorScheme = useColorScheme(); // Can be dark | light | no-preference

/*
* To enable changing the app theme dynamically in the app (run-time)
* we're gonna use useState so we can override the default device theme
*/
const [isDark, setIsDark] = useState(null);

const handleTheme = async () => {
const theme = await getObjectData(THEME);
if (theme !== null) {
setIsDark(theme.type === "dark");
} else {
setIsDark(deviceColorScheme === "dark");
}
};

// Get the theme from async storage on mount
useEffect(() => {
handleTheme();
}, []);

const defaultTheme = {
isDark,
// Changing color schemes according to theme
colors: isDark ? darkColors : lightColors,
// Overrides the isDark value will cause re-render inside the context.
setScheme: async (scheme) => {
setIsDark(scheme === "dark");
},
};

return (
<ThemeContext.Provider value={defaultTheme}>
{props.children}
</ThemeContext.Provider>
);
};

// Custom hook to get the theme object returns {isDark, colors, setScheme}
export const useTheme = () => useContext(ThemeContext);

Cargando…
Cancelar
Guardar