Source

externalCode/courseSingle.js

import * as React from "react";

/**
 * @typedef {Function} CoursePropsFilterCallback
 * @param {Object} oldProps Course props
 * @return {Object}  New props
 */

/**
 * @typedef {Object} CourseMaterialsProps
 * @property { Object } global App global style
 * @property { Object } colors App colors
 * @property { TranslationFunction } t
 * @property { HTML } materials Contents of course materials field
 * @property { RNStyleValue } tagsStyles Defined styles for different tags
 * @property { RNStyleValue } materialsStyles Defined styles for course materials
 * @property { Function } baseFontStyle Function to set a font style
 * @property {NavigationService} navigation
 * @property {Function} onLinkPress Helper function that handles link press for HTML component
 */

/**
 * @typedef {Object} HeaderAuthorComponentProps
 * @property { Object } user Author details
 * @property { Boolean } lightStyle Returns true if course is using cover image
 * @property { Object } global App global style
 * @property { Number } userId User id of course author
 * @property { Function } onUserProfilePress
 * @property { Function } loadingInProgress
 */

/**
 * @typedef {Function} TransformCourseActionButtonsCallback
 * @property {React.ComponentType} buttons Course action button
 * @property {CourseViewModel} course
 * @property {TranslationFunction} t
 * @property {Object} colors App colors
 * @property {Object} global App global style
 * @property {Array<Object>} products Products for IAP
 * @property {NavigationService} navigation
 * @property {Function} startCourse Function to start a course
 * @property {Function} continueCourse Function to continue a course
 * @property {React.ComponentType} continueCourse Renders pricing for the course
 */

/**
 * @typedef {Object} IsCourseStatusHiddenCallback
 * @property {CourseViewModel} course
 * @property {Boolean} hasStarted Returns `true` if course progress has started
 * @property {Object} courseTree Course lessons, topics, quizzes
 */

/**
 * @typedef {Object} IsCategoryTagsHiddenCallback
 * @property {CourseViewModel} course
 */
/**
 * @typedef {Object} IsCourseDescriptionHiddenCallback
 * @property {CourseViewModel} course
 */

/**
 * @typedef {Object} CourseTitleProps
 * @property { CourseViewModel } course
 * @property { Object } global App global style
 * @property {Object} colors App colors
 * @property { Boolean } lightStyle Returns true if course is using cover image
 * @property { Object } opacity Returns an object generated by React Native Animated
 */

/**
 * @typedef {Object} CourseHeaderDetailsProps
 * @property { CourseViewModel } courseVM
 * @property { Object } global App global style
 * @property {Object} colors App colors
 * @property {Object} labels Learndash labels
 * @property {TranslationFunction} t
 * @property {Number} lCount Lessons count
 * @property {Number} t Learn Topics count
 * @property {Number} qCount Quizzes count
 * @property {NavigationService} navigation
 * @property {String} learndashCourseParticipants Can be used to determine if participants should be shown in the component
 */

/**
 * @typedef {Object} CourseHeaderRightProps
 * @property {Object} global App global style
 * @property {Object} colors App colors
 * @property {TranslationFunction} t
 * @property {CourseViewModel} courseVM
 * @property {Boolean} isShowingOpenCourseForGuestUser Returns `true` if course should be open for guest user
 * @property {String} contentStyle Can be used to indicate what DownloadStatusIndicator component style should use
 */

/**
 * @class
 * Single Course Screen Hooks.
 * Instance name: courseSingleApi
  
   Hooks to modify the individual courses’ pages.
 * @example
 * externalCodeSetup.courseSingleApi.METHOD_NAME
 */
export class CourseSingleApi {
	/**
	 * @private
	 * @property {React.ComponentType<any>} headerAuthorRenderer
	 */
	headerAuthorRenderer = null;

	/**
	 * @private
	 * @property {boolean} showLessonsCount
	 */
	showLessonsCount = true;
	/**
	 * @private
	 * @property {boolean} showQuizzesCount
	 */
	showQuizzesCount = true;

	/**
	 * Sets component for overriding the default course author component.
	 * @method
	 * @param {React.ComponentType<HeaderAuthorComponentProps>} renderer
	 * @example <caption>Add more details besides the author name</caption>
	 *
	 * //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} />)
	 * }
	 */
	setHeaderAuthorRenderer = renderer => {
		this.headerAuthorRenderer = renderer;
	};

	/**
	 * @private
	 * @property {CoursePropsFilterCallback} filterIncomingCourseProps
	 */
	filterIncomingCourseProps = props => props;

	/**
	 * Sets the callback function `filterIncomingCourseProps` used for filtering props that are coming into Courses Single Screen component
	 * @param {CoursePropsFilterCallback} filterIncomingCourseProps
	 * @method
	 * @example <caption>Add a date object to incoming course props</caption>
	 * externalCodeSetup.courseSingleApi.setFilterIncomingCourseProps((props) => {
	 *   return {
	 *     ...props,
	 *     date: new Date()
	 *   }
	 * });
	 */
	setFilterIncomingCourseProps = filterIncomingCourseProps => {
		this.filterIncomingCourseProps = filterIncomingCourseProps;
	};

