Academy
This is the academy navigator we made for the app Algotrading, this template is suitable for other products that have courses/training contents/academies. Enjoy!
Academy stack:
Topics screen
Playlist screen (each topics have several videos)
Playlist screen
Step 1: Create the navigator
//import screens
import { createStackNavigator } from "@react-navigation/stack";
import AcademyListScreen from "../Screens/Academy/AcademyListScreen";
import PlaylistAcademyScreen from "../Screens/Academy/PlaylistScreen";
import VideoScreen from "../Screens/Academy/VideoScreen";
function AcademyNavigation() {
const Stack = createStackNavigator();
const screens = [
{ name: "AcademyList", component: AcademyListScreen },
{ name: "PlaylistAcademy", component: PlaylistAcademyScreen },
{ name: "Video", component: VideoScreen },
];
return (
//use mapping for the stack screens for better and more compact code
<Stack.Navigator>
{screens.map((screen, i) => (
<Stack.Screen
name={screen.name}
options={{ headerShown: false }}
component={screen.component}
key={i}
/>
))}
</Stack.Navigator>
);
}
export default AcademyNavigation;
Step 2: Academy Topics screen
Playlists will be using Flatlist
import { FlatList, SafeAreaView, View, TouchableOpacity} from "react-native";
export default function AcademyListScreen() {
const [data, setData] = useState([]);
const navigation = useNavigation();
const academyList = ["Onboarding", "Basic", "Intermediate", "Advance","Trading Plan"];
//Make card
const renderItem = ({ item }) => {
return (
//on the click event, we also have to pass parameter to that screen
<TouchableOpacity
onPress={() =>
navigation.navigate("PlaylistAcademy", {
item,
})
}
>
<Image
borderRadius="lg"
position="relative"
alignSelf="center"
resizeMode="cover"
alt="List Academy"
w="90%"
h={150}
overflow="hidden"
m={2}
p={2}
source={{
uri: "https://firebasestorage.googleapis.com/v0/b/algotradingid.appspot.com/o/content%2FOnboarding%20-%20Futures%20Trading?alt=media",
}}
/>
<View
style={{
flex: 1,
flexDirection: "row-reverse",
zIndex: 2,
}}
>
<Heading color="white" mt={-10} mr={9}>
{item}
</Heading>
</View>
<Divider />
</TouchableOpacity>
);
};
return (
<Box flex={1} mt={10}>
<Heading m={4}>Academy</Heading>
<FlatList
data={academyList}
initialNumToRender={5}
keyExtractor={(item, index) => `${item + index}`}
renderItem={renderItem}
/>
</Box>
);
}
result:
Step 3: Playlist (or sub-Topics) Screen
//receive parameter we passed from the previous screen
//recieve as { route }, then log in on the console to see the data structure
const PlaylistScreen = ({ route }) => {
const [dataPlaylist, setDataPlaylist] = useState([]);
const [dataSpeakers, setDataSpeakers] = useState([]);
const [refreshing, setRefreshing] = useState(false);
const navigation = useNavigation();
const { loading, loadingShow, loadingClose } = useAppContext();
const [isPreloading, setIsPreloading] = useState(false)
console.log("route : ", route)
// get data speakers
const getAllSpeakers = async () =>{
let arr = [];
const ref = collection(db, "speakers");
const q = query(ref)
const querySnapshot = await getDocs(q);
querySnapshot.forEach((doc) => {
arr.unshift({id:doc.id, data:doc.data()});
});
setDataSpeakers(arr);
}
// get data contents / videos / courses / whatever you wanna call it
const getData = async () => {
let arr = [];
loadingShow();
const ref = collection(db, "content");
const q = query(
ref,
where("tags", "array-contains", String(route.params.item).toLowerCase())
);
const querySnapshot = await getDocs(q);
querySnapshot.forEach((doc) => {
arr.unshift(doc.data());
});
console.log(`this is the ${String(route.params.item).toLowerCase()} playlist: `, arr);
setDataPlaylist(arr);
loadingClose();
};
// call the getData and getSpeakers whenever the { route } changes
useEffect(() => {
getData();
getAllSpeakers();
}, [route]);
const renderItem = ({ item }) => (
// pass data to the next screen (item and dataSpeakers)
<>
<TouchableOpacity
onPress={() =>
navigation.navigate("Video", {
item,
dataSpeakers
})
}
>
<Box w="90%" alignSelf="center" h={100} overflow="hidden" m={2} p={2} >
<Flex flexDir="row" h="100%">
<Box h="100%" p={1} position="relative">
{isPreloading && <Skeleton h="72" w="148" position="absolute" zIndex={2} speed={4}/>}
<Image
h="70"
w="140"
source={{ uri: item.thumbnail }}
onLoadStart={()=>setIsPreloading(true)}
onLoadEnd={()=>setIsPreloading(false)}
alt={item.title}
/>
</Box>
<Box h="100%" flex={1} p={1}>
<Heading size="sm">{item.title}</Heading>
<Text>{item.description}</Text>
</Box>
</Flex>
<Divider />
</Box>
</TouchableOpacity>
</>
);
return (
<Box flex={1} mt={10}>
<HStack mt={5}>
<Ionicons name="arrow-back" size={28} color="black" style={{marginHorizontal:10}} onPress={()=>navigation.goBack()}/>
<Heading backgroundColor='red.800'>{route.params.item} Playlists</Heading>
</HStack>
<FlatList
style={{ height: "100%" }}
data={dataPlaylist}
keyExtractor={(item, index) => `${item + index}`}
renderItem={renderItem}
refreshing={loading}
onRefresh={getData}
ListEmptyComponent={null}
/>
</Box>
);
};
export default PlaylistScreen;
Result:
Step 4 : Video Screen
const VideoScreen = ({ route }) => {
const video = React.useRef(null);
const [status, setStatus] = React.useState({});
const [isPreloading, setIsPreloading] = React.useState(false);
const navigation = useNavigation();
const dataa = route.params.item;
return (
<SafeAreaView flex={1}>
<ScrollView>
{/* <Heading size="sm" fontWeight={600}>
This is {dataa.title}
</Heading>
<Pressable onPress={()=>navigation.goBack()}>
<Ionicons name="arrow-back" size={24} color="black" margin={20} />
</Pressable> */}
<HStack my={5}>
<Ionicons name="arrow-back" size={24} color="black" style={{marginHorizontal:10}} onPress={()=>navigation.goBack()}/>
<Heading size="md" backgroundColor='red.800'>{dataa.title}</Heading>
</HStack>
<Box position="relative">
{isPreloading &&
<Skeleton h="200" position="absolute" zIndex={2} speed={6}/>
}
//this is the video player:
<Video
onLoadStart={()=>setIsPreloading(true)}
onReadyForDisplay={()=>setIsPreloading(false)}
shouldPlay={true}
ref={video}
style={styles.video}
source={{
uri: `https://customer-2q11tdtmqhtobay5.cloudflarestream.com/${dataa.video}/manifest/video.m3u8`,
}}
useNativeControls
resizeMode="contain"
isLooping
onPlaybackStatusUpdate={(status) => setStatus(() => !status)}
/>
</Box>
<Box m={10}>
<Heading>{dataa.title}</Heading>
<Flex flexDirection="row">
{dataa.tags && dataa.tags.map((tag, index)=>(
<Badge colorScheme="info" borderRadius={7} key={index} mx={1}>{tag}</Badge>
))}
</Flex>
<Text textAlign="justify">
{dataa.description}
</Text>
</Box>
</ScrollView>
</SafeAreaView>
);
};
export default VideoScreen;
const styles = StyleSheet.create({
container: {
flex: 1,
},
video: {
width: "100%",
height: 200,
},
});
Result:
Last updated
Was this helpful?