Source

externalCode/quiz.ts

import React from "react";
import {
	Course,
	NavigationService,
	TQuizViewModel,
	TTranslationFunction
} from "./types";
import {NavigationProp} from "@react-navigation/native";

export const API_NAME = "quizApi";

/**
 * QuestionClosedRenderMode
 */
type QuestionClosedRenderMode = "webview" | "htmlparser";

/**
 * OverrideQuestionHTMLWrapperCallback
 */
type OverrideQuestionHTMLWrapperCallback = {
	/**
	 * Default HTML wrapper
	 */
	HTML: string,
	/**
	 * HTML that contains the input element
	 */
	inputHTML: string,
	/**
	 * CSS passed to webview by default
	 */
	css: string
};

/**
 * QuizPrevNextComponentProps
 */
type QuizPrevNextComponentProps = {
	/**
	 * Function to execute if previous or next button is a quiz
	 */
	onQuizClick: Function,
	/**
	 * Function to execute if previous or next button is a lesson
	 */
	onLessonClick: Function,
	/**
	 * Function to execute if previous or next button is a topic
	 */
	onTopicClick: Function,
	/**
	 * App global style
	 */
	global: Record<any, any>,
	/**
	 * App colors
	 */
	colors: Record<any, any>,
	t: TTranslationFunction,
	/**
	 * Data about previous lesson/topic/quiz
	 */
	prevObject: Record<any, any>,
	/**
	 * Data about next lesson/topic/quiz
	 */
	nextObject: Record<any, any>,
	/**
	 * Course id of lesson/topic/quiz
	 */
	courseId: number,
	/**
	 * Shows an alert with information that next object is locked
	 */
	nextLockedAlert: Function,
	/**
	 * Props which can be passed to an AuthWrapper
	 */
	headerRightAuthWrapperProps: Record<any, any>
};

/**
 * QuizTitleComponentProps
 */
type QuizTitleComponentProps = {
	quiz: TQuizViewModel,
	currentSwiperPosition: number,
	/**
	 * App global style
	 */
	global: Record<any, any>,
	/**
	 * Learndash labels
	 */
	labels: Record<any, any>,

	/**
	 * Current questions in the quiz
	 */
	questions: Record<any, any>[],
	quizOrder: number,
	quizTotalCount: number,
	/**
	 * App colors
	 */
	colors: Record<any, any>,
	/**
	 * Helper function which can be called to set header height
	 */
	setHeaderHeight: Function,
	/**
	 * Returns `false` if quiz doesn't have a timer set. Will return a component if timer for the quiz is set.
	 */
	renderQuizTimer: boolean | React.ComponentType,
	t: TTranslationFunction,
	navigation: NavigationService,
	/**
	 * Function to execute if previous or next button is a quiz
	 */
	onQuizClick: Function,
	/**
	 * Function to execute if previous or next button is a lesson
	 */
	onLessonClick: Function,
	/**
	 * Function to execute if previous or next button is a topic
	 */
	onTopicClick: Function,
	/**
	 * Course id of lesson/topic/quiz
	 */
	courseId: number,
	/**
	 * Data about previous lesson/topic/quiz
	 */
	prevObject: Record<any, any>,
	/**
	 * Data about next lesson/topic/quiz
	 */
	nextObject: Record<any, any>,
	course: Course,
	/**
	 * Shows an alert with information that next object is locked
	 */
	nextLockedAlert: Function,
	/**
	 * Returns `true` if screen is currently showing the results of the quiz
	 */
	isResultsVisible: boolean,
	/**
	 * Returns default back button component
	 */
	backToCourse: React.ComponentType,
	/**
	 * Returns `true` if question numbering should be hidden
	 */
	hideQuestionNumbering: boolean,
	/**
	 * Returns `true` if prev/next buttons should be hidden
	 */
	hidePrevNext: boolean,
	/**
	 * Returns `false` if hidePrevNext is `true`. Will return buttons which can navigate through previous and next screens if hidePrevNext is `false`
	 */
	prevNext: boolean | React.ComponentType
};

/**
 * QuizScreenHeaderProps
 */