	isCategoryTagsHidden = course => false;

	/**
	 * You can use this to show or hide the category tags in the course single screen.
	 * @param {IsCategoryTagsHiddenCallback} isCategoryTagsHidden
	 * @method
	 * @example
	 *
	 * externalCodeSetup.courseSingleApi.setIsCategoryTagsHidden((course) => {
	 *  return false;
	 * })
	 */
	setIsCategoryTagsHidden = isCategoryTagsHidden => {
		this.isCategoryTagsHidden = isCategoryTagsHidden;
	};

	/**
	 * @deprecated
	 */
	// show number of lessons on course single screen beside the lesson title
	setShowLessonsCount = () => (this.showLessonsCount = true);
	/**
	 * @deprecated
	 */
	// show number of quizzes on course single screen beside the quizzes title
	setShowQuizzesCount = () => (this.showQuizzesCount = true);

	// Course Materials
	CourseMaterialsComponent = null;
	/**
	 * Sets a component that overrides the content added in course materials field found in BuddyBoss site > Learndash LMS > Courses > [Selected Course] > Settings.
	 * @method
	 * @param {React.ComponentType<CourseMaterialsProps>} CourseMaterialsComponent
	 * @example <caption>Use a different component depending on the logged-in user</caption>
	 *
	 * //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");
	 * import { useScreenProps } from "@src/utils/ScreenPropsProvider";
	 * 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 { auth } = useScreenProps(['auth']);
	 *
	 *    const [opening, setOpening] = useState(false);
	 *    const [progress, setProgress] = useState(0);
	 *
	 *    const onLinkPress = async (evt, url) => {
	 *
	 *        //Get auth from available screen props
	 *
	 *        //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} /> )
	 *
	 * }
	 */
	setCourseMaterialsComponent = CourseMaterialsComponent => {
		this.CourseMaterialsComponent = CourseMaterialsComponent;
	};

	transformCourseActionButtons = (
		buttons,
		course,
		t,
		colors,
		global,
		products,
		navigation,
		startCoure,
		continueCourse
	) => buttons;

	/**
	 * You can transform the default course action buttons which are starting, buying or continuing a course by replacing it with your preferred action buttons.
	 * @param {TransformCourseActionButtonsCallback} transformCourseActionButtons
	 * @method
	 * @example <caption> Add more components for course action </caption>
	 *
	 * 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];
	 *  })
	 *
	 * }
	 */

	setTransformCourseActionButtons = transformCourseActionButtons => {
		this.transformCourseActionButtons = transformCourseActionButtons;
	};

	isCourseStatusHidden = (course, hasStarted, courseTree) => false;

	/**
	 * You can use this hook to show or hide the course status component.
	 * @param {IsCourseStatusHiddenCallback} isCourseStatusHidden
	 * @method
	 * @example <caption> Hide the course status component depending on the course title </caption>
	 *  externalCodeSetup.courseSingleApi.setIsCourseStatusHidden((course, hasStarted, courseTree) => {
	 *    if (course.title == "React Native"){
	 *      return true;
	 *    }
	 *    return false;
	 *  })
	 */
	setIsCourseStatusHidden = isCourseStatusHidden => {
		this.isCourseStatusHidden = isCourseStatusHidden;
	};

	isCourseDescriptionHidden = course => false;

	/**
	 * You can use this hook to show or hide the course description component.
	 * @param {IsCourseDescriptionHiddenCallback} isCourseDescriptionHidden
	 * @method
	 * @example <caption> Hide the course description depending on the course title </caption>
	 *  externalCodeSetup.courseSingleApi.setIsCourseDescriptionHidden((course) => {
	 *    if (course.title == "React Native"){
	 *      return true;
	 *    }
	 *    return false;
	 *  })
	 */
	setIsCourseDescriptionHidden = isCourseDescriptionHidden => {
		this.isCourseDescriptionHidden = isCourseDescriptionHidden;
	};

	CourseTitleComponent = null;

	/**
	 * You can use this to replace the course title component.
	 * For example, you can use this to change the title's font size.
	 * @method
	 * @param {React.ComponentType<CourseTitleProps>} CourseTitleComponent
	 * @example <caption>Change the title's font size depending on the course title</caption>
	 *
	 * 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>
	 *
	 *  });
	 * }
	 */
	setCourseTitleComponent = CourseTitleComponent => {
		this.CourseTitleComponent = CourseTitleComponent;
	};

	CourseHeaderDetails = null;

	/**
	 * 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.
	 * @method
	 * @param {React.ComponentType<CourseHeaderDetailsProps>} CourseHeaderDetails
	 * @example <caption> Implement default BB component </caption>
	 *
	 * ...
	 *
	 * 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>
	 *       </>
	 *     );
	 *
	 *   })
	 * }
	 */
	setCourseHeaderDetails = CourseHeaderDetails => {
		this.CourseHeaderDetails = CourseHeaderDetails;
	};

	HeaderRightComponent = null;

