From 76ec6331f54325bd8f946d3f6af07808cdce53d1 Mon Sep 17 00:00:00 2001 From: aeroreyna Date: Sat, 19 Mar 2022 09:28:02 -0700 Subject: [PATCH] Add profile, group, and Courses cards --- App.js | 4 +- Views/Courses.js | 103 +++++++++++++++++++++++++++++------ Views/Groups.js | 13 +++-- Views/Search.js | 34 ++++++------ components/CourseCard.js | 104 ++++++++++++++++++++++++++++++++++++ components/GroupCard.js | 86 +++++++++++++++++++++++++++++ components/Media.js | 18 +++++-- components/ProfileCard.js | 80 +++++++++++++++++++++++++++ components/ProfileHeader.js | 2 +- 9 files changed, 401 insertions(+), 43 deletions(-) create mode 100644 components/CourseCard.js create mode 100644 components/GroupCard.js create mode 100644 components/ProfileCard.js diff --git a/App.js b/App.js index 4995ea0..fb26bb7 100644 --- a/App.js +++ b/App.js @@ -191,9 +191,9 @@ export default function App() { { + let courses; + let popular; + await API.getCourses().then((data) => { + courses = data.groups; + popular = [...data.groups].sort((a, b) => { + return Object.keys(b.subscribed).length - Object.keys(a.subscribed).length; + }); + }); + let watching = {}; + let watchingProms = []; + Object.keys(profileObj.data).forEach((videoId) => { + if (profileObj.data[videoId].profileId) { + let profileId = profileObj.data[videoId].profileId; + watchingProms.push(API.getUserProfile(profileId).then(profile => { + if (!profile.isCourse) + return 0; + if (!watching[profileId]) + watching[profileId] = { profile, progress: [], mostRecent: 0 }; + if (watching[profileId].mostRecent < profileObj.data[videoId].ts) + watching[profileId].mostRecent = profileObj.data[videoId].ts; + watching[profileId].progress.push(profileObj.data[videoId]); + })); + } + }); + let watchingArray = []; + await Promise.all(watchingProms).then(() => { + for (const courseId in watching) { + watchingArray.push(watching[courseId]); + } + watchingArray = watchingArray.sort((a, b) => { + return b.mostRecent - a.mostRecent; + }); + }); + return { + courses, + popular, + watching: watchingArray, + } +} const Courses = () => { + const [Me, setMeProfile] = React.useState({}); const [searchQuery, setSearchQuery] = React.useState(''); const [groups, setGroups] = React.useState([]); + const [popular, setPopular] = React.useState([]); + const [watching, setWatching] = React.useState([]); const [queryTimer, setQueryTimer] = React.useState(0); - useEffect(() => { - API.getCourses('').then((data) => { - setGroups(data.groups || []); - }); + useEffect(async () => { + let Me = await API.getMe(); + setMeProfile(Me); + //API.getCourses('').then((data) => { + // setGroups(data.groups || []); + //}); + let r = await getCourses(Me); + setGroups(r.courses || []); + setPopular(r.popular || []); + setWatching(r.watching || []); }, []) - - const onChangeSearch = query => { setSearchQuery(query); if (queryTimer) clearTimeout(queryTimer); @@ -35,7 +84,10 @@ const Courses = () => { setQueryTimer(timerId); }; const renderProfile = (({ item }) => { - return (); + return (); + }); + const watchingCourse = (({ item }) => { + return (); }); return ( @@ -44,11 +96,30 @@ const Courses = () => { onChangeText={onChangeSearch} value={searchQuery} /> - item._id} - /> + {groups.length ? <> : } + + Continue Watching: + item.profile._id} + /> + Recently Added: + item._id} + /> + Popular: + item._id} + /> + ) } @@ -57,7 +128,5 @@ export default Courses; const styles = StyleSheet.create({ container: { - flex: 1, - backgroundColor: "#edf2f7", }, }); diff --git a/Views/Groups.js b/Views/Groups.js index 62c776c..70f8d58 100644 --- a/Views/Groups.js +++ b/Views/Groups.js @@ -3,6 +3,7 @@ import { Searchbar } from 'react-native-paper'; import { View, ActivityIndicator, StyleSheet, SafeAreaView, FlatList } from 'react-native'; import API from "../API"; import UserName from "../components/UserName"; +import GroupCard from "../components/GroupCard"; import ProfileSmallHeader from '../components/ProfileSmallHeader.js' const Groups = () => { @@ -35,16 +36,19 @@ const Groups = () => { setQueryTimer(timerId); }; const renderProfile = (({ item }) => { - return (); + return (); }); return ( - + item._id} @@ -57,7 +61,6 @@ export default Groups; const styles = StyleSheet.create({ container: { - flex: 1, - backgroundColor: "#edf2f7", + padding: 5, }, }); diff --git a/Views/Search.js b/Views/Search.js index 7659c6a..37e30d0 100644 --- a/Views/Search.js +++ b/Views/Search.js @@ -1,8 +1,9 @@ -import React, {useEffect} from "react"; +import React, { useEffect } from "react"; import { Searchbar } from 'react-native-paper'; -import { View, ActivityIndicator, StyleSheet, SafeAreaView, FlatList } from 'react-native'; +import { View, ScrollView, StyleSheet, SafeAreaView, FlatList } from 'react-native'; import API from "../API"; import UserName from "../components/UserName"; +import ProfileCard from "../components/ProfileCard"; import ProfileSmallHeader from '../components/ProfileSmallHeader.js' const Search = () => { @@ -11,36 +12,40 @@ const Search = () => { const [queryTimer, setQueryTimer] = React.useState(0); useEffect(() => { - API.searchProfiles('').then((data)=>{ + API.searchProfiles('').then((data) => { setProfiles(data.profiles || []); }); }, []) - + const onChangeSearch = query => { setSearchQuery(query); - if(queryTimer) clearTimeout(queryTimer); - let timerId = setTimeout(()=>{ - API.searchProfiles(query).then((data)=>{ + if (queryTimer) clearTimeout(queryTimer); + let timerId = setTimeout(() => { + API.searchProfiles(query).then((data) => { setProfiles(data.profiles || []); }); }, 300); setQueryTimer(timerId); }; const renderProfile = (({ item }) => { - return (); + return (); }); return ( - + item._id} - /> + contentContainerStyle={styles.container} + numColumns={2} + columnWrapperStyle={{justifyContent: "space-evenly"}} + data={profiles} + renderItem={renderProfile} + keyExtractor={item => item._id} + /> ) } @@ -49,7 +54,6 @@ export default Search; const styles = StyleSheet.create({ container: { - flex: 1, - backgroundColor: "#edf2f7", + padding: 5, }, }); diff --git a/components/CourseCard.js b/components/CourseCard.js new file mode 100644 index 0000000..57205e5 --- /dev/null +++ b/components/CourseCard.js @@ -0,0 +1,104 @@ +import React, { useState, useEffect } from 'react'; +import { Text, StyleSheet, View } from 'react-native'; +import Icon from 'react-native-vector-icons/MaterialIcons'; +import { Avatar, Card, Title, Paragraph } from 'react-native-paper'; +import API from './../API.js'; +import { useNavigation } from '@react-navigation/native'; +import AsyncStorage from '@react-native-async-storage/async-storage'; + +const DefaultPhoto = "https://social.emmint.com/uploads/e6f9be6d665dc43417701bf16a90122c.png"; + +const storeName = async (key, value) => { + try { + const jsonValue = JSON.stringify(value) + await AsyncStorage.setItem('Name_' + key, jsonValue) + } catch (e) { + } +} + +const getName = async (key) => { + try { + const value = await AsyncStorage.getItem('Name_' + key) + if (value !== null) { + return JSON.parse(value); + } + } catch (e) { + return [] + } +} + +let CourseCard = ({ profileid, hideIcon, profileObj }) => { + let [profile, setProfile] = useState(profileObj || {}); + const navigation = useNavigation(); + + useEffect(async () => { + if (profileObj._id) return 0; + let cacheProfile = await getName(profileid); + if (cacheProfile && cacheProfile.profile) setProfile(cacheProfile); + let p = await API.getUserProfile(profileid).catch(() => { return {} }); + setProfile(p); + storeName(profileid, p) + }, [profileid]); + + let icon = profile._id ? (!profile.isGroup ? "person-outline" : "group") : ''; + icon = icon === "person-outline" && profile.subscription && profile.subscription > (new Date() - 0) ? "assignment-ind" : icon; + icon = icon === "group" && profile.isCourse ? "subscriptions" : icon; + let photoUrl = profile.data && profile.data.profileImg ? profile.data.profileImg : DefaultPhoto; + + const onPress = () => { + return navigation.navigate('Profile', { profileid: profile._id }) + } + + return ( + + + + <Text style={{ paddingTop: 10 }}> + {!hideIcon ? <Icon name={icon} size={18} /> : <></>} + </Text> + <Text> + {profile.profile && profile.profile.firstName} {profile.profile && profile.profile.lastName} + </Text> + + + {profileObj.profile.description ? profileObj.profile.description : "We are working on this course description, soon to come!"} + + {profile.data ? + + + + By: {profile.data.author ? profile.data.author : 'Working on this'} + + + {profile.data.year ? profile.data.year : 'XXXX'} + + + + {profile.data.duration ? profile.data.duration : '??'} + + + + {profile.data.language} + + + + : <> + } + + + + ); +} + +export default React.memo(CourseCard); + +const styles = StyleSheet.create({ + content: { + margin: 4, + width: 250, + }, + centerItems: { + justifyContent: 'center', + alignItems: 'center', + } +}); diff --git a/components/GroupCard.js b/components/GroupCard.js new file mode 100644 index 0000000..1c92ee7 --- /dev/null +++ b/components/GroupCard.js @@ -0,0 +1,86 @@ +import React, { useState, useEffect } from 'react'; +import { Text, StyleSheet } from 'react-native'; +import Icon from 'react-native-vector-icons/MaterialIcons'; +import { Avatar, Button, Card, Title, Paragraph } from 'react-native-paper'; +import API from './../API.js'; +import { useNavigation } from '@react-navigation/native'; +import AsyncStorage from '@react-native-async-storage/async-storage'; + +const DefaultPhoto = "https://social.emmint.com/uploads/e6f9be6d665dc43417701bf16a90122c.png"; + +const storeName = async (key, value) => { + try { + const jsonValue = JSON.stringify(value) + await AsyncStorage.setItem('Name_' + key, jsonValue) + } catch (e) { + } +} + +const getName = async (key) => { + try { + const value = await AsyncStorage.getItem('Name_' + key) + if (value !== null) { + return JSON.parse(value); + } + } catch (e) { + return [] + } +} + +let ProfileCard = ({ profileid, hideIcon, profileObj }) => { + let [profile, setProfile] = useState(profileObj || {}); + const navigation = useNavigation(); + + useEffect(async () => { + if (profileObj._id) return 0; + let cacheProfile = await getName(profileid); + if (cacheProfile && cacheProfile.profile) setProfile(cacheProfile); + let p = await API.getUserProfile(profileid).catch(() => { return {} }); + setProfile(p); + storeName(profileid, p) + }, [profileid]); + + let icon = profile._id ? (!profile.isGroup ? "person-outline" : "group") : ''; + icon = icon === "person-outline" && profile.subscription && profile.subscription > (new Date() - 0) ? "assignment-ind" : icon; + icon = icon === "group" && profile.isCourse ? "subscriptions" : icon; + let photoUrl = profile.profile.photo ? 'https://social.emmint.com/' + profile.profile.photo : DefaultPhoto; + + const onPress = () => { + return navigation.navigate('Profile', { profileid: profile._id }) + } + + return ( + + + + <Text style={{ paddingTop: 10 }}> + {!hideIcon ? <Icon name={icon} size={18} /> : <></>} + </Text> + <Text> + {profile.profile && profile.profile.firstName} {profile.profile && profile.profile.lastName} + </Text> + + {profileObj.profile.description} + + + + {Object.keys(profile.subscribed).length} + + + + + ); +} + +export default React.memo(ProfileCard); + +const styles = StyleSheet.create({ + content: { + margin: 4, + width: "50%", + }, + centerItems: { + justifyContent: 'center', + alignItems: 'center', + } +}); diff --git a/components/Media.js b/components/Media.js index 5f656a6..836c0da 100644 --- a/components/Media.js +++ b/components/Media.js @@ -3,6 +3,7 @@ import { Text, View, ScrollView, Image, StyleSheet } from 'react-native'; import API from './../API.js'; import VideoPlayer from './VideoPlayer.js'; import VimeoPlayer from './VimeoPlayer.js'; +import { WebView } from 'react-native-webview'; const videoIdF = (content) => { let vimeoTag = content.match(/@vimeo:[0-9]+/); @@ -28,7 +29,7 @@ const imagesTagF = (content) => { const iframeTagF = (content) => { let iframeMatch = content.match(/@iframe:.+\w/g); - if (!iframeMatch) return 0; + if (!iframeMatch) return []; let tag = iframeMatch[0].substring(1); let parts = [tag.substring(1, tag.indexOf(":")), tag.substring(tag.indexOf(":") + 1)]; return parts; @@ -39,14 +40,20 @@ let Media = (props) => { const imagesTag = imagesTagF(props.content); const imageStyle = imagesTag.length == 1 ? styles.image : styles.multipleImage; const videosId = videoIdF(props.content); + const iframeSrc = iframeTagF(props.content) || []; let [videosFiles, setVideosFiles] = useState([]); useEffect(async () => { if (!videosId[1]) return 0; let videoObj = await API.getVideo(videosId[1]); setVideosFiles(videoObj.files || []); }, [props.content]) - const vimeo = videosFiles.length ? : + const video = videosFiles.length ? : (videosId.length ? : <>); + const iframe = iframeSrc.length ? + : <>; return ( @@ -59,7 +66,8 @@ let Media = (props) => { }) } - {vimeo} + {video} + {iframe} ); } @@ -75,5 +83,9 @@ const styles = StyleSheet.create({ width: "49%", aspectRatio: 1, margin: 2, + }, + iframe:{ + width: "100%", + minHeight: 300, } }); diff --git a/components/ProfileCard.js b/components/ProfileCard.js new file mode 100644 index 0000000..6381deb --- /dev/null +++ b/components/ProfileCard.js @@ -0,0 +1,80 @@ +import React, { useState, useEffect } from 'react'; +import { Text, StyleSheet } from 'react-native'; +import Icon from 'react-native-vector-icons/MaterialIcons'; +import { Avatar, Button, Card, Title, Paragraph } from 'react-native-paper'; +import API from './../API.js'; +import { useNavigation } from '@react-navigation/native'; +import AsyncStorage from '@react-native-async-storage/async-storage'; + +const DefaultPhoto = "https://social.emmint.com/uploads/e6f9be6d665dc43417701bf16a90122c.png"; + +const storeName = async (key, value) => { + try { + const jsonValue = JSON.stringify(value) + await AsyncStorage.setItem('Name_' + key, jsonValue) + } catch (e) { + } +} + +const getName = async (key) => { + try { + const value = await AsyncStorage.getItem('Name_' + key) + if (value !== null) { + return JSON.parse(value); + } + } catch (e) { + return [] + } +} + +let ProfileCard = ({ profileid, hideIcon, profileObj }) => { + let [profile, setProfile] = useState(profileObj || {}); + const navigation = useNavigation(); + + useEffect(async () => { + if (profileObj._id) return 0; + let cacheProfile = await getName(profileid); + if (cacheProfile && cacheProfile.profile) setProfile(cacheProfile); + let p = await API.getUserProfile(profileid).catch(() => { return {} }); + setProfile(p); + storeName(profileid, p) + }, [profileid]); + + let icon = profile._id ? (!profile.isGroup ? "person-outline" : "group") : ''; + icon = icon === "person-outline" && profile.subscription && profile.subscription > (new Date() - 0) ? "assignment-ind" : icon; + icon = icon === "group" && profile.isCourse ? "subscriptions" : icon; + let photoUrl = profile.profile.photo ? 'https://social.emmint.com/' + profile.profile.photo : DefaultPhoto; + + const onPress = () => { + return navigation.navigate('Profile', { profileid: profile._id }) + } + + return ( + + + + + <Text> + {profile.profile && profile.profile.firstName} {profile.profile && profile.profile.lastName} + </Text> + + {profileObj.profile.description} + + + + + ); +} + +export default React.memo(ProfileCard); + +const styles = StyleSheet.create({ + content: { + margin: 4, + width: "50%", + }, + centerItems:{ + justifyContent: 'center', + alignItems: 'center', + } +}); diff --git a/components/ProfileHeader.js b/components/ProfileHeader.js index 6d65c4e..caed737 100644 --- a/components/ProfileHeader.js +++ b/components/ProfileHeader.js @@ -6,7 +6,7 @@ import UserName from './UserName'; const DefaultPhoto = "https://social.emmint.com/uploads/e6f9be6d665dc43417701bf16a90122c.png"; const ProfileHeader = ({ profileObj }) => { - let photoUrl = profileObj.profile.photo ? 'https://social.emmint.com/' + profileObj.profile.photo : DefaultPhoto; + let photoUrl = profileObj.profile && profileObj.profile.photo ? 'https://social.emmint.com/' + profileObj.profile.photo : DefaultPhoto; return ( <>