| import { NavigationContainer } from "@react-navigation/native"; | import { NavigationContainer } from "@react-navigation/native"; | ||||
| import { Provider, useDispatch } from "react-redux"; | import { Provider, useDispatch } from "react-redux"; | ||||
| import store from "./store"; | import store from "./store"; | ||||
| import { ThemeProvider } from "@Styles"; | |||||
| import '@i18n' | |||||
| import { useFonts } from "expo-font"; | import { useFonts } from "expo-font"; | ||||
| import RootNavigation from "./navigation/RootNavigation"; | 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 { addHeaderToken } from "./request"; | ||||
| import { useTranslation } from "react-i18next"; | |||||
| function App() { | function App() { | ||||
| const { i18n } = useTranslation(); | |||||
| const dispatch = useDispatch(); | const dispatch = useDispatch(); | ||||
| const getToken = async () => { | |||||
| const initialSetup = async () => { | |||||
| const token = await getData(JWT_TOKEN); | const token = await getData(JWT_TOKEN); | ||||
| const refreshToken = await getData(JWT_REFRESH_TOKEN); | const refreshToken = await getData(JWT_REFRESH_TOKEN); | ||||
| const language = await getObjectData(LANGUAGE); | |||||
| if (language !== null) { | |||||
| await i18n.changeLanguage(language.code); | |||||
| } | |||||
| if (token) { | if (token) { | ||||
| addHeaderToken(token); | addHeaderToken(token); | ||||
| }; | }; | ||||
| useEffect(() => { | useEffect(() => { | ||||
| getToken(); | |||||
| initialSetup(); | |||||
| }, []); | }, []); | ||||
| return ( | return ( | ||||
| return ( | return ( | ||||
| <Provider store={store}> | <Provider store={store}> | ||||
| <App /> | |||||
| <ThemeProvider> | |||||
| <App /> | |||||
| </ThemeProvider> | |||||
| </Provider> | </Provider> | ||||
| ); | ); | ||||
| }; | }; |
| return { | return { | ||||
| presets: ['babel-preset-expo'], | presets: ['babel-preset-expo'], | ||||
| plugins: [ | 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" | |||||
| } | |||||
| } | |||||
| ] | |||||
| ] | ] | ||||
| }; | }; | ||||
| }; | }; |
| import { useDispatch } from "react-redux"; | import { useDispatch } from "react-redux"; | ||||
| import { logoutUser } from "../../store/actions/login/loginActions"; | import { logoutUser } from "../../store/actions/login/loginActions"; | ||||
| import useAuthHook from "../../hooks/useAuthHook"; | import useAuthHook from "../../hooks/useAuthHook"; | ||||
| import { useTheme } from "@Styles"; | |||||
| import { useTranslation } from "react-i18next"; | |||||
| const CustomDrawer = (props) => { | const CustomDrawer = (props) => { | ||||
| const { colors } = useTheme(); | |||||
| const { t } = useTranslation(); | |||||
| const {logoutAuthProvider} = useAuthHook() | |||||
| const { logoutAuthProvider } = useAuthHook(); | |||||
| const dispatch = useDispatch(); | const dispatch = useDispatch(); | ||||
| const handleLogout = async () => { | const handleLogout = async () => { | ||||
| logoutAuthProvider() | |||||
| logoutAuthProvider(); | |||||
| dispatch(logoutUser()); | dispatch(logoutUser()); | ||||
| } | |||||
| }; | |||||
| return ( | return ( | ||||
| <View style={styles.container}> | <View style={styles.container}> | ||||
| /> | /> | ||||
| <Text style={styles.userName}>Diligent Software</Text> | <Text style={styles.userName}>Diligent Software</Text> | ||||
| </ImageBackground> | </ImageBackground> | ||||
| <View style={styles.drawerItems}> | |||||
| <View | |||||
| style={[styles.drawerItems, { backgroundColor: colors.background }]} | |||||
| > | |||||
| <DrawerItemList {...props} /> | <DrawerItemList {...props} /> | ||||
| </View> | </View> | ||||
| </DrawerContentScrollView> | </DrawerContentScrollView> | ||||
| <View style={styles.footer}> | |||||
| <View style={[styles.footer, { backgroundColor: colors.background }]}> | |||||
| <TouchableOpacity | <TouchableOpacity | ||||
| onPress={() => Linking.openURL("mailto:office@dilig.net")} | onPress={() => Linking.openURL("mailto:office@dilig.net")} | ||||
| style={styles.footerButton} | style={styles.footerButton} | ||||
| > | > | ||||
| <View style={styles.buttonContent}> | <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> | </View> | ||||
| </TouchableOpacity> | </TouchableOpacity> | ||||
| <TouchableOpacity onPress={handleLogout} style={styles.footerButton}> | <TouchableOpacity onPress={handleLogout} style={styles.footerButton}> | ||||
| <View style={styles.buttonContent}> | <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> | </View> | ||||
| </TouchableOpacity> | </TouchableOpacity> | ||||
| </View> | </View> | ||||
| flex: 1, | flex: 1, | ||||
| }, | }, | ||||
| drawer: { | drawer: { | ||||
| flex: 1, | |||||
| backgroundColor: "#8200d6", | backgroundColor: "#8200d6", | ||||
| }, | }, | ||||
| backgroundImage: { | backgroundImage: { | ||||
| }, | }, | ||||
| drawerItems: { | drawerItems: { | ||||
| flex: 1, | flex: 1, | ||||
| backgroundColor: "#fff", | |||||
| paddingTop: 10, | paddingTop: 10, | ||||
| }, | }, | ||||
| footer: { | footer: { |
| StyleSheet, | StyleSheet, | ||||
| } from "react-native"; | } from "react-native"; | ||||
| import { globalStyles } from "../styles/global"; | import { globalStyles } from "../styles/global"; | ||||
| import { useTheme } from "@Styles"; | |||||
| const InputField = ({ | const InputField = ({ | ||||
| label, | label, | ||||
| onChangeText, | onChangeText, | ||||
| text, | text, | ||||
| name, | name, | ||||
| handleBlur | |||||
| handleBlur, | |||||
| }) => { | }) => { | ||||
| const { colors } = useTheme(); | |||||
| return ( | return ( | ||||
| <View style={styles.textField}> | <View style={styles.textField}> | ||||
| {icon} | {icon} | ||||
| placeholder={label} | placeholder={label} | ||||
| placeholderTextColor="#C6C6C6" | placeholderTextColor="#C6C6C6" | ||||
| keyboardType={keyboardType} | keyboardType={keyboardType} | ||||
| style={styles.textInput} | |||||
| style={[styles.textInput, { color: colors.textPrimary }]} | |||||
| secureTextEntry={true} | secureTextEntry={true} | ||||
| value={text} | value={text} | ||||
| onChangeText={onChangeText} | onChangeText={onChangeText} | ||||
| ) : ( | ) : ( | ||||
| <TextInput | <TextInput | ||||
| name={name} | name={name} | ||||
| autoCapitalize='none' | |||||
| autoCapitalize="none" | |||||
| autoCorrect={false} | autoCorrect={false} | ||||
| placeholder={label} | placeholder={label} | ||||
| placeholderTextColor="#C6C6C6" | placeholderTextColor="#C6C6C6" | ||||
| keyboardType={keyboardType} | keyboardType={keyboardType} | ||||
| style={styles.textInput} | |||||
| style={[styles.textInput, { color: colors.textPrimary }]} | |||||
| value={text} | value={text} | ||||
| onChangeText={onChangeText} | onChangeText={onChangeText} | ||||
| onBlur={handleBlur} | onBlur={handleBlur} |
| 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 |
| import React from "react"; | import React from "react"; | ||||
| import { View, Text, Image, TouchableOpacity, StyleSheet } from "react-native"; | 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 ListItem = ({ photo, title, onPress, publishedAt }) => { | ||||
| const { colors } = useTheme(); | |||||
| const { t } = useTranslation(); | |||||
| const formatDate = (date) => { | const formatDate = (date) => { | ||||
| const tempDate = new Date(date); | const tempDate = new Date(date); | ||||
| const fDate = | const fDate = | ||||
| <View style={{ width: windowWidth - 220 }}> | <View style={{ width: windowWidth - 220 }}> | ||||
| <Text | <Text | ||||
| style={{ | style={{ | ||||
| color: "#333", | |||||
| color: colors.textPrimary, | |||||
| fontFamily: "poppins-regular", | fontFamily: "poppins-regular", | ||||
| fontSize: 14, | fontSize: 14, | ||||
| textTransform: "uppercase", | textTransform: "uppercase", | ||||
| </Text> | </Text> | ||||
| <Text | <Text | ||||
| style={{ | style={{ | ||||
| color: "#333", | |||||
| color: colors.textPrimary, | |||||
| fontFamily: "poppins-regular", | fontFamily: "poppins-regular", | ||||
| fontSize: 14, | fontSize: 14, | ||||
| textTransform: "uppercase", | textTransform: "uppercase", | ||||
| </View> | </View> | ||||
| </View> | </View> | ||||
| <TouchableOpacity style={globalStyles.button} onPress={onPress}> | <TouchableOpacity style={globalStyles.button} onPress={onPress}> | ||||
| <Text style={styles.buttonText}>Read More</Text> | |||||
| <Text style={styles.buttonText}>{t('common.readMore')}</Text> | |||||
| </TouchableOpacity> | </TouchableOpacity> | ||||
| </View> | </View> | ||||
| ); | ); |
| 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"; |
| 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; |
| 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", | |||||
| }, | |||||
| }; |
| 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", | |||||
| }, | |||||
| }; |
| 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 Drawer = createDrawerNavigator(); | ||||
| const AppStack = () => { | 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; |
| import React from "react"; | import React from "react"; | ||||
| import AppStack from "./AppStack"; | import AppStack from "./AppStack"; | ||||
| import AuthStack from "./AuthStack"; | import AuthStack from "./AuthStack"; | ||||
| import { SafeAreaView } from "react-native"; | |||||
| import { useSelector } from "react-redux"; | 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 RootNavigation = () => { | ||||
| const { isDark, colors } = useTheme(); | |||||
| const tokens = useSelector(selectTokens); | 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; | export default RootNavigation; |
| import { createBottomTabNavigator } from "@react-navigation/bottom-tabs"; | import { createBottomTabNavigator } from "@react-navigation/bottom-tabs"; | ||||
| import { getFocusedRouteNameFromRoute } from "@react-navigation/native"; | import { getFocusedRouteNameFromRoute } from "@react-navigation/native"; | ||||
| import HomeScreen from "../screens/HomeScreen"; | |||||
| import HomeScreen from "@Screens/HomeScreen"; | |||||
| import Ionicons from "@expo/vector-icons/Ionicons"; | 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 Tab = createBottomTabNavigator(); | ||||
| const Stack = createNativeStackNavigator(); | const Stack = createNativeStackNavigator(); | ||||
| const HomeStack = () => { | const HomeStack = () => { | ||||
| const { colors } = useTheme(); | |||||
| return ( | return ( | ||||
| <Stack.Navigator> | <Stack.Navigator> | ||||
| <Stack.Screen | <Stack.Screen | ||||
| <Stack.Screen | <Stack.Screen | ||||
| name="PostDetails" | name="PostDetails" | ||||
| component={PostDetailsScreen} | component={PostDetailsScreen} | ||||
| options={({route}) => ({ | |||||
| title: route.params.title | |||||
| options={({ route }) => ({ | |||||
| headerStyle: { backgroundColor: colors.background }, | |||||
| headerTintColor: colors.textPrimary, | |||||
| title: route.params.title, | |||||
| })} | })} | ||||
| /> | /> | ||||
| </Stack.Navigator> | </Stack.Navigator> |
| "eject": "expo eject" | "eject": "expo eject" | ||||
| }, | }, | ||||
| "dependencies": { | "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-community/datetimepicker": "6.5.2", | ||||
| "@react-native-masked-view/masked-view": "0.2.8", | "@react-native-masked-view/masked-view": "0.2.8", | ||||
| "@react-navigation/bottom-tabs": "^6.4.3", | "@react-navigation/bottom-tabs": "^6.4.3", | ||||
| "expo-status-bar": "~1.4.2", | "expo-status-bar": "~1.4.2", | ||||
| "expo-web-browser": "~12.0.0", | "expo-web-browser": "~12.0.0", | ||||
| "formik": "^2.2.9", | "formik": "^2.2.9", | ||||
| "i18next": "^22.4.13", | |||||
| "jwt-decode": "^3.1.2", | "jwt-decode": "^3.1.2", | ||||
| "lodash.filter": "^4.6.0", | "lodash.filter": "^4.6.0", | ||||
| "lodash.isempty": "^4.4.0", | "lodash.isempty": "^4.4.0", | ||||
| "react": "18.1.0", | "react": "18.1.0", | ||||
| "react-dom": "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-gesture-handler": "~2.8.0", | ||||
| "react-native-indicators": "^0.17.0", | "react-native-indicators": "^0.17.0", | ||||
| "react-native-loading-spinner-overlay": "^3.0.1", | "react-native-loading-spinner-overlay": "^3.0.1", |
| 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 = () => { | 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({ | const styles = StyleSheet.create({ | ||||
| container: { | |||||
| flex: 1, | |||||
| justifyContent: 'center', | |||||
| alignItems: 'center' | |||||
| } | |||||
| }) | |||||
| container: { | |||||
| flex: 1, | |||||
| justifyContent: "center", | |||||
| alignItems: "center", | |||||
| }, | |||||
| }); | |||||
| export default FavoriteScreen; | |||||
| export default FavoriteScreen; |
| import { | import { | ||||
| View, | View, | ||||
| Text, | Text, | ||||
| SafeAreaView, | |||||
| ScrollView, | ScrollView, | ||||
| ImageBackground, | ImageBackground, | ||||
| TextInput, | TextInput, | ||||
| StyleSheet, | StyleSheet, | ||||
| } from "react-native"; | } from "react-native"; | ||||
| import Feather from "@expo/vector-icons/Feather"; | 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 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 HomeScreen = ({ navigation }) => { | ||||
| const { colors } = useTheme(); | |||||
| const { t } = useTranslation(); | |||||
| const [posts, setPosts] = useState([]); | const [posts, setPosts] = useState([]); | ||||
| const [query, setQuery] = useState(""); | const [query, setQuery] = useState(""); | ||||
| } | } | ||||
| }, [posts]); | }, [posts]); | ||||
| return ( | return ( | ||||
| <SafeAreaView style={{ flex: 1, backgroundColor: "#fff" }}> | |||||
| <Layout> | |||||
| <ScrollView style={{ padding: 20 }}> | <ScrollView style={{ padding: 20 }}> | ||||
| <View style={styles.wrapper}> | <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()}> | <TouchableOpacity onPress={() => navigation.openDrawer()}> | ||||
| <ImageBackground | <ImageBackground | ||||
| source={require("../assets/images/diligent-purple.png")} | source={require("../assets/images/diligent-purple.png")} | ||||
| onChangeText={(text) => searchFilter(text)} | onChangeText={(text) => searchFilter(text)} | ||||
| autoCapitalize="none" | autoCapitalize="none" | ||||
| autoCorrect={false} | autoCorrect={false} | ||||
| placeholder="Search" | |||||
| placeholder={t('common.search')} | |||||
| value={query} | value={query} | ||||
| placeholderTextColor="#C6C6C6" | placeholderTextColor="#C6C6C6" | ||||
| style={{ flex: 1, color: colors.textPrimary }} | |||||
| /> | /> | ||||
| </View> | </View> | ||||
| <Text> | <Text> | ||||
| {filteredData.length === 0 && ( | {filteredData.length === 0 && ( | ||||
| <View> | <View> | ||||
| <Text style={globalStyles.boldText}>No Results Found</Text> | |||||
| <Text | |||||
| style={[globalStyles.boldText, { color: colors.textPrimary }]} | |||||
| > | |||||
| {t('common.noResults')} | |||||
| </Text> | |||||
| </View> | </View> | ||||
| )} | )} | ||||
| </Text> | </Text> | ||||
| /> | /> | ||||
| ))} | ))} | ||||
| </ScrollView> | </ScrollView> | ||||
| </SafeAreaView> | |||||
| </Layout> | |||||
| ); | ); | ||||
| }; | }; | ||||
| import React, { useEffect } from "react"; | 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 MaterialIcons from "@expo/vector-icons/MaterialIcons"; | ||||
| import Ionicons from "@expo/vector-icons/Ionicons"; | 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 { Formik } from "formik"; | ||||
| import { loginSchema } from "../schemas/loginSchema"; | |||||
| import { loginSchema } from "@Schemas/loginSchema"; | |||||
| import { useDispatch, useSelector } from "react-redux"; | 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 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 LoginScreen = ({ navigation }) => { | ||||
| const { colors } = useTheme(); | |||||
| const { t } = useTranslation(); | |||||
| const { response, promptAsync } = useAuthHook(); | const { response, promptAsync } = useAuthHook(); | ||||
| const dispatch = useDispatch(); | const dispatch = useDispatch(); | ||||
| const error = useSelector(selectLoginError); | const error = useSelector(selectLoginError); | ||||
| const storeToken = async (token) => { | const storeToken = async (token) => { | ||||
| await storeData(ACCESS_TOKEN, token); | await storeData(ACCESS_TOKEN, token); | ||||
| } | |||||
| }; | |||||
| useEffect(() => { | useEffect(() => { | ||||
| if (response?.type === "success") { | if (response?.type === "success") { | ||||
| }; | }; | ||||
| return ( | return ( | ||||
| <SafeAreaView style={globalStyles.safeArea}> | |||||
| <Layout> | |||||
| <Loader visible={isLoading} /> | <Loader visible={isLoading} /> | ||||
| <View style={{ paddingHorizontal: 25 }}> | <View style={{ paddingHorizontal: 25 }}> | ||||
| <View style={{ alignItems: "center" }}> | <View style={{ alignItems: "center" }}> | ||||
| errors, | errors, | ||||
| }) => ( | }) => ( | ||||
| <> | <> | ||||
| <Text style={globalStyles.boldText}>Sign In</Text> | |||||
| <Text | |||||
| style={[globalStyles.boldText, { color: colors.textPrimary }]} | |||||
| > | |||||
| {t('login.signIn')} | |||||
| </Text> | |||||
| <InputField | <InputField | ||||
| name="email" | name="email" | ||||
| label={"Email"} | |||||
| label={t('login.email')} | |||||
| keyboardType="email-address" | keyboardType="email-address" | ||||
| onChangeText={handleChange("email")} | onChangeText={handleChange("email")} | ||||
| text={values.email} | text={values.email} | ||||
| style={{ color: colors.textPrimary }} | |||||
| handleBlur={handleBlur("email")} | handleBlur={handleBlur("email")} | ||||
| icon={ | icon={ | ||||
| <MaterialIcons | <MaterialIcons | ||||
| )} | )} | ||||
| <InputField | <InputField | ||||
| name="password" | name="password" | ||||
| label={"Password"} | |||||
| filedButtonLabel={"Forgot?"} | |||||
| label={t('login.password')} | |||||
| filedButtonLabel={t('register.forgot')} | |||||
| fieldButtonFunction={() => {}} | fieldButtonFunction={() => {}} | ||||
| inputType="password" | inputType="password" | ||||
| handleBlur={handleBlur("password")} | handleBlur={handleBlur("password")} | ||||
| <Text style={styles.errorMessage}>{errors.password}</Text> | <Text style={styles.errorMessage}>{errors.password}</Text> | ||||
| )} | )} | ||||
| {error && <Text style={styles.errorMessage}>{error}</Text>} | {error && <Text style={styles.errorMessage}>{error}</Text>} | ||||
| <CustomButton label={"Login"} onPress={handleSubmit} /> | |||||
| <CustomButton label={t('login.login')} onPress={handleSubmit} /> | |||||
| </> | </> | ||||
| )} | )} | ||||
| </Formik> | </Formik> | ||||
| <Text style={globalStyles.regularCenteredText}>Or login with ...</Text> | |||||
| <Text | |||||
| style={[ | |||||
| globalStyles.regularCenteredText, | |||||
| { color: colors.textPrimary }, | |||||
| ]} | |||||
| > | |||||
| {t('login.orLoginWith')} | |||||
| </Text> | |||||
| <View style={styles.providersContainer}> | <View style={styles.providersContainer}> | ||||
| <TouchableOpacity | <TouchableOpacity | ||||
| onPress={handleGoogleAuth} | onPress={handleGoogleAuth} | ||||
| </TouchableOpacity> | </TouchableOpacity> | ||||
| </View> | </View> | ||||
| <View style={styles.registerContainer}> | <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")}> | <TouchableOpacity onPress={() => navigation.navigate("Register")}> | ||||
| <Text style={styles.registerButtonText}>Sign Up</Text> | |||||
| <Text style={styles.registerButtonText}>{t('login.signUp')}</Text> | |||||
| </TouchableOpacity> | </TouchableOpacity> | ||||
| </View> | </View> | ||||
| </View> | </View> | ||||
| </SafeAreaView> | |||||
| </Layout> | |||||
| ); | ); | ||||
| }; | }; | ||||
| 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({ | 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; |
| 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 { 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 PostDetailsScreen = ({ navigation, route }) => { | ||||
| const [post, setPost] = useState({}); | const [post, setPost] = useState({}); | ||||
| const { colors } = useTheme(); | |||||
| const fetchPost = async () => { | 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) { | if (data) { | ||||
| setPost(data.data); | setPost(data.data); | ||||
| } | } | ||||
| }, []); | }, []); | ||||
| return ( | return ( | ||||
| <View style={{ flex: 1 }}> | |||||
| <Layout> | |||||
| <Image | <Image | ||||
| style={styles.image} | style={styles.image} | ||||
| source={{ | source={{ | ||||
| uri: `http://localhost:1337${post?.attributes?.profileImage.data.attributes.url}`, | 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} | {post?.attributes?.title} | ||||
| </Text> | </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> | |||||
| ); | ); | ||||
| }; | }; | ||||
| textAlign: "center", | textAlign: "center", | ||||
| }, | }, | ||||
| description: { | description: { | ||||
| marginHorizontal: 20 | |||||
| } | |||||
| marginHorizontal: 20, | |||||
| }, | |||||
| }); | }); | ||||
| export default PostDetailsScreen; | export default PostDetailsScreen; |
| 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 ProfileScreen = () => { | ||||
| const { colors } = useTheme(); | |||||
| return ( | return ( | ||||
| <View style={styles.container}> | |||||
| <Text>Profile</Text> | |||||
| </View> | |||||
| <Layout> | |||||
| <View style={styles.container}> | |||||
| <Text style={{color: colors.textPrimary}}>Profile</Text> | |||||
| </View> | |||||
| </Layout> | |||||
| ) | ) | ||||
| } | } | ||||
| import React, { useEffect } from "react"; | import React, { useEffect } from "react"; | ||||
| import { | import { | ||||
| SafeAreaView, | |||||
| ScrollView, | ScrollView, | ||||
| View, | View, | ||||
| Text, | Text, | ||||
| Alert, | Alert, | ||||
| } from "react-native"; | } 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 MaterialIcons from "@expo/vector-icons/MaterialIcons"; | ||||
| import Ionicons from "@expo/vector-icons/Ionicons"; | 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 { Formik } from "formik"; | ||||
| import { registerSchema } from "../schemas/registerSchema"; | |||||
| import { registerSchema } from "@Schemas/registerSchema"; | |||||
| import { useDispatch, useSelector } from "react-redux"; | 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 { | import { | ||||
| clearRegisterErrors, | clearRegisterErrors, | ||||
| registerUser, | registerUser, | ||||
| } from "../store/actions/register/registerActions"; | |||||
| } from "@Store/actions/register/registerActions"; | |||||
| import useAuthHook from "../hooks/useAuthHook"; | 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 RegisterScreen = ({ navigation }) => { | ||||
| const { colors } = useTheme(); | |||||
| const { t } = useTranslation(); | |||||
| const { response, promptAsync } = useAuthHook(); | const { response, promptAsync } = useAuthHook(); | ||||
| const dispatch = useDispatch(); | const dispatch = useDispatch(); | ||||
| const error = useSelector(selectRegisterError); | const error = useSelector(selectRegisterError); | ||||
| const storeToken = async (token) => { | const storeToken = async (token) => { | ||||
| await storeData(ACCESS_TOKEN, token); | await storeData(ACCESS_TOKEN, token); | ||||
| } | |||||
| }; | |||||
| const handleApiResponseSuccess = () => { | 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) => { | const handleSignup = (values) => { | ||||
| }, [response]); | }, [response]); | ||||
| return ( | return ( | ||||
| <SafeAreaView style={globalStyles.safeArea}> | |||||
| <Layout> | |||||
| <Loader visible={isLoading} /> | <Loader visible={isLoading} /> | ||||
| <ScrollView | <ScrollView | ||||
| showsVerticalScrollIndicator={false} | showsVerticalScrollIndicator={false} | ||||
| <View style={{ alignItems: "center" }}> | <View style={{ alignItems: "center" }}> | ||||
| <RegistrationSVG width={300} height={300} /> | <RegistrationSVG width={300} height={300} /> | ||||
| </View> | </View> | ||||
| <Text style={globalStyles.boldText}>Sign Up</Text> | |||||
| <Text style={[globalStyles.boldText, { color: colors.textPrimary }]}> | |||||
| {t("register.register")} | |||||
| </Text> | |||||
| <View style={styles.providersContainer}> | <View style={styles.providersContainer}> | ||||
| <TouchableOpacity | <TouchableOpacity | ||||
| onPress={handleGoogleAuth} | onPress={handleGoogleAuth} | ||||
| <TwitterSVG height={24} width={24} /> | <TwitterSVG height={24} width={24} /> | ||||
| </TouchableOpacity> | </TouchableOpacity> | ||||
| </View> | </View> | ||||
| <Text style={globalStyles.regularCenteredText}> | |||||
| Or, sign up with email... | |||||
| <Text | |||||
| style={[ | |||||
| globalStyles.regularCenteredText, | |||||
| { color: colors.textPrimary }, | |||||
| ]} | |||||
| > | |||||
| {t("register.orSignUpWithMail")} | |||||
| </Text> | </Text> | ||||
| <Formik | <Formik | ||||
| initialValues={{ | initialValues={{ | ||||
| name={"username"} | name={"username"} | ||||
| text={values.username} | text={values.username} | ||||
| onChangeText={handleChange("username")} | onChangeText={handleChange("username")} | ||||
| label={"Username"} | |||||
| label={t("register.username")} | |||||
| handleBlur={handleBlur("username")} | handleBlur={handleBlur("username")} | ||||
| icon={ | icon={ | ||||
| <Ionicons | <Ionicons | ||||
| name={"email"} | name={"email"} | ||||
| text={values.email} | text={values.email} | ||||
| onChangeText={handleChange("email")} | onChangeText={handleChange("email")} | ||||
| label={"Email"} | |||||
| label={t("register.email")} | |||||
| handleBlur={handleBlur("email")} | handleBlur={handleBlur("email")} | ||||
| icon={ | icon={ | ||||
| <MaterialIcons | <MaterialIcons | ||||
| name={"password"} | name={"password"} | ||||
| text={values.password} | text={values.password} | ||||
| onChangeText={handleChange("password")} | onChangeText={handleChange("password")} | ||||
| label={"Password"} | |||||
| label={t("register.password")} | |||||
| handleBlur={handleBlur("password")} | handleBlur={handleBlur("password")} | ||||
| icon={ | icon={ | ||||
| <Ionicons | <Ionicons | ||||
| name={"confirmPassword"} | name={"confirmPassword"} | ||||
| text={values.confirmPassword} | text={values.confirmPassword} | ||||
| onChangeText={handleChange("confirmPassword")} | onChangeText={handleChange("confirmPassword")} | ||||
| label={"Confirm Password"} | |||||
| label={t("register.confirmPassword")} | |||||
| handleBlur={handleBlur("confirmPassword")} | handleBlur={handleBlur("confirmPassword")} | ||||
| icon={ | icon={ | ||||
| <Ionicons | <Ionicons | ||||
| </Text> | </Text> | ||||
| )} | )} | ||||
| {error && <Text style={styles.errorMessage}>{error}</Text>} | {error && <Text style={styles.errorMessage}>{error}</Text>} | ||||
| <CustomButton label={"Sign Up"} onPress={handleSubmit} /> | |||||
| <CustomButton | |||||
| label={t("register.signUp")} | |||||
| onPress={handleSubmit} | |||||
| /> | |||||
| </> | </> | ||||
| )} | )} | ||||
| </Formik> | </Formik> | ||||
| <View style={styles.alreadyRegistered}> | <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()}> | <TouchableOpacity onPress={() => navigation.goBack()}> | ||||
| <Text style={globalStyles.primaryBold}>Sign In</Text> | |||||
| <Text style={globalStyles.primaryBold}>{t("register.signIn")}</Text> | |||||
| </TouchableOpacity> | </TouchableOpacity> | ||||
| </View> | </View> | ||||
| </ScrollView> | </ScrollView> | ||||
| </SafeAreaView> | |||||
| </Layout> | |||||
| ); | ); | ||||
| }; | }; | ||||
| 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; |
| } | } | ||||
| }; | }; | ||||
| 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) => { | export const getData = async (key) => { | ||||
| try { | try { | ||||
| const value = await AsyncStorage.getItem(key); | const value = await AsyncStorage.getItem(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) => { | export const removeData = async (key) => { | ||||
| try { | try { | ||||
| await AsyncStorage.removeItem(key); | await AsyncStorage.removeItem(key); |
| 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, | |||||
| }; |
| 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); |