	/**
	 * You can use this hook to customize the component on the top right corner of the CourseSingleScreen which the download icon usually occupies.
	 * @method
	 * @param {React.ComponentType<CourseHeaderRightProps>} HeaderRightComponent
	 * @example
	 * ...
	 *
	 * import AuthWrapper from "@src/components/AuthWrapper";
	 * import DownloadStatusIndicator from "@src/components/Downloader/DownloadStatusIndicator";
	 * import StatusBarWrapper from "@src/utils/StatusBarWrapper";
	 * import { DownloadTypes } from "@src/services/enums/downloads";
	 *
	 * export const applyCustomCode = (externalCodeSetup: any) => {
	 *
	 *     externalCodeSetup.courseSingleApi.setHeaderRightComponent(({
	 *         global,
	 *         colors,
	 *         t,
	 *         courseVM,
	 *         isShowingOpenCourseForGuestUser,
	 *         contentStyle
	 *     }) => (
	 *         courseVM.hasAccess &&
	 *         !courseVM.offlineDisabled && (
	 *             <AuthWrapper
	 *                 actionOnGuestLogin={
	 *                     isShowingOpenCourseForGuestUser
	 *                         ? null
	 *                         : "showAuthScreen"
	 *                 }
	 *             >
	 *                 <DownloadStatusIndicator
	 *                     lightStyle={
	 *                         contentStyle === StatusBarWrapper.lightStyle
	 *                     }
	 *                     postId={courseVM.id}
	 *                     postType={DownloadTypes.course}
	 *                     colors={colors}
	 *                     global={global}
	 *                     size={30}
	 *                     t={t}
	 *                     iconColor={colors.linkColor}
	 *                     containerStyle={{
	 *                         marginLeft: 15
	 *                     }}
	 *                 />
	 *             </AuthWrapper>
	 *         )
	 *
	 *     ))
	 * }
	 */
	setHeaderRightComponent = HeaderRightComponent => {
		this.HeaderRightComponent = HeaderRightComponent;
	};

	CourseContentTitleComponent = null;
	/**
	 * You can use this to replace the course content title component.
	 * @method
	 * @property {CourseViewModel} course
	 * @property {Object} global App global style
	 * @property {Object} colors App colors
	 * @property {Object} labels Learndash labels
	 * @property {TranslationFunction} t
	 * @example <caption>Replace the course content title component</caption>
	 *
	 * import { Text } from "react-native";
	 *
	 * export const applyCustomCode = externalCodeSetup => {
	 *
	 *   externalCodeSetup.courseSingleApi.setCourseContentTitleComponent((props) => {
	 *
	 *     return (
	 *      <Text style={{fontSize: 24}}> {`${course.title} Chapters`} </Text>
	 *   )
	 *  });
	 * }
	 */
	setCourseContentTitleComponent = CourseContentTitleComponent => {
		this.CourseContentTitleComponent = CourseContentTitleComponent;
	};

	CourseDescriptionComponent = null;
	/**
	 * You can use this to replace the course description component.
	 * @method
	 * @property {CourseViewModel} course
	 * @property {Object} global App global style
	 * @property {Object} colors App colors
	 * @property {Object} labels Learndash labels
	 * @property {TranslationFunction} t
	 * @example <caption>Replace the course description component</caption>
	 *
	 * import { Text } from "react-native";
	 *
	 * export const applyCustomCode = externalCodeSetup => {
	 *
	 *   externalCodeSetup.courseSingleApi.setCourseDescriptionComponent((props) => {
	 *
	 *     return (
	 *      <Text style={{fontSize: 24}}> {`About ${course.title}`} </Text>
	 *   )
	 *  });
	 * }
	 */
	setCourseDescriptionComponent = CourseDescriptionComponent => {
		this.CourseDescriptionComponent = CourseDescriptionComponent;
	};

	LessonProgressStepsComponent = null;
	/**
	 * You can use this to replace the course lesson progress step component.
	 * @method
	 * @property {CourseViewModel} course
	 * @property {Object} item Object containing data such like lesson id, title, topics etc
	 * @property {Object} global App global style
	 * @property {Object} colors App colors
	 * @property {TranslationFunction} t
	 * @property {Number} count Number indicating total progress
	 * @property {Number} completedSteps Number indicating progress completed
	 * @example <caption>Replace the course lesson progress steps component</caption>
	 *
	 * import { Text } from "react-native";
	 *
	 * export const applyCustomCode = externalCodeSetup => {
	 *
	 *   externalCodeSetup.courseSingleApi.setLessonProgressStepsComponent((props) => {
	 *
	 *   const { count, completedSteps } = props
	 *   const percentCompleted = Math.round((completedSteps/count) * 100);
	 *
	 *     return (
	 *      <Text style={{ color: percentCompleted === 100 ? 'green' : 'black'}}>
	 *       {`${percentCompleted}%`}
	 *     </Text>
	 *   )
	 *  });
	 * }
	 */
	setLessonProgressStepsComponent = LessonProgressStepsComponent => {
		this.LessonProgressStepsComponent = LessonProgressStepsComponent;
	};
}