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 numerous, front end
components in this project, most notably on the "mini-apps" section.
It is a collection of smaller applications, each one with a specific
goal. Their functions include:
- tracking activities like daily steps, using the user's
geolocation
- doctor's consultations organizer
- contraction's counter
- yoga exercise for pregnant women (video gallery).
- Mozart for babies (music player with collection of songs)
and others...
It was my responsibility to put together some of these apps, either
completely from scratch or partially. Some of them required more
complex app state management, with the use of Redux.
Code snippet from MozartScreen functional component
export default function MozartScreen() {
let getDownloadedSongs = useSelector(RSApp.getMozartDownloadedSongs);
getDownloadedSongs = getDownloadedSongs || [];
const defaultAppColor = Theme.Mozart;
const { translateStatic } = useTranslations();
const currentParent = useSelector(RSParents.getAppsData);
const parentType = useSelector(RSAuth.getParentType);
const lang = useSelector((state: RootState) => state.app?.lang);
const dispatch = useDispatch();
const [downloadedSongs, setDownloadedSong] = useState(getDownloadedSongs);
const [blurEffect, setBlurEffect] = useState<boolean>(false);
const [blurRef, setBlurRef] = useState<any>(null);
const [blurRefSlider, setBlurRefSlider] = useState<any>(null);
const [loadingTime, setLoadingTime] = useState<boolean>(false);
const [showInfoModal, setShowInfoModal] = useState(false);
const [currentTime, setCurrentTime] = useState(0);
const [endTime, setEndTime] = useState(0);
const [isLoopMode, setIsLoopMode] = useState<boolean>(false);
const [isShuffleMode, setIsShuffleMode] = useState<boolean>(false);
const [move, setMove] = useState(0);
const videoPlayerRef = useRef<any>(null);
const pageRef = useRef(null);
const pageRefSlider = useRef(null);
const arrowIconRef = useRef(null);
const [isNowPlaying, setIsNowPlaying] = useState<boolean>(
videoPlayerRef?.current?.state?.isPlaying || true
);
let mozartSongProps: any =
currentParent?.mine?.miniAppsData?.mozart?.mozartSongPropsFromDB || [];
const [songsDB, setSongsDB] = useState<Array<any>>([]); // songs
const [selectedSong, setSelectedSong] = useState<any>({});
const getSongProps = (selectedSong: songPropTypes, songsDB: Array<any>) => {
let songInfo = (songsDB &&
songsDB.find((element: any) => element.song == selectedSong.song)) || {
url: "",
song: 0,
sizeMb: 0,
name: "",
duration: ""
};
let result = {
name: songInfo.name,
songUrl: songInfo.url,
sizeMb: songInfo.sizeMb,
song: songInfo.song,
duration: songInfo.duration
};
return result;
};
const [songProps, setSongProps] = useState<any>(
getSongProps(selectedSong, songsDB)
);
// let songProps = getSongProps(selectedSong, songsDB);
const getSongPropsFromDB = async () => {
let resultDB = await PouchDBUtil.getMaster().query(
"all/doc-by-type_subType",
{
key: ["mozartSong", null],
include_docs: true
// attachments: true
}
);
if (resultDB) {
resultDB = resultDB.rows.map((el: any) => el.doc);
return resultDB;
}
};
useEffect(() => {
if (
typeof mozartSongProps == "undefined" ||
mozartSongProps.length == 0 ||
(mozartSongProps[0]?._55 && mozartSongProps[0]?._55.length == 0)
) {
// step (1): request songs from DB and record them in redux state
// then load them from there to the local state
// (this step will be triggered on first run only)
const songs = getSongPropsFromDB() || [];
console.log("get songs from DB");
getSongPropsFromDB()
.then(() => {
dispatch(
RAParents?.addMozartSongPropsFromDB(parentType, songs as any, true)
);
setSongsDB(mozartSongProps[0]?._55);
})
.catch((err: any) => {
console.log(`Error with getting songs from database: `, err);
});
} else if (typeof songsDB == "undefined" || songsDB.length == 0) {
// step (2): this is a backup
// if step 1 was not completed successfuly and
// songs were not recieved from redux state, retry
setSongsDB(mozartSongProps[0]?._55);
}
if (
videoPlayerRef &&
videoPlayerRef.current &&
typeof songsDB != "undefined" &&
songsDB.length > 0
) {
// step (4): start the player (this will be repeated after every song switch)
videoPlayerRef.current.resume();
setIsNowPlaying(true);
} else if (songsDB && songsDB.length > 0) {
// step (3): load song props to local state and setSelectedSong
setSongProps(
getSongProps(getCombinedSongList(currentParent, songsDB)[0], songsDB)
);
setSelectedSong(getCombinedSongList(currentParent, songsDB)[0]);
}
}, [songsDB, selectedSong]);
const goToNextSong = (next: boolean) => {
// reset time before loading new song
setCurrentTime(0);
setEndTime(0);
let song = next
? playSong(selectedSong, isShuffleMode, currentParent, songsDB).next
: playSong(selectedSong, isShuffleMode, currentParent, songsDB).previous;
if (song) {
setSongProps(getSongProps(song, songsDB));
setSelectedSong(song);
}
};
const playPause = () => {
setIsNowPlaying(!isNowPlaying);
if (isNowPlaying) {
videoPlayerRef.current.pause();
} else {
videoPlayerRef.current.resume();
}
};
const switchToSong = (song: songPropTypes) => {
setSelectedSong(song);
setSongProps(getSongProps(song, songsDB));
videoPlayerRef.current.resume();
};
const songData = (downloadedSongs: Array<any>) => {
let nameAndPath = songProps.songUrl.slice(
songProps.songUrl.lastIndexOf("/") + 1
);
let songName = Number(nameAndPath.slice(0, nameAndPath.lastIndexOf(".")));
let fileFormat = nameAndPath.slice(nameAndPath.lastIndexOf("."));
let isDownloaded = downloadedSongs.indexOf(songName) >= 0;
return { songName, fileFormat, isDownloaded };
};
const downloadFile = (downloadedSongs: Array<any>) => {
let { songName, fileFormat, isDownloaded } = songData(downloadedSongs);
setLoadingTime(true);
// Remove or add song to downloaded list
if (isDownloaded) {
let newDownloads = [...downloadedSongs];
newDownloads = newDownloads.filter(el => el != songName);
setDownloadedSong(newDownloads);
dispatch(RAApp.toggleDownloadedMozartSong(songName, !isDownloaded));
setLoadingTime(false);
return;
}
const downloadDest = `${RNFS.DocumentDirectoryPath}/${songName}.${fileFormat}`;
const result = RNFS.downloadFile({
fromUrl: songProps.songUrl,
toFile: downloadDest,
cacheable: true
});
result.promise
.then(() => {
console.log("Song is downloaded");
let newDownloads = [...downloadedSongs];
newDownloads.push(songName);
setDownloadedSong(newDownloads);
dispatch(RAApp.toggleDownloadedMozartSong(songName, !isDownloaded));
setLoadingTime(false);
})
.catch(err => {
setLoadingTime(false);
console.log(`Error with downloading song ${songName}`, err);
});
};
const rotatePercentbar = {
// not a magic number!
// this dynamically turns current song time to percents, to rotation degrees
// 0% of song completed = 0deg; 100% of song completed = 180deg
from: {
transform: [
{
rotateZ:
(currentTime > 0 &&
endTime > 0 &&
`${(currentTime / endTime) * 100 * 1.8}deg`) ||
"0deg"
}
]
},
to: {
transform: [
{
rotateZ:
(currentTime > 0 &&
endTime > 0 &&
`${(currentTime / endTime) * 100 * 1.8}deg`) ||
"0deg"
}
]
}
};
const rotateLoading = {
from: {
transform: [{ rotateZ: "0deg" }]
},
to: {
transform: [{ rotateZ: "360deg" }]
}
};
const rightHeadphoneDynamicColor = {
backgroundColor:
currentTime > endTime - 1 && currentTime !== 0 && endTime !== 0
? Theme.drinkWaterSecondaryColor
: Theme.lightGray2
};
...
Code snippet from MozartPlayList functional component
import React, { useEffect, useState } from "react";
import {
Text,
View,
TouchableOpacity,
StyleSheet,
Image,
Platform,
ScrollView
} from "react-native";
import Theme from "../../../../../Theme";
import Screen from "../../../../../constants/Screen";
import {
songPropTypes,
toggleFavorite,
getSavedFavorite,
getIsFavorite
} from "../mozartUtil";
// import { songs } from "./songs";
import { useTranslations } from "../../../../../utils/hooks/TranslationHooks";
import IconII from "react-native-vector-icons/Ionicons";
import { useDispatch } from "react-redux";
import { styles } from "../styles/playlistStyles";
export default function Playlist({
color,
selectedSong,
switchToSong,
currentParent,
parentType,
songsDB
}: {
color: string;
selectedSong: any;
switchToSong: Function;
currentParent: any;
parentType: any;
songsDB: any;
}) {
const { translateStatic } = useTranslations();
const dispatch = useDispatch();
let favoriteSongs = getSavedFavorite(currentParent);
const getSongProps = (selectedSong: songPropTypes, songsDB: Array) => {
let songInfo = songsDB.find(
(element: any) => element.song == selectedSong.song
) || {
url: "",
song: 0,
sizeMb: 0,
name: "",
duration: ""
};
let result = {
name: songInfo.name,
songUrl: songInfo.url,
sizeMb: songInfo.sizeMb,
song: songInfo.song,
duration: songInfo.duration
};
return result;
};
const songView = (song: songPropTypes, index: number) => {
let newSong = songsDB.find((el: any) => el.song == song.song) || songsDB[0];
let isFav = getIsFavorite(currentParent, newSong?.song || -1);
let isSelected = selectedSong == newSong;
return (
<TouchableOpacity ;
onPress={() => {
switchToSong(newSong);
}}
key={index}
style={styles.songRowParentContainer}
>
< View style={styles.songNameContainer}
{newSong ? (
<Text
style={[
styles.songTextItems,
{ color: isSelected ? Theme.color2 : Theme.gray1 }
]}
>
{translateStatic(getSongProps(newSong, songsDB).name)}
</Text>
) : null}
/>
<View style={styles.timeAndIconContainer}>
<Text style={[styles.songTextItems, { color: Theme.gray1 }]}>
{getSongProps(newSong, songsDB).duration}
</Text>
<TouchableOpacity
onPress={() =>
toggleFavorite(song, currentParent, dispatch, parentType)
}
style={styles.heartIconContainer}
>
<IconII
name={"md-heart"}
size={Math.round(Screen.screenWidth * 0.05)}
color={isFav ? color : Theme.gray3}
/>
</TouchableOpacity>
</View>
</>TouchableOpacity>
);
};
return (
<ScrollView
style={styles.scrollViewContainer}
showsVerticalScrollIndicator={false}
>
{songsDB &&
songsDB
.filter((el: any) => favoriteSongs.indexOf(el.song) > -1)
.sort((a: any, b: any) => a.song - b.song)
.map(songView)}
{songsDB &&
favoriteSongs.length > 0 &&
favoriteSongs.length !=
songsDB?.filter((el: any) => el.song > -1).length ? (
<View style={styles.songDeviderStyleLine} />
) : null}
{songsDB &&
songsDB
.filter(
(el: any) => el.song > -1 && favoriteSongs.indexOf(el.song) == -1
)
.sort((a: any, b: any) => a.song - b.song)
.map(songView)}
<View style={styles.dummyPlaceholderView} />
</ScrollView>
);
}
Back to projects