type QuizScreenHeaderProps = {
	quiz: TQuizViewModel,
	currentSwiperPosition: number,
	/**
	 * App global style
	 */
	global: Record<any, any>,
	/**
	 * Learndash labels
	 */
	labels: Record<any, any>,

	/**
	 * Current questions in the quiz
	 */
	questions: Record<any, any>[],
	quizOrder: number,
	quizTotalCount: number,
	/**
	 * App colors
	 */
	colors: Record<any, any>,
	/**
	 * Helper function which can be called to set header height
	 */
	setHeaderHeight: Function,
	/**
	 * Returns `false` if quiz doesn't have a timer set. Will return a component if timer for the quiz is set.
	 */
	renderQuizTimer: boolean | React.ComponentType,
	t: TTranslationFunction,
	navigation: NavigationService,
	/**
	 * Function to execute if previous or next button is a quiz
	 */
	onQuizClick: Function,
	/**
	 * Function to execute if previous or next button is a lesson
	 */
	onLessonClick: Function,
	/**
	 * Function to execute if previous or next button is a topic
	 */
	onTopicClick: Function,
	/**
	 * Course id of lesson/topic/quiz
	 */
	courseId: number,
	/**
	 * Data about previous lesson/topic/quiz
	 */
	prevObject: Record<any, any>,
	/**
	 * Data about next lesson/topic/quiz
	 */
	nextObject: Record<any, any>,
	course: Course,
	/**
	 * Shows an alert with information that next object is locked
	 */
	nextLockedAlert: Function,
	/**
	 * Returns `true` if screen is currently showing the results of the quiz
	 */
	isResultsVisible: boolean,
	/**
	 * Returns default back button component
	 */
	backToCourse: React.ComponentType,
	/**
	 * Returns `true` if question numbering should be hidden
	 */
	hideQuestionNumbering: boolean,
	/**
	 * Returns `true` if prev/next buttons should be hidden
	 */
	hidePrevNext: boolean,
	/**
	 * Returns `false` if hidePrevNext is `true`. Will return buttons which can navigate through previous and next screens if hidePrevNext is `false`
	 */
	prevNext: boolean | React.ComponentType,
	/**
	 * Default styling for left section of the header
	 */
	headerLeftStyle: Record<any, any>,
	/**
	 * Default styling of lesson header
	 */
	style: Record<any, any>
};

/**
 * QuizMaterialsComponentProps
 */
type QuizMaterialsComponentProps = {
	/**
	 * App global style
	 */
	global: Record<any, any>,
	/**
	 * App colors
	 */
	colors: Record<any, any>,

	t: TTranslationFunction,

	/**
	 * Contents of course materials field
	 */
	materials: string,
	/**
	 * Default styles for different HTML tags
	 */
	tagsStyles: Record<any, any>,
	/**
	 * Default styles for course materials
	 */
	materialsStyles: Record<any, any>,
	/**
	 * Function to set a font style
	 */
	baseFontStyle: Function,
	navigation: NavigationService,
	/**
	 * Handles link press for the HTML component
	 */
	onLinkPress: Function
};

/**
 * QuizStartButtonProps
 */
type QuizStartButtonProps = {
	/**
	 * Returns `true` if quiz start button should be visible.
	 */
	showStart: boolean,
	/**
	 * App global style
	 */
	global: Record<any, any>,
	/**
	 * Default styles for the button.
	 */
	styles: Record<any, any>,
	quiz: TQuizViewModel,
	/**
	 * Returns `true` if quiz is currently loading.
	 */
	quizStartLoading: boolean,
	/**
	 * Checks form and sets state as invalid if applicable.
	 */
	checkForm: Function,
	/**
	 * Starts the quiz.
	 */
	startQuiz: Function,

	t: TTranslationFunction,
	/**
	 * Learndash labels
	 */
	labels: Record<any, any>,
	/**
	 * Returns `true` if quiz can be resumed.
	 */
	isResumeQuiz: boolean | null
};

/**
 * QWebViewContentComponentProps
 */
type QWebViewContentComponentProps = {
	/**
	 * Returns `true` if device is connected to an internet network
	 */
	online: boolean,
	t: TTranslationFunction,
	/**
	 * The default function used for determining how to handle webview requests.
	 */
	onShouldStartLoadWithRequest: Function,
	/**
	 * Default height
	 */
	height: number,
	/**
	 * Contains data of the web site to be loaded in the webview
	 */
	source: Record<any, any>,
	/**
	 * App global style
	 */
	global: Record<any, any>,
	/**
	 * App colors
	 */
	colors: Record<any, any>,
	/**
	 * Modal which shows up when the "Read More" component is pressed
	 */
	ModalHeaderComponent: React.FC
};

/**
 * QuizOfflineComponentProps
 */
