Bläddra i källkod

added connection context and small fixes

Viktoriia 1 år sedan
förälder
incheckning
a975747010

+ 8 - 3
App.tsx

@@ -4,13 +4,18 @@ import { NavigationContainer } from '@react-navigation/native';
 import { queryClient } from 'src/utils/queryClient';
 
 import Route from './Route';
+import { ConnectionProvider } from 'src/contexts/ConnectionContext';
+import ConnectionBanner from 'src/components/ConnectionBanner/ConnectionBanner';
 
 export default function App() {
   return (
     <QueryClientProvider client={queryClient}>
-      <NavigationContainer>
-        <Route />
-      </NavigationContainer>
+      <ConnectionProvider>
+        <NavigationContainer>
+          <ConnectionBanner />
+          <Route />
+        </NavigationContainer>
+      </ConnectionProvider>
     </QueryClientProvider>
   );
 }

+ 41 - 0
src/components/ConnectionBanner/ConnectionBanner.tsx

@@ -0,0 +1,41 @@
+import React, { useEffect, useState } from 'react';
+import { View, Text } from 'react-native';
+import { useConnection } from 'src/contexts/ConnectionContext';
+import { styles } from './styles';
+
+const ConnectionBanner = () => {
+  const netInfo = useConnection();
+  const [visible, setVisible] = useState(false);
+  const [bannerMessage, setBannerMessage] = useState('');
+  const [hadConnection, setHadConnection] = useState(netInfo?.isInternetReachable);
+
+  useEffect(() => {
+    if (netInfo?.isInternetReachable !== null) {
+      if (hadConnection === false && netInfo?.isInternetReachable) {
+        setBannerMessage('Internet connection restored!');
+        showBanner();
+      } else if (netInfo?.isInternetReachable === false) {
+        setBannerMessage('No internet connection!');
+        showBanner();
+      }
+      setHadConnection(netInfo?.isInternetReachable);
+    }
+  }, [netInfo?.isInternetReachable]);
+
+  const showBanner = () => {
+    setVisible(true);
+    setTimeout(() => {
+      setVisible(false);
+    }, 3000);
+  };
+
+  if (!visible) return null;
+
+  return (
+    <View style={styles.banner}>
+      <Text style={styles.text}>{bannerMessage}</Text>
+    </View>
+  );
+};
+
+export default ConnectionBanner;

+ 20 - 0
src/components/ConnectionBanner/styles.tsx

@@ -0,0 +1,20 @@
+import { StyleSheet, Dimensions } from 'react-native';
+import { statusBarHeight } from 'src/constants/constants';
+
+export const styles = StyleSheet.create({
+  banner: {
+    position: 'absolute',
+    top: statusBarHeight,
+    left: 0,
+    width: Dimensions.get('window').width,
+    backgroundColor: 'rgba(15, 63, 79, 0.7)',
+    padding: 10,
+    justifyContent: 'center',
+    alignItems: 'center',
+    zIndex: 1
+  },
+  text: {
+    color: 'white',
+    fontWeight: '500'
+  }
+});

+ 2 - 10
src/components/Modal/index.tsx

@@ -1,14 +1,9 @@
 import React, { FC, ReactNode } from 'react';
-import {
-  Dimensions,
-  DimensionValue,
-  Platform,
-  StatusBar,
-  View
-} from 'react-native';
+import { Dimensions, DimensionValue, View } from 'react-native';
 import ReactModal from 'react-native-modal';
 import { ModalHeader } from './ModalHeader/modal-header';
 import { styles } from './style';
+import { statusBarHeight } from 'src/constants/constants';
 
 type Props = {
   children: ReactNode;
@@ -26,9 +21,6 @@ export const Modal: FC<Props> = ({
   headerTitle
 }) => {
   const screenHeight = Dimensions.get('screen').height;
-  const NOTCH_HEIGHT = 44;
-  const statusBarHeight =
-    Platform.OS === 'ios' ? StatusBar.currentHeight || NOTCH_HEIGHT : StatusBar.currentHeight || 0;
   const adjustedHeight = screenHeight - statusBarHeight;
 
   return (

+ 7 - 2
src/components/PageWrapper/styles.ts

@@ -1,5 +1,10 @@
-import { StyleSheet } from 'react-native';
+import { Platform, StatusBar, StyleSheet } from 'react-native';
 
 export const styles = StyleSheet.create({
-  wrapper: { marginLeft: '5%', marginRight: '5%', height: '100%' }
+  wrapper: {
+    marginLeft: '5%',
+    marginRight: '5%',
+    height: '100%',
+    paddingTop: Platform.OS === 'android' ? StatusBar.currentHeight : 0
+  }
 });

+ 5 - 0
src/constants/constants.ts

@@ -0,0 +1,5 @@
+import { Platform, StatusBar } from 'react-native';
+
+const NOTCH_HEIGHT = 44;
+export const statusBarHeight =
+  Platform.OS === 'ios' ? StatusBar.currentHeight || NOTCH_HEIGHT : StatusBar.currentHeight || 0;

+ 14 - 0
src/contexts/ConnectionContext.tsx

@@ -0,0 +1,14 @@
+import React, { useContext, createContext } from 'react';
+import { useNetInfo } from '@react-native-community/netinfo';
+
+import { NetInfoState } from '@react-native-community/netinfo';
+
+const ConnectionContext = createContext<NetInfoState | null>(null);
+
+export const ConnectionProvider = ({ children }: { children: React.ReactNode }) => {
+  const netInfo = useNetInfo();
+
+  return <ConnectionContext.Provider value={netInfo}>{children}</ConnectionContext.Provider>;
+};
+
+export const useConnection = () => useContext(ConnectionContext);

+ 4 - 0
src/screens/InAppScreens/ProfileScreen/Profile/edit-personal-info.tsx

@@ -140,6 +140,10 @@ export const EditPersonalInfo = () => {
                     queryKey: userQueryKeys.getProfileData(),
                     refetchType: 'all'
                   });
+                  queryClient.invalidateQueries({
+                    queryKey: userQueryKeys.getProfileInfo(),
+                    refetchType: 'all'
+                  });
 
                   Image.clearDiskCache();
                   Image.clearMemoryCache();

+ 2 - 1
src/screens/InAppScreens/ProfileScreen/Settings/index.tsx

@@ -88,8 +88,9 @@ const Settings = () => {
   return (
     <PageWrapper>
       <Header label={'Settings'} />
-      {buttons.map((button) => (
+      {buttons.map((button, index) => (
         <MenuButton
+          key={index}
           label={button.label}
           icon={button.icon}
           red={button.red}

+ 33 - 20
src/screens/InAppScreens/ProfileScreen/index.tsx

@@ -1,4 +1,4 @@
-import React, { FC, ReactNode, useEffect, useState } from 'react';
+import React, { FC, ReactNode, useState } from 'react';
 import { ScrollView, Text, TouchableOpacity, View } from 'react-native';
 import { Image } from 'expo-image';
 import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs';
@@ -12,7 +12,7 @@ import {
   usePostGetProfileInfoQuery
 } from '@api/user';
 
-import { BigText, Button, PageWrapper, Loading } from '../../../components';
+import { BigText, Button, PageWrapper, Loading, AvatarWithInitials } from '../../../components';
 import { Colors } from '../../../theme';
 import { styles } from './styles';
 import { ButtonVariants } from '../../../types/components';
@@ -58,10 +58,18 @@ const ProfileScreen: FC<Props> = ({ navigation, route }) => {
     <PageWrapper>
       <View style={styles.pageWrapper}>
         <View>
-          <Image
-            style={{ borderRadius: 64 / 2, width: 64, height: 64 }}
-            source={{ uri: API_HOST + data.avatar }}
-          />
+          {data.avatar ? (
+            <Image
+              style={{ borderRadius: 64 / 2, width: 64, height: 64 }}
+              source={{ uri: API_HOST + data.avatar }}
+            />
+          ) : (
+            <AvatarWithInitials
+              text={`${data.first_name[0] ?? ''}${data.last_name[0] ?? ''}`}
+              flag={API_HOST + data.homebase_flag}
+              size={64}
+            />
+          )}
         </View>
         <View style={{ gap: 5, width: '75%' }}>
           <Text style={[styles.headerText, { fontSize: getFontSize(18), flex: 0 }]}>
@@ -82,7 +90,7 @@ const ProfileScreen: FC<Props> = ({ navigation, route }) => {
                 Age: {getYears(data.date_of_birth)}
               </Text>
               <Image source={{ uri: API_HOST + data.homebase_flag }} style={styles.countryFlag} />
-              {data.homebase2_flag ? (
+              {data.homebase2_flag && data.homebase2_flag !== data.homebase_flag ? (
                 <Image
                   source={{ uri: API_HOST + data.homebase2_flag }}
                   style={[styles.countryFlag, { marginLeft: -15 }]}
@@ -108,9 +116,8 @@ const ProfileScreen: FC<Props> = ({ navigation, route }) => {
           )
         }}
       >
-        <Tab.Screen
-          name="Personal Info"
-          component={() => (
+        <Tab.Screen name="Personal Info">
+          {() => (
             <PersonalInfo
               data={{
                 bio: data.bio,
@@ -123,7 +130,9 @@ const ProfileScreen: FC<Props> = ({ navigation, route }) => {
               }}
             />
           )}
-        />
+        </Tab.Screen>
+        <Tab.Screen name="Ranking">{() => <View></View>}</Tab.Screen>
+        <Tab.Screen name="Photos">{() => <View></View>}</Tab.Screen>
       </Tab.Navigator>
     </PageWrapper>
   );
@@ -158,10 +167,11 @@ const PersonalInfo: FC<PersonalInfoProps> = ({ data }) => {
       style={{ marginTop: 20 }}
     >
       <InfoItem inline={true} title={'RANKING'}>
-        {data.scores?.map((data) => {
+        {data.scores?.map((data, index) => {
           if (!data.score) return null;
           return (
             <View
+              key={index}
               style={{ display: 'flex', flexDirection: 'column', gap: 5, alignItems: 'center' }}
             >
               <Text style={[styles.headerText, { flex: 0 }]}>{data.score}</Text>
@@ -171,8 +181,11 @@ const PersonalInfo: FC<PersonalInfoProps> = ({ data }) => {
         })}
       </InfoItem>
       <InfoItem showMore={showMoreSeries} inline={true} title={'SERIES BADGES'}>
-        {data.series?.slice(0, showMoreSeries ? data.series.length : 8).map((data) => (
-          <View style={{ display: 'flex', flexDirection: 'column', gap: 5, alignItems: 'center' }}>
+        {data.series?.slice(0, showMoreSeries ? data.series.length : 8).map((data, index) => (
+          <View
+            key={index}
+            style={{ display: 'flex', flexDirection: 'column', gap: 5, alignItems: 'center' }}
+          >
             <Image source={{ uri: API_HOST + data.icon_png }} style={{ width: 28, height: 28 }} />
             <Text style={[styles.headerText, { flex: 0 }]}>{data.score}</Text>
           </View>
@@ -204,12 +217,12 @@ const PersonalInfo: FC<PersonalInfoProps> = ({ data }) => {
       </InfoItem>
       <InfoItem title={'SOCIAL LINKS'}>
         <View style={{ display: 'flex', flexDirection: 'row', gap: 15, alignItems: 'center' }}>
-          {data.links.f?.link ? <IconFacebook fill={Colors.DARK_BLUE} /> : null}
-          {data.links.i?.link ? <IconInstagram fill={Colors.DARK_BLUE} /> : null}
-          {data.links.t?.link ? <IconTwitter fill={Colors.DARK_BLUE} /> : null}
-          {data.links.y?.link ? <IconYouTube fill={Colors.DARK_BLUE} /> : null}
-          {data.links.www?.link ? <IconGlobe fill={Colors.DARK_BLUE} /> : null}
-          {data.links.other?.link ? <IconLink fill={Colors.DARK_BLUE} /> : null}
+          {data.links?.f?.link ? <IconFacebook fill={Colors.DARK_BLUE} /> : null}
+          {data.links?.i?.link ? <IconInstagram fill={Colors.DARK_BLUE} /> : null}
+          {data.links?.t?.link ? <IconTwitter fill={Colors.DARK_BLUE} /> : null}
+          {data.links?.y?.link ? <IconYouTube fill={Colors.DARK_BLUE} /> : null}
+          {data.links?.www?.link ? <IconGlobe fill={Colors.DARK_BLUE} /> : null}
+          {data.links?.other?.link ? <IconLink fill={Colors.DARK_BLUE} /> : null}
         </View>
       </InfoItem>
     </ScrollView>

+ 5 - 5
src/screens/LoginScreen/index.tsx

@@ -1,4 +1,4 @@
-import { FC } from 'react';
+import { FC, useEffect } from 'react';
 import { View } from 'react-native';
 import { NavigationProp, useNavigation, CommonActions } from '@react-navigation/native';
 import { Formik } from 'formik';
@@ -31,10 +31,10 @@ const LoginScreen: FC<Props> = ({ navigation }) => {
     await fetchAndSaveStatistics(token);
   }
 
-  if (data) {
-    if (data.token) {
+  useEffect(() => {
+    if (data && data.token) {
       storage.set('token', data.token);
-      storage.set('uid', data.uid);
+      storage.set('uid', data.uid.toString());
       updateLocalData(data.token);
       dispatch(
         CommonActions.reset({
@@ -43,7 +43,7 @@ const LoginScreen: FC<Props> = ({ navigation }) => {
         })
       );
     }
-  }
+  }, [data]);
 
   return (
     <PageWrapper>

+ 11 - 11
src/screens/RegisterScreen/EditAccount/index.tsx

@@ -1,4 +1,4 @@
-import React, { useCallback, useState } from 'react';
+import React, { useCallback, useEffect, useState } from 'react';
 import { View, ScrollView } from 'react-native';
 import { Formik } from 'formik';
 import * as yup from 'yup';
@@ -43,16 +43,8 @@ const EditAccount = () => {
     }, [])
   );
 
-  if (isLoading) {
-    return null;
-  }
-
-  const updateLocalData = async (token: string) => {
-    await fetchAndSaveStatistics(token);
-  }
-
-  if (data) {
-    if (data.token) {
+  useEffect(() => {
+    if (data && data.token) {
       storage.set('token', data.token);
       storage.set('uid', data.uid.toString());
       updateLocalData(data.token);
@@ -63,6 +55,14 @@ const EditAccount = () => {
         })
       );
     }
+  }, [data]);
+
+  const updateLocalData = async (token: string) => {
+    await fetchAndSaveStatistics(token);
+  };
+
+  if (isLoading) {
+    return null;
   }
 
   return (