Class

CourseSingleApi

CourseSingleApi()

Single Course Screen Hooks. Instance name: courseSingleApi

Hooks to modify the individual courses’ pages.

Constructor

# new CourseSingleApi()

Example
externalCodeSetup.courseSingleApi.METHOD_NAME

Members

# setShowLessonsCount

Deprecated:
  • Yes

# setShowQuizzesCount

Deprecated:
  • Yes

Methods

# setCourseHeaderDetails(CourseHeaderDetails)

You can use this hook to customize the Course Status component of the LearnDash course details if a course is not yet in progress. For example, you can use this to change the course's preview video, featured image or the "Course Includes" details.

Parameters:
Name Type Description
CourseHeaderDetails React.ComponentType.<CourseHeaderDetailsProps>
Example

Implement default BB component

...

import Icon from "@src/components/Icon";
import AppImageBackground from "@src/components/AppImageBackground";
import { CourseVideo } from "@src/components/Course/CourseStatus";

export const applyCustomCode = externalCodeSetup => {

  externalCodeSetup.courseSingleApi.setCourseHeaderDetails(props => {

    const {
      courseVM,
      global,
      labels,
      colors,
      t,
      lCount,
      tCount,
      qCount,
      navigation,
      learndashCourseParticipants
    } = props;

    const size = 26;

    const shouldShowParticipants = !!+learndashCourseParticipants;

    const showIncludesTitle = lCount !== 0 ||
      tCount !== 0 ||
      qCount !== 0 ||
      courseVM.certificate?.available

    const styles = StyleSheet.create({
      videoContainer: {
        borderTopLeftRadius: 10,
        borderTopRightRadius: 10,
        overflow: "hidden",
        ...(!shouldShowParticipants && {
          borderBottomLeftRadius: 10,
          borderBottomRightRadius: 10
        })
      },

      avatar: {
        width: size + 4,
        height: size + 4,
        borderRadius: size + 4 / 2,
        borderColor: colors.bodyFrontBg,
        borderWidth: 2
      },
      enrolledText: {
        ...global.textAlt,
        left: -8,
        fontSize: 13
      },
      courseDetailsText: {
        ...global.itemText,
        marginBottom: 10,
        marginLeft: 14
      }

    });

    return (
      <>
        <View
          style={styles.videoContainer}
        >
          {!!!courseVM.videoUrl &&
            !!courseVM.featuredUrl && (
              <AppImageBackground
                source={{ uri: courseVM.featuredUrl }}
                style={{ width: "100%", height: 200 }}
              />
            )}
          {typeof courseVM.videoUrl === "string" &&
            courseVM.videoUrl !== "" && (
              <CourseVideo
                url={courseVM.videoUrl}
                feature={courseVM.featuredUrl}
                global={global}
                colors={colors}
                navigation={navigation}
              />
            )}
        </View>

        <View
          style={{
            paddingHorizontal: 16,
            ...(shouldShowParticipants && { paddingVertical: 12 })
          }}
        >
          {courseVM?.members?.length > 0 &&
            shouldShowParticipants && (
              <View style={{ ...global.row }}>
                {courseVM.members
                  .map(x => x.member_rest?.avatar?.thumb || "")
                  .map((url, index) => (
                    <AppAvatar
                      key={url}
                      style={[styles.avatar, { left: index === 0 ? 0 : -10 	 *  index, zIndex: index }]}
                      borderRadius={size / 2}
                      size={size}
                      source={{
                        uri: url
                      }}
                    />
                  ))}
                <Text style={styles.enrolledText}>
                  {courseVM.enrolledMembers > 2
                    ? `+${courseVM.enrolledMembers - 2} ${t("course:enrolled")}`
                    : ""}
                </Text>
              </View>
            )}
          {showIncludesTitle && (
            <Text
              style={[
                global.courseIncludesTitle,
                {
                  marginBottom: 15,
                  marginTop: courseVM?.members?.length > 0 ? 20 : 0
                }
              ]}
            >
              {t("course:includesTitle", { label: labels.course })}
            </Text>
          )}
          {lCount !== 0 && (
            <View style={{ flexDirection: "row" }}>
              <Icon
                webIcon={"IconAndroidGroup"}
                tintColor={colors.descLightTextColor}
                icon={require("@src/assets/img/book.png")}
              />
              <Text
                style={styles.courseDetailsText}
              >
                {lCount === ""
                  ? "  " + labels.lessons
                  : t("course:itemCount", {
                    count: lCount,
                    slabel: labels.lesson,
                    plabel: labels.lessons
                  })}
              </Text>
            </View>
          )}
          {tCount !== 0 && (
            <View style={{ flexDirection: "row" }}>
              <Icon
                webIcon={"IconAndroidGroup"}
                tintColor={colors.descLightTextColor}
                icon={require("@src/assets/img/topic.png")}
              />
              <Text
                style={styles.courseDetailsText}
              >
                {tCount === ""
                  ? "  " + labels.topics
                  : t("course:itemCount", {
                    count: tCount,
                    slabel: labels.topic,
                    plabel: labels.topics
                  })}
              </Text>
            </View>
          )}
          {qCount !== 0 && (
            <View style={{ flexDirection: "row" }}>
              <Icon
                webIcon={"IconAndroidGroup"}
                tintColor={colors.descLightTextColor}
                icon={require("@src/assets/img/quiz.png")}
              />
              <Text
                style={styles.courseDetailsText}
              >
                {qCount === ""
                  ? "  " + labels.quiz
                  : t("course:itemCount", {
                    count: qCount,
                    slabel: labels.quiz,
                    plabel: labels.quizzes
                  })}
              </Text>
            </View>
          )}
          {courseVM.certificate?.available && (
            <View style={{ flexDirection: "row" }}>
              <Icon
                webIcon={"IconAndroidGroup"}
                tintColor={colors.descLightTextColor}
                icon={require("@src/assets/img/small-certificate.png")}
              />
              <Text
                style={styles.courseDetailsText}
              >
                {t("course:certificate", { label: labels.course })}
              </Text>
            </View>
          )}
        </View>
      </>
    );

  })
}

# setCourseMaterialsComponent(CourseMaterialsComponent)

Sets a component that overrides the content added in course materials field found in BuddyBoss site > Learndash LMS > Courses > [Selected Course] > Settings.

Parameters:
Name Type Description
CourseMaterialsComponent React.ComponentType.<CourseMaterialsProps>
Example

Use a different component depending on the logged-in user

//In custom_code/components/CourseMaterial.js

import React, { useState } from 'react';
import { View, Text, Alert, Dimensions } from 'react-native';
import { useSelector } from "react-redux";
import HTML from "react-native-render-html";
import { WebView } from 'react-native-webview';
import {
   documentFilename,
   isInExtensionList,
   htmlHandleClicks
} from "@src/utils";
import { previewDocument } from "@src/utils/previewFiles";
const ent = require("ent");
const DEVICE_WIDTH = Dimensions.get("window").width;

const CourseMaterial = (props) => {

   const { tagsStyles, materialsStyles, baseFontStyle, materials, navigation, t } = props;
   const user = useSelector((state) => state.user.userObject);
   const online = useSelector((state) => state.network.online);

   const [opening, setOpening] = useState(false);
   const [progress, setProgress] = useState(0);

   const onLinkPress = async (evt, url) => {

       //Get auth from available screen props
       const { auth } = navigation.getScreenProps();

       //Checks if extension added in url is supported for preview
       if (isInExtensionList(url)) {
           return await previewDocument({
               url,
               localName: documentFilename(url),
               token: auth.token,
               t,
               beginCallback: () => {
                   setProgress(0);
                   setOpening(true);
               },
               progressCallback: setProgress,
               doneCallback: localFile => {
                   setTimeout(() => {
                       setOpening(false);
                   }, 1000);
               },
               failCallback: () => {
                   setTimeout(() => {
                       setOpening(false);
                   }, 1000);
               }
           });
       }

       if (online) {
           //If online, handle urls using app's helper function
           return htmlHandleClicks(navigation, false)(evt, url);
       }

       Alert.alert(t("course:materialRequiresConnection"));
   };

   return user.id === 1 ?

       //Use a new component for SpecialUserComponent
       <>
           <Text style={{marginBottom: 20}}> Welcome back {user.name}! We prepared the materials for you. </Text>
           <WebView source={{ uri: 'https://link-to-best-materials.com' }} style={{ width: "auto", height: 300 }} />
       </>
       :
       //Use app's default component for RegularUserComponent
       <HTML
           tagsStyles={{ ...tagsStyles, ...materialsStyles }}
           baseFontStyle={baseFontStyle(15)}
           html={ent.decode(materials)}
           imagesMaxWidth={DEVICE_WIDTH - 32}
           onLinkPress={onLinkPress}
       />

}

export default CourseMaterial;

//In custom_code/index.js

...

import CourseMaterial from "./components/CourseMaterial";
export const applyCustomCode = externalCodeSetup => {

   externalCodeSetup.courseSingleApi.setCourseMaterialsComponent((props) => <CourseMaterial {...props} /> )

}

# setCourseTitleComponent(CourseTitleComponent)

You can use this to replace the course title component. For example, you can use this to change the title's font size.

Parameters:
Name Type Description
CourseTitleComponent React.ComponentType.<CourseTitleProps>
Example

Change the title's font size depending on the course title

import Animated from "react-native-reanimated";

export const applyCustomCode = externalCodeSetup => {

  externalCodeSetup.courseSingleApi.setCourseTitleComponent((props) => {

    const { course, global, colors, styles, lightStyle, opacity } = props;

    let fontSize = 34;

    if (course.title === "React Native") {
      fontSize = 20;
    }

    return <View>
      <Animated.Text
        style={[
          global.iosStyleScreenTitle,
         styles.title,
         lightStyle ? { color: "white" } : { color: colors.textColor },
         { opacity, fontSize: fontSize }
       ]}
     >
       {course.title}
     </Animated.Text>
   </View>

 });
}

# setFilterIncomingCourseProps(filterIncomingCourseProps)

Sets the callback function filterIncomingCourseProps used for filtering props that are coming into Courses Single Screen component

Parameters:
Name Type Description
filterIncomingCourseProps CoursePropsFilterCallback
Example

Add a date object to incoming course props

externalCodeSetup.courseSingleApi.setFilterIncomingCourseProps((props) => {
  return {
    ...props,
    date: new Date()
  }
});

# setHeaderAuthorRenderer(renderer)

Sets component for overriding the default course author component.

Parameters:
Name Type Description
renderer React.ComponentType.<HeaderAuthorComponentProps>
Example

Add more details besides the author name

//In custom_code/components/CourseAuthor.js

import React from 'react';
import { View, Text } from 'react-native';
import AppAvatar from "@src/components/AppAvatar";
import AppTouchableOpacity from "@src/components/AppTouchableOpacity";
import { getBestImageVariant } from "@src/utils/CCDataUtil";
import { withUserClickHandler } from "@src/components/hocs/withUserClickHandler"; //Use BuddyBoss HOC for easier navigation

const CourseAuthor = (props) => {

   const { user, global, lightStyle, toUserBasedOnSettings } = props;

   //Able navigate to profile of `user` because of wrapping component with HOC
   const boundCallback = React.useMemo(
	        () => toUserBasedOnSettings.bind(null, null, user),
       [user]
   );

   //Use BuddyBoss getBestImageVariant helper function to get best image size for the component
   const userAvatarUrl = React.useMemo(
       () =>
           !!user
               ? user.avatar_urls
                   ? getBestImageVariant(user.avatar_urls, 96)
                   : user?.avatar?.thumb
               : "",
       [user]
   );

   return <View style={[global.row]}>
       <AppTouchableOpacity onPress={boundCallback}>
           <View>
               {user && (
                   <AppAvatar
                       size={26}
                       name={user.name}
                       source={{
                           uri: userAvatarUrl
                       }}
                   />
               )}
           </View>
       </AppTouchableOpacity>
       <View style={{ marginLeft: 8 }}>
           {user && (

               <>
                   <Text
                       style={[
                           global.itemAuthorName,
                           { marginBottom: 1 },
                           lightStyle ? { color: "white" } : {}
                       ]}
                   >
                       {user?.name}
                   </Text>

                   <Text> {user?.member_rest.user_email} </Text>
               </>

           )}
       </View>
   </View>
}

export default withUserClickHandler(CourseAuthor);

//In custom_code/index.js

...

import CourseAuthor from "./components/CourseAuthor";
export const applyCustomCode = externalCodeSetup => {
   externalCodeSetup.courseSingleApi.setHeaderAuthorRenderer((props) => <CourseAuthor {...props} />)
}

# setIsCategoryTagsHidden(isCategoryTagsHidden)

You can use this to show or hide the category tags in the course single screen.

Parameters:
Name Type Description
isCategoryTagsHidden IsCategoryTagsHiddenCallback
Example
externalCodeSetup.courseSingleApi.setIsCategoryTagsHidden((course) => {
 return false;
})

# setIsCourseDescriptionHidden(isCourseDescriptionHidden)

You can use this hook to show or hide the course description component.

Parameters:
Name Type Description
isCourseDescriptionHidden IsCourseDescriptionHiddenCallback
Example

Hide the course description depending on the course title

 externalCodeSetup.courseSingleApi.setIsCourseDescriptionHidden((course) => {
   if (course.title == "React Native"){
     return true;
   }
   return false;
 })

# setIsCourseStatusHidden(isCourseStatusHidden)

You can use this hook to show or hide the course status component.

Parameters:
Name Type Description
isCourseStatusHidden IsCourseStatusHiddenCallback
Example

Hide the course status component depending on the course title

 externalCodeSetup.courseSingleApi.setIsCourseStatusHidden((course, hasStarted, courseTree) => {
   if (course.title == "React Native"){
     return true;
   }
   return false;
 })

# setTransformCourseActionButtons(transformCourseActionButtons)

You can transform the default course action buttons which are starting, buying or continuing a course by replacing it with your preferred action buttons.

Parameters:
Name Type Description
transformCourseActionButtons TransformCourseActionButtonsCallback
Example

Add more components for course action

import CourseActionButton from "@src/components/Course/CourseActionButton";
export const applyCustomCode = externalCodeSetup => {

 externalCodeSetup.courseSingleApi.setTransformCourseActionButtons((
   CourseActionBtn,
   courseVM,
   t,
   colors,
   global,
   products,
   navigation,
   startCourse,
   continueCourse,
   priceComponentRender) => {

   const Info =
     <View style={{ paddingHorizontal: 20, paddingVertical: 10 }}>
       <Text>To continue the course, tap the button below</Text>
     </View>

   const Redirect =
     <View style={{
       paddingHorizontal: 20,
       paddingVertical: 16,
       flexDirection: "row",
       alignItems: "center",
       justifyContent: "space-between"
     }}>
       <CourseActionButton
         title={"Go to Courses screen"}
         onPress={() => navigation.navigate("CoursesScreen")}
         style={{backgroundColor: "cyan"}}
       />
     </View>
   return [Info, CourseActionBtn, Redirect];
 })

}