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