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:

  1. Topics screen

  2. Playlist screen (each topics have several videos)

  3. 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?