| @@ -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> | |||
| ); | |||
| }; | |||
| @@ -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" | |||
| } | |||
| } | |||
| ] | |||
| ] | |||
| }; | |||
| }; | |||
| @@ -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: { | |||
| @@ -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} | |||
| @@ -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 | |||
| @@ -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> | |||
| ); | |||
| @@ -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"; | |||
| @@ -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; | |||
| @@ -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", | |||
| }, | |||
| }; | |||
| @@ -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", | |||
| }, | |||
| }; | |||
| @@ -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; | |||
| @@ -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; | |||
| @@ -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> | |||
| @@ -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", | |||
| @@ -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; | |||
| @@ -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> | |||
| ); | |||
| }; | |||
| @@ -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> | |||
| ); | |||
| }; | |||
| @@ -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; | |||
| @@ -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; | |||
| @@ -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> | |||
| ) | |||
| } | |||
| @@ -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> | |||
| ); | |||
| }; | |||
| @@ -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; | |||
| @@ -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); | |||
| @@ -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, | |||
| }; | |||
| @@ -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); | |||