type QuizOfflineComponentProps = {
	/**
	 * App global style
	 */
	global: Record<any, any>,

	t: TTranslationFunction,
	/**
	 * Default wrapper style
	 */
	wrapperCompsStyle: Record<any, any>,
	/**
	 * Renders the navigation component
	 */
	renderNav: Function,
	/**
	 * Default styles for the offline component
	 */
	styles: Record<any, any>
};

/**
 * QuizTimerProps
 */
type QuizTimerProps = {
	/**
	 * Returns `true` if timer should be visible
	 */
	showTimer: boolean,
	item: TQuizViewModel,
	/**
	 * App global style
	 */
	global: Record<any, any>,
	/**
	 * App colors
	 */
	colors: Record<any, any>,
	styles: Record<any, any>,
	timerRef: Record<any, any>,
	/**
	 * Updates the timer value
	 */
	onTimePassed: Function,
	/**
	 * Submits the quiz
	 */
	onTimeOver: Function
};

/**
 * QuestionOverviewButtonsProps
 */
type QuestionOverviewButtonsProps = {
	style: Record<any, any>,
	/**
	 * App global style
	 */
	global: Record<any, any>,
	t: TTranslationFunction,
	/**
	 * Learndash labels
	 */
	labels: Record<any, any>,
	/**
	 * Marks a question for review
	 */
	onReviewPress: Function,
	/**
	 * Shows summary of the quiz
	 */
	onSummaryPress: Function,
	/**
	 * Returns `true` if the summary button should be disabled
	 */
	disableSummaryButton: boolean
};

/**
 * @class
 * Quiz Hooks.
 * Instance name: quizApi
  
   You can use these hooks to customize the quiz questions for your app.
 * @example
 * externalCodeSetup.quizApi.METHOD_NAME
 */
export class QuizApi {
	questionClosedRenderMode: "webview" | "htmlparser" = "htmlparser";

	/**
	 * It overrides the Closed Question type render mode. The default mode is `htmlparser`. If the question is not rendering correctly, you can try setting it to `webview` mode.
	 * @method
	 * @param {QuestionClosedRenderMode} questionClosedRenderMode Available values: `webview` | `htmlparser`
	 * @example
	 * externalCodeSetup.quizApi.setQuestionClosedRenderMode("webview")
	 */
	setQuestionClosedRenderMode = (
		questionClosedRenderMode: "webview" | "htmlparser"
	) => {
		this.questionClosedRenderMode = questionClosedRenderMode;
	};

	wrapQuestionHtmlFilter: (
		HTML: string,
		inputHTML: string,
		css: string
	) => string = (HTML, inputHTML, css) => HTML;

	/**
	 * We use an HTML wrapper for "Fill in the blank" questions to accept input answers.
	 * You can use this to change the HTML output.
	 * @method
	 * @param {OverrideQuestionHTMLWrapperCallback} wrapQuestionHtmlFilter
	 * @example <caption>Add additional details below the input field</caption>
	 * externalCodeSetup.quizApi.setWrapQuestionHtmlFilter((HTMLWrapper, inputHtml, css) => {
	 *
	 *    const disableZoom = true;
	 *    const myNewHtml =
	 *      `
	 *   		<!DOCTYPE html>
	 *   		<html>
	 *    			<head>
	 *   				<title>Topic Content</title>
	 *   				<meta http-equiv="content-type" content="text/html; charset=utf-8">
	 *   				<meta name="viewport" content="width=device-width, initial-scale=1 ${disableZoom ? "maximum-scale=1.0" : ""} ">
	 *   				<style type="text/css">
	 *   				${css}
	 *   				.content {
	 *   					width: 100%;
	 *   					overflow: hidden;
	 *   					padding-bottom: 4px;
	 *   				}
	 *   				</style>
	 *   			</head>
	 *   			<body>
	 *   				<div class="content">
	 *   				` +
	 *        inputHtml +
	 *      `
	 *        <p> Please use UPPERCASE characters only</p>
	 *   				</div>
	 *   			</body>
	 *   		</html>
	 *   	`;
	 *    return myNewHtml;
	 *  })
	 */
	setWrapQuestionHtmlFilter = (
		wrapQuestionHtmlFilter: (
			HTML: string,
			inputHTML: string,
			css: string
		) => string
	) => {
		this.wrapQuestionHtmlFilter = wrapQuestionHtmlFilter;
	};

	PrevNextComponent: React.ComponentType<
		QuizPrevNextComponentProps
	> | null = null;

	/**
	 * You can use this to replace the previous and next buttons on the quiz screen.
	 * @method
	 * @param {React.ComponentType<QuizPrevNextComponentProps>} PrevNextComponent
	 * @example <caption> Change colors of the default previous and next buttons </caption>
	 *
	 * //In custom_code/components/PrevNext.js
	 *
	 * import React from "react";
	 * import { Text, View, StyleSheet } from "react-native";
	 * import AppTouchableOpacity from "@src/components/AppTouchableOpacity";
	 * import Icon from "@src/components/Icon";
	 * import { shadeColor } from "@src/utils";
	 *
	 * export const onObjectClick = (
	 *    object,
	 *    onQuizClick,
	 *    onTopicClick,
	 *    onLessonClick
	 * ) => {
	 *    if (!!!object) {
	 *        return false;
	 *    }
	 *    switch (object.type) {
	 *        case "quiz":
	 *            onQuizClick(object.parentType, object.parent)(object);
	 *            break;
	 *        case "topic":
	 *            onTopicClick(object, object.parent);
	 *            break;
	 *        case "lesson":
	 *            onLessonClick(object);
	 *            break;
	 *    }
	 * };
	 *
	 * const PrevNext = ({
	 *    global,
	 *    colors,
	 *    t,
	 *    prevObject,
	 *    nextObject,
	 *    courseId,
	 *    onQuizClick,
	 *    onLessonClick,
	 *    onTopicClick,
	 *    nextLockedAlert
	 * }) => {
	 *
	 *    return (
	 *        <View style={[global.row]}>
	 *            <AppTouchableOpacity
	 *                style={[
	 *                    global.wrappedButton,
	 *                    global.wrappedTextButton,
	 *                    { marginRight: 4, backgroundColor: "purple" }
	 *                ]}
	 *                onPress={() => {
	 *                    if (prevObject !== "disabled") {
	 *                        onObjectClick(prevObject, onQuizClick, onTopicClick, onLessonClick);
	 *                    }
	 *                }}
	 *            >
	 *                <View style={global.row}>
	 *                    <View style={global.linkWithArrow}>
	 *                        <Text
	 *                            style={[
	 *                                global.wrappedTextButtonLabel,
	 *                                {
	 *                                    color:
	 *                                        !!!prevObject || prevObject === "disabled"
	 *                                            ? shadeColor(colors.headerIconColor, 0.4)
	 *                                            : "red"
	 *                                }
	 *                            ]}
	 *                        >
	 *                            {t("lesson:prevButtonText")}
	 *                        </Text>
	 *                    </View>
	 *                </View>
	 *            </AppTouchableOpacity>
	 *
	 *            <AppTouchableOpacity
	 *                style={[global.wrappedButton, global.wrappedTextButton, {backgroundColor: "purple"}]}
	 *                onPress={() => {
	 *                    if (nextObject !== "disabled") {
	 *                        onObjectClick(nextObject, onQuizClick, onTopicClick, onLessonClick);
	 *                    } else if (typeof nextLockedAlert === "function") {
	 *                        nextLockedAlert();
	 *                    }
	 *                }}
	 *            >
	 *                <View style={global.row}>
	 *                    <View style={global.linkWithArrow}>
	 *                        <Text
	 *                            style={[
	 *                                global.wrappedTextButtonLabel,
	 *                                {
	 *                                    color:
	 *                                        !!!nextObject || nextObject === "disabled"
	 *                                            ? shadeColor(colors.headerIconColor, 0.4)
	 *                                            : "blue"
	 *                                }
	 *                            ]}
	 *                        >
	 *                            {t("lesson:nextButtonText")}
	 *                        </Text>
	 *                    </View>
	 *                </View>
	 *            </AppTouchableOpacity>
	 *        </View>
	 *    );
	 * };
	 *
	 * export default PrevNext;
	 *
	 * //In custom_code/index.js...
	 *
	 * ...
	 *
	 * import PrevNextComponent from './components/PrevNext';
	 * export const applyCustomCode = externalCodeSetup => {
	 *   externalCodeSetup.quizApi.setPrevNextComponent((props) => <PrevNextComponent {...props} />);
	 * }
	 *
	 */
	setPrevNextComponent = (
		PrevNextComponent: React.ComponentType<QuizPrevNextComponentProps> | null
	) => {
		this.PrevNextComponent = PrevNextComponent;
	};

	QuizTitleComponent: React.ComponentType<
		QuizTitleComponentProps
	> | null = null;

	/**
	 * You can use this to replace the quiz's title component.
	 * For example, you can use this to add more details to the quiz's title.
	 * @method
	 * @param {React.ComponentType<QuizTitleComponentProps>} QuizTitleComponent
	 * @example <caption> Add more quiz details to component </caption>
	 *
	 * //In custom_code/components/QuizTitle.js...
	 *
	 * import React from "react";
	 * import {View, Text} from "react-native";
	 * import Animated from "react-native-reanimated";
	 * const QuizTitle = ({
	 *	quiz,
	 *	global,
	 *	colors,
	 *	paddingTop = 14,
	 *	setHeaderHeight,
	 *	t,
	 *	labels,
	 *	questions,
	 *	currentSwiperPosition,
	 *	quizOrder,
	 *	quizTotalCount
	 * }) => {
	 *
	 * const paddingBottom = 14;
	 *
	 * return (
	 *   <Animated.View
	 *     style={[
	 *	   {
	 *       backgroundColor: colors.bodyFrontBg,
	 *       width: "100%",
	 *       shadowOffset: {width: 0, height: 1},
	 *       shadowRadius: 1,
	 *       shadowColor: "#000",
	 *       shadowOpacity: 0.05
	 *     }
	 *     ]}
	 *   >
	 *     <View
	 *       style={[
	 *        global.row,
	 *        {
	 *          justifyContent: "space-between",
	 *          alignItems: "flex-start",
	 *          paddingTop,
	 *          paddingBottom
	 *        }
	 *       ]}
	 *       onLayout={event => {
	 *         const {height} = event.nativeEvent.layout;
	 *         typeof setHeaderHeight === "function" && setHeaderHeight(height);
	 *       }}
	 *     >
	 *     <Animated.View
	 *       style={{
	 *         flex: 1,
	 *         paddingHorizontal: 20
	 *       }}
	 *     >
	 *       <Animated.Text
	 *         style={[
	 *           global.courseHeaderTitle,
	 *           {marginBottom: 5}
	 *         ]}
	 *       >
	 *         {quiz.title}
	 *       </Animated.Text>
	 *
	 *       {
	 *         !quiz.hideQuestionPositionOverview &&
	 *         !quiz.hideQuestionNumbering &&
	 *         currentSwiperPosition > 0 ? (
	 *         <Text style={global.courseHeaderSubTitle}>
	 *           {t("quiz:questionCount", {
	 *             question: labels.question,
	 *             current: currentSwiperPosition,
	 *             total: questions
	 *             ? questions.size
	 *             : ""
	 *           })}
	 *         </Text>
	 *         ) : (
	 *         <Text style={global.courseHeaderSubTitle}>
	 *           {`Quiz ${quizOrder + 1} of ${quizTotalCount}`}
	 *         </Text>
	 *         )
	 *       }
	 *
	 *       <Text style={global.courseHeaderSubTitle}>
	 *         Author: {quiz.author.name}
	 *       </Text>
	 *       <Text style={global.courseHeaderSubTitle}>
	 *         Completed: {quiz.completed.toString()}
	 *       </Text>
	 *
	 *     </Animated.View>
	 *   </View>
	 *  </Animated.View>
	 *  );
	 * };
	 *
	 * export default QuizTitle;
	 *
	 * //In custom_code/index.js...
	 *
	 * ...
	 * import QuizTitle from "./components/QuizTitle";
	 * export const applyCustomCode = externalCodeSetup => {
	 *  externalCodeSetup.quizApi.setQuizTitleComponent(props => <QuizTitle {...props} />)
	 * }
	 *
	 */
	setQuizTitleComponent = (
		QuizTitleComponent: React.ComponentType<QuizTitleComponentProps> | null
	) => {
		this.QuizTitleComponent = QuizTitleComponent;
	};

	QuizScreenHeader: React.ComponentType<QuizScreenHeaderProps> | null = null;
	/**
	 * You can use this hook to customize the header of the Quiz Single Screen which by default, contains the back-to-course button and the previous/next buttons.
	 * @method
	 * @param {React.ComponentType<QuizScreenHeaderProps>} QuizScreenHeader
	 * @example <caption> Add a timer on the quiz screen header </caption>
	 *
	 * //In custom_code/components/QuizScreenHeader.js...
	 *
	 * import React from "react";
	 * import {View, Text} from "react-native";
	 * import Animated from "react-native-reanimated";
	 * import {DEVICE_WIDTH} from "@src/styles/global";
	 * import AuthWrapper from "@src/components/AuthWrapper";
	 *
	 * const Header = ({
	 *     headerLeftStyle,
	 *     style,
	 *     global,
	 *     colors,
	 *     backToCourse,
	 *     headerRightAuthWrapperProps,
	 *     prevNext,
	 *     quiz,
	 *     renderQuizTimer
	 * }) => {
	 *   return (
	 *     <Animated.View
	 *       style={[
	 *         global.row,
	 *         global.fakeHeader,
	 *         {
	 *           backgroundColor: "transparent",
	 *           paddingHorizontal: 10,
	 *           overflow: "hidden"
	 *         },
	 *         {
	 *           width: DEVICE_WIDTH
	 *         },
	 *         style
	 *       ]}
	 *     >
	 *       <View
	 *         style={[
	 *           {
	 *             alignItems: "center",
	 *             justifyContent: "center",
	 *             flexDirection: "row",
	 *             flex: 1,
	 *             height: "100%",
	 *
	 *                         backgroundColor: "gray",
	 *                         borderRadius: 20
	 *           }
	 *         ]}
	 *       >
	 *         <View style={[global.headerButtonLeft, headerLeftStyle]}>
	 *           {backToCourse}
	 *         </View>
	 *
	 *         <View style={[global.headerCustomTitle]}>
	 *           {renderQuizTimer(quiz, global, colors)}
	 *         </View>
	 *
	 *         <View style={[global.headerButtonRight]}>
	 *           <AuthWrapper
	 *             actionOnGuestLogin={"hide"}
	 *             {...headerRightAuthWrapperProps}
	 *           >
	 *             {prevNext}
	 *           </AuthWrapper>
	 *         </View>
	 *       </View>
	 *     </Animated.View>
	 *   );
	 * };
	 *
	 * export default Header;
	 *
	 *  //In custom_code/index.js...
	 *
	 * import QuizScreenHeader from "./components/QuizScreenHeader";
	 * export const applyCustomCode = externalCodeSetup => {
	 *   externalCodeSetup.quizApi.setQuizScreenHeader(props => <QuizScreenHeader {...props} />)
	 * }
	 *
	 */
	setQuizScreenHeader = (
		QuizScreenHeader: React.ComponentType<QuizScreenHeaderProps> | null
	) => {
		this.QuizScreenHeader = QuizScreenHeader;
	};

	MaterialsComponent: React.ComponentType<
		QuizMaterialsComponentProps
	> | null = null;

	/**
	 * You can use this to customize the component that displays the materials of the lesson.
	 * @method
	 * @param {React.ComponentType<QuizMaterialsComponentProps>} MaterialsComponent
	 * @example
	 *
	 * ...
	 *
	 * import {
	 *   Dimensions,
	 * } from "react-native";
	 * import HTML from "react-native-render-html";
	 * import {RenderListPrefix} from "@src/components/Course/CourseMaterials"
	 * const ent = require("ent");
	 * const DEVICE_WIDTH = Dimensions.get("window").width;
	 *
	 * export const applyCustomCode = (externalCodeSetup) => {
	 *
	 *   externalCodeSetup.quizApi.setMaterialsComponent(props => {
	 *       const {
	 *           tagsStyles,
	 *           materialsStyles,
	 *           baseFontStyle,
	 *           materials,
	 *           onLinkPress,
	 *           global,
	 *           colors
	 *       } = props;
	 *
	 *       return (
	 *           <HTML
	 *               tagsStyles={{...tagsStyles, ...materialsStyles}}
	 *               baseFontStyle={baseFontStyle(15)}
	 *               html={ent.decode(materials)}
	 *               imagesMaxWidth={DEVICE_WIDTH - 32}
	 *               onLinkPress={onLinkPress}
	 *               listsPrefixesRenderers={{
	 *                   ul: (attrib, children, styles, passProps) => (
	 *                       <RenderListPrefix
	 *                           parent={"ul"}
	 *                           colors={colors}
	 *                           global={global}
	 *                           passProps={passProps}
	 *                       />
	 *                   ),
	 *                   ol: (attrib, children, styles, passProps) => (
	 *                       <RenderListPrefix
	 *                           parent={"ol"}
	 *                           colors={colors}
	 *                           global={global}
	 *                           passProps={passProps}
	 *                       />
	 *                   )
	 *               }}
	 *           />
	 *       );
	 *   });
	 * }
	 */
	setMaterialsComponent = (
		MaterialsComponent: React.ComponentType<QuizMaterialsComponentProps> | null
	) => {
		this.MaterialsComponent = MaterialsComponent;
	};

	QuizStartButton: React.ComponentType<QuizStartButtonProps> | null = null;

	/**
	 * You can use this to customize the "Start Quiz" button that appears when opening the QuizSingleScreen.
	 * @method
	 * @param {React.ComponentType<QuizStartButtonProps>} QuizStartButton
	 * @example
	 *
	 * import AppButton from "@src/components/AppButton";
	 *
	 * export const applyCustomCode = (externalCodeSetup) => {
	 *     externalCodeSetup.quizApi.setQuizStartButton(props => {
	 *         const {
	 *             showStart,
	 *             styles,
	 *             global,
	 *             quiz,
	 *             checkForm,
	 *             startQuiz,
	 *             quizStartLoading,
	 *             isResumeQuiz,
	 *             t,
	 *             labels
	 *         } = props;
	 *
	 *         return showStart ? (
	 *             <View
	 *                 style={[
	 *                     styles.buttonWrap,
	 *                     {paddingBottom: styles.containerPadding},
	 *                     global.quizStartButtonContainer
	 *                 ]}
	 *             >
	 *                 <AppButton
	 *                     style={[
	 *                         styles.button,
	 *                         {
	 *                             backgroundColor: styles.buttonBackground
	 *                         },
	 *                         global.quizStartButton
	 *                     ]}
	 *                     onPress={() => {
	 *                         if (quiz.formActivated && !checkForm(quiz)) {
	 *                             return false;
	 *                         } else if (!quizStartLoading) {
	 *                             startQuiz(quiz);
	 *                         }
	 *                     }}
	 *                     label={t(isResumeQuiz ? "quiz:continue" : "quiz:start", {
	 *                         quiz: labels.quiz
	 *                     })}
	 *                     labelStyle={global.quizStartButtonLabel}
	 *                     global={global}
	 *                     loading={quizStartLoading}
	 *                     loadingStyle={global.quizStartButtonLoading}
	 *                 />
	 *             </View>
	 *         ) : (
	 *             <></>
	 *         );
	 *
	 *     });
	 * }
	 */
	setQuizStartButton = (
		QuizStartButton: React.ComponentType<QuizStartButtonProps> | null
	) => {
		this.QuizStartButton = QuizStartButton;
	};

	WebViewContentComponent: React.ComponentType<
		QWebViewContentComponentProps
	> | null = null;

	/**
	 * You can use this hook to replace the webview being used in the quiz content.
	 * For example, you can choose to replace it with the default react-native webview.
	 * @method
	 * @param {React.ComponentType<QWebViewContentComponentProps>} WebViewContentComponent
	 * @example
	 *
	 * ...
	 *
	 * import WebViewWithMore from "@src/components/WebViewWithMore";
	 * export const applyCustomCode = (externalCodeSetup) => {
	 *     externalCodeSetup.quizApi.setWebViewContentComponent(
	 *         ({
	 *             online,
	 *             t,
	 *             onShouldStartLoadWithRequest,
	 *             height,
	 *             source,
	 *             global,
	 *             colors,
	 *             ModalHeaderComponent
	 *         }) => (
	 *             <WebViewWithMore
	 *                 online={online}
	 *                 t={t}
	 *                 onShouldStartLoadWithRequest={onShouldStartLoadWithRequest}
	 *                 height={height}
	 *                 source={source}
	 *                 global={global}
	 *                 colors={colors}
	 *                 ModalHeaderComponent={ModalHeaderComponent}
	 *             />
	 *         )
	 *     );
	 * }
	 */
	setWebViewContentComponent = (
		WebViewContentComponent: React.ComponentType<
			QWebViewContentComponentProps
		> | null
	) => {
		this.WebViewContentComponent = WebViewContentComponent;
	};

	OfflineComponent: React.ComponentType<
		QuizOfflineComponentProps
	> | null = null;

	/**
	 * You can use this hook to customize the component that displays a "Come back later. Quizzes are not available offline" message.
	 * The component is visible when attempting to start a quiz while the device is offline.
	 * @method
	 * @param {React.ComponentType<QuizOfflineComponentProps>} OfflineComponent
	 * @example
	 *
	 * ...
	 *
	 * import Icon from "@src/components/Icon";
	 *
	 * export const applyCustomCode = (externalCodeSetup) => {
	 *     externalCodeSetup.quizApi.setOfflineComponent(
	 *         ({global, t, wrapperCompsStyle, renderNav, styles}) => (
	 *             <View style={[wrapperCompsStyle, styles.wrapper]} key={"intro"}>
	 *                 {renderNav()}
	 *                 <View style={styles.container}>
	 *                     <Icon
	 *                         tintColor={"#DCDDDF"}
	 *                         icon={{fontIconName: "wifi-slash", weight: 400}}
	 *                         styles={global.emptyIcon}
	 *                     />
	 *                     <Text style={[global.emptyTitle, styles.title]}>
	 *                         {t("quiz:offlineAlertTitle")}
	 *                     </Text>
	 *                     <Text style={[global.emptyDesc, styles.message]}>
	 *                         {t("quiz:offlineAlertMessage")}
	 *                     </Text>
	 *                 </View>
	 *             </View>
	 *         )
	 *     );
	 * }
	 */
	setOfflineComponent = (
		OfflineComponent: React.ComponentType<QuizOfflineComponentProps> | null
	) => {
		this.OfflineComponent = OfflineComponent;
	};

	QuizTimer: React.ComponentType<QuizTimerProps> | null = null;

	/**
	 * You can use this hook to customize the QuizTimer component.
	 * @method
	 * @param {React.ComponentType<QuizTimerProps>} QuizTimer
	 * @example
	 *
	 * ...
	 *
	 * import Icon from "@src/components/Icon";
	 * import TimeCountDown from "@src/components/TimerCountDown";
	 *
	 * export const applyCustomCode = (externalCodeSetup: ExternalCodeSetup) => {
	 *     externalCodeSetup.quizApi.setQuizTimer(
	 *         ({
	 *             showTimer,
	 *             item,
	 *             global,
	 *             colors,
	 *             styles,
	 *             timerRef,
	 *             onTimePassed,
	 *             onTimeOver
	 *         }) =>
	 *             showTimer ? (
	 *                 <View style={styles.container}>
	 *                     <Icon
	 *                         icon={{fontIconName: "stopwatch", weight: 400}}
	 *                         webIcon={"IconArrowBack"}
	 *                         tintColor={colors.textIconColor}
	 *                         styles={styles.icon}
	 *                     />
	 *                     <TimeCountDown
	 *                         ref={timerRef}
	 *                         paused={false}
	 *                         onTimePassed={onTimePassed}
	 *                         onTimeOver={onTimeOver}
	 *                         secondsLimit={item.timeLimit}
	 *                         textProps={{style: global.timer}}
	 *                     />
	 *                     <Text>The timer</Text>
	 *                 </View>
	 *             ) : (
	 *                 <></>
	 *             )
	 *     );
	 * }
	 */
	setQuizTimer = (QuizTimer: React.ComponentType<QuizTimerProps> | null) => {
		this.QuizTimer = QuizTimer;
	};

	QuestionOverviewButtons: React.ComponentType<
		QuestionOverviewButtonsProps
	> | null = null;

	/**
	 * You can use this hook to customize the "Review Question" and "Quiz summary" buttons in the QuizSingleScreen.
	 * @method
	 * @param {QuestionOverviewButtonsProps} QuestionOverviewButtons
	 * @example
	 *
	 * ...
	 *
	 * import QuestionOverviewButtons from "@src/components/QuestionOverview/QuestionOverviewButtons";
	 *
	 * export const applyCustomCode = (externalCodeSetup) => {
	 *     externalCodeSetup.questionApiHooks.setQuestionOverviewButtons(
	 *         ({
	 *             style,
	 *             global,
	 *             t,
	 *             labels,
	 *             onReviewPress,
	 *             onSummaryPress,
	 *             disableSummaryButton
	 *         }) => (
	 *             <QuestionOverviewButtons
	 *                 style={style}
	 *                 global={global}
	 *                 reviewQuestionLabel={t("quiz:reviewQuestionLabel", {
	 *                     question: labels.question
	 *                 })}
	 *                 summaryQuestionLabel={t("quiz:quizSummaryLabel", {
	 *                     quiz: labels.quiz
	 *                 })}
	 *                 onReviewPress={onReviewPress}
	 *                 onSummaryPress={onSummaryPress}
	 *                 disableSummaryButton={disableSummaryButton}
	 *             />
	 *         )
	 *     );
	 * }
	 */
	setQuestionOverviewButtons = (
		QuestionOverviewButtons: React.ComponentType<
			QuestionOverviewButtonsProps
		> | null
	) => {
		this.QuestionOverviewButtons = QuestionOverviewButtons;
	};
}