Feia Pregnancy 2.0

This is a mobile app, made with React Native. It is currently on the market. It is a continuation of the previously lunched Feia Pregnancy app, but with entirely new design, updated content information and some new cloud features. Unlike its predecessor, this one is aiming for international markets and hoping to become a global leader in its field.

FEIA official web page

Specially designed app for the needs of pregnant woman. It contains various medical advices, medical recommendations and diagnosis, as well as a collection of "mini-app" applications.

I was part of the development team and worked on many, front end tasks in this project, most notably the numerous article sections and their coresponding custom components, including:
- accordion component (for convenient presentation of information to the user, when they click on the title of the article, with smooth animations)
- favorites list component (where the user picks their preferred articles, which are then saved to their account in the cloud)
- search component (which allows the user to quickly filter the rich collection of articles and easily find what they need)
and others...

I worked on these and many other components of the app. Some of the external libraries and services which we used in the development process include:
React Navigation (for efficient stack navigation architecture), Redux (app state management), Fuse.js (advanced search algorithm), Firebase (cloud services) - Firestore and cloud functions. For some of them we collaborated closely with colleagues from the back-end team.

Code snippet from FavoriteListScreen functional component
          
  ...

  useFocusEffect(
    React.useCallback(() => {
      BackHandler.addEventListener('hardwareBackPress', onBackPress);

      return () =>
        BackHandler.removeEventListener('hardwareBackPress', onBackPress);
    }, [showSliderPopup]),
  );

  useEffect(() => {
    cacheImage();

    if (isMounted.current) {
      if (isExams) {
        getExamArticles();
      } else {
        getArticles();
      }
    }
    return () => {
      isMounted.current = false;
    };
  }, []);

  useEffect(() => {
      defaultArticles?.length > 0 &&
      searchAndSortArticles(defaultArticles);
  }, [searchVal]);

  const cacheImage = () => {
    let img = articlesData.img !== '' ? articlesData.img : articlesData.icon;
    CacheUtil.cacheImages(CacheUtil.CATEGORY, [articlesData._id], [img]);
  };

  function searchAndSortArticles(articles: any) {
    let filteredArticles = articles;

    if (searchVal) {
      filteredArticles = searchFilter(articles, searchVal, currentLocale);
    }

    filteredArticles = sortArticles(
      filteredArticles,
      currentLocale,
      favoriteArticles,
    );
    setFilteredArticles(filteredArticles);
  }

  async function getArticles() {
    let articles = await DBUtil.getArticlesByCatAndWeek(articlesCatID);

    articles = removeArticlesWithoutTranslation(
      articles,
      currentLocale,
      isFamilyTasks,
    );
    articles = sortArticles(articles, currentLocale, favoriteArticles);

    if (isMounted.current) {
      setFilteredArticles(articles);
      setDefaultArticles(articles);
      setIsLoading(false);
    }
  }

  async function getExamArticles() {
    const indexLists: any = articlesData.children.findIndex(
      (item: any) => item.catType == 'all-info',
    );

    const indexQuestion = articlesData.children.findIndex(
      (item: any) => item.catType == 'question',
    );

    const nameOfAllExams =
      articlesData?.children[indexLists]?.name || 'See All Exams';
    const nameOfDoctorQuestions =
      articlesData?.children[indexQuestion]?.name || 'Questions To The Doctor';
    const questionsCatID = articlesData.children[indexQuestion].id;

    const childrensList = articlesData?.children[indexLists]?.children;
    let examArticlesCatID: string;
    let childrenName: string;
    let index;

    if (currentWeek < 12) {
      index = childrensList.findIndex(
        (item: any) => item.catType == 'first-quarter',
      );
      childrenName = childrensList[index].name;
      examArticlesCatID = childrensList[index].id;
    } else if (currentWeek > 12 && currentWeek < 25) {
      index = childrensList.findIndex(
        (item: any) => item.catType == 'second-quarter',
      );
      childrenName = childrensList[index].name;
      examArticlesCatID = childrensList[index].id;
    } else {
      index = childrensList.findIndex(
        (item: any) => item.catType == 'third-quarter',
      );
      childrenName = childrensList[index].name;
      examArticlesCatID = childrensList[index].id;
    }

    let examArticles = await DBUtil.getArticlesByCatAndWeek(examArticlesCatID);
    let questionsArticles = await DBUtil.getArticlesByCatAndWeek(
      questionsCatID,
    );

    examArticles = removeArticlesWithoutTranslation(
      examArticles,
      currentLocale,
    );
    questionsArticles = removeArticlesWithoutTranslation(
      questionsArticles,
      currentLocale,
      true,
    );

    examArticles = sortArticles(examArticles, currentLocale, favoriteArticles);
    questionsArticles = sortArticles(
      questionsArticles,
      currentLocale,
      favoriteArticles,
    );

    if (isMounted.current) {
      setHeaderDesription(childrenName);
      setFilteredArticles(examArticles);
      setDefaultArticles(examArticles);
      setQuestionArticles(questionsArticles);
      setExamFooterNames({
        nameOfAllExams: nameOfAllExams,
        nameOfDoctorQuestions: nameOfDoctorQuestions,
      });
      setIsLoading(false);
    }
  }

  const chooseComponent = React.useMemo(() => {
    return (
      (isAccordionComponent && (
        <CustomAccordion
          articleData={filteredArticles || []}
          usePlusButton={isExams ? '#CCA3CD' : '#A6C8CD'}
          onPlusButtonPress={(val: {viewed: boolean; title: string}) => {
            isExams ? setExamOptions(val) : setSymptomsOptions(val);
          }}
          useFlatlist
        />
      )) || (
        <Favorites
          favData={filteredArticles || []}
          currentLocale={currentLocale}
          isHeartIcon={isArticles}
          imageSource={articlesData._id}
        />
      )
    );
  }, [filteredArticles, favoriteArticles, viewedArticles]);

  return (
    <KeyboardAvoidingView
      behavior="height"
      enabled
      style={{backgroundColor: Theme.white, flex: 1}}
    >
      <BackHeader
        onPress={() => {
          onPress ? onPress() : goBack();
        }}
        stylesContainer={(showSliderPopup && {zIndex: -2}) || {}}
      />

      <View
        style={{
          flex: 1,
          backgroundColor: Theme.white,
          marginTop: Screen.screenWidth * 0.1,
        }}>
        <View style={{flex: 1}}>
          <Header
            name={articlesData.name}
            description={isExams ? headerDesription.toUpperCase() : ''}
            stylesContainer={{marginBottom: 0}}
          />

          <CloudyBackground
            style={{zIndex: -1}}
            clouds={[
              {type: CLOUD_TYPE.TYPE_1, vSize: '10%', position: '2%'},
              {type: CLOUD_TYPE.TYPE_1, vSize: '12%', position: '40%'},
              {type: CLOUD_TYPE.TYPE_2, vSize: '12%', position: '72%'},
            ]}
          />
          {(!isLoading && filteredArticles && chooseComponent) || (
            <GlobalLoading visible={isLoading} />
          )}

          {/* Footers */}
          {(isExams && (
            <ArticleFooter
              leftButton={() => {
                navigate('Main', {
                  screen: 'AllArticles',
                  params: {
                    screen: 'ListOfAll',
                    params: {
                      categoryID: articlesCatID || '',
                      currentLocale: currentLocale,
                      data: articlesData || {},
                    },
                  },
                });
              }}
              rightButton={() => setShowSliderPopup(true)}
              nameOfLeftBtn={examFooterNames.nameOfAllExams}
              nameOfRightBtn={examFooterNames.nameOfDoctorQuestions}
              style={{
                leftButtonContainer: {flex: 1},
                rightButtonText: {fontSize: 11},
              }}
            />
          )) || <BottomButton isSearch={true} getSearchVal={setSearchVal} />}
        </View>
        {showSliderPopup && (
          <SliderPopup
            closeList={() => setShowSliderPopup(false)}
            articleData={questionArticles}
            name={examFooterNames.nameOfDoctorQuestions}
          />
        )}
      </View>

      {examOptions?.viewed && (
        <ReviewMainScreen
          closeList={() => {
            setExamOptions({viewed: false});
            console.log('here');
          }}
          currentDate={''}
          dataTitle={examOptions.title}
        />
      )}

      {symptomsOptions?.viewed && (
        <SymptomsMainScreen
          closeList={() => setSymptomsOptions({viewed: false})}
          currentDate={''}
          dataTitle={symptomsOptions.title}
        />
      )}
    </KeyboardAvoidingView>
  );
}

          
        
Code snippet from MainContext provider component
          
// This component provides context for all other components,
// which are subscribed to listen for it

import React from 'react';

const initialState = {
  selectedWeekByUser: 0,
};

const contextWrapper = (component?: React.Component) => ({
  ...initialState,
  setSelectedWeekByUser: (index: number) => {
    initialState.selectedWeekByUser = index;
    component?.setState({context: contextWrapper(component)});
  },
});

type Context = ReturnType<typeof contextWrapper>;

export const Context = React.createContext<Context>(contextWrapper());

interface State {
  context: Context;
}

export class ContextProvider extends React.Component {
  state: State = {
    context: contextWrapper(this),
  };

  render() {
    return (
      <Context.Provider value={this.state.context}>
        {this.props.children}
      </Context.Provider>
    );
  }
}

          
        
Code snippet from MainStack navigation component
          

// Here I wrap the entire navigation stack with context provider,
// giving React context access to all nested components who are listening 

...
const Stack = createNativeStackNavigator();

const MainStack = () => {
  return (
    <ContextProvider>
      <Stack.Navigator screenOptions={{headerShown: false}}>
        <Stack.Screen name="PregnancyMain" component={PregnancyMainScreen} />
        <Stack.Screen name="ImportantMain" component={ImportantMainScreen} />
        <Stack.Screen name="AllArticles" component={AllArticlesStack} />
        <Stack.Screen name="Comparison" component={ComparisonsMainScreen} />
        <Stack.Screen
          name="GestationalWeek"
          component={GestationalWeekScreen}
        />
        <Stack.Screen name="Calendar" component={CalendarsStack} />
      </Stack.Navigator>
    </ContextProvider>
  );
};

export default MainStack;

export type MainStackNavigationProp =
  NativeStackNavigationProp<MainStackParamList>;

          
        

Back to projects