Skip to content

POC for video quiz support#985

Open
HarishV14 wants to merge 2 commits intomasterfrom
poc-video-quiz
Open

POC for video quiz support#985
HarishV14 wants to merge 2 commits intomasterfrom
poc-video-quiz

Conversation

@HarishV14
Copy link
Contributor

No description provided.

@gemini-code-assist
Copy link

Summary of Changes

Hello @HarishV14, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a proof of concept for integrating interactive quizzes directly into video content playback. The changes enable the application to fetch quiz questions associated with specific video timestamps, pause the video at these points, display a quiz dialog to the user, and resume playback after the user interacts with the quiz. The implementation supports various question types and provides immediate feedback, enhancing the interactive learning experience.

Highlights

  • Video Quiz Core Functionality: Introduced abstract methods in BaseVideoWidgetFragment for video quiz setup, pausing, and playing video, which are then implemented in NativeVideoWidgetFragment to control the ExoPlayer.
  • Quiz Orchestration: VideoContentFragment now orchestrates the entire video quiz flow, including fetching questions from the backend, setting up quiz triggers at specific video timestamps, pausing video playback to display quizzes, and resuming playback upon quiz completion.
  • Interactive Quiz Dialog: A new VideoQuizDialogFragment has been added to provide the user interface for displaying and interacting with video quiz questions. It supports multiple-choice, multiple-select, and gap-fill question types, offering immediate feedback.
  • Backend Integration: New API endpoints and network calls have been added in CourseService and CourseNetwork to fetch video quiz questions, with corresponding data models defined in NetworkVideoQuestion.kt.
  • Quiz State Management: A VideoQuizViewModel was introduced to manage the state of video quiz questions, tracking completed questions and determining the next relevant question to display at a given video position.
  • ExoPlayer Integration: ExoPlayerUtil has been enhanced to schedule quiz triggers at precise video timestamps using PlayerMessage and provides methods to programmatically pause and play the video.
  • New UI Resources: Several new drawable and layout XML files have been added to support the visual presentation of the video quiz dialog, including icons for feedback and styling for quiz options and gap-fill inputs.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a proof-of-concept for video quiz support, a significant feature enhancement. The implementation spans across the UI, networking, and data layers, including a new dialog for displaying questions, logic for triggering quizzes at specific video timestamps, and corresponding data models and service calls. The overall approach is sound. I've provided several suggestions to improve code quality, maintainability, and testability. Key areas for improvement include refactoring complex methods, addressing potential testability issues, and handling all states in asynchronous operations.

Comment on lines +5 to +9
import android.graphics.Color
import android.graphics.Typeface
import android.os.Bundle
import android.text.InputType
import android.util.Log

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The imports for android.graphics.Color, android.graphics.Typeface, and android.util.Log are unused in this file. Please remove them to keep the code clean.


private var answerViews = mutableListOf<View>()
private var selectedAnswerIds = mutableListOf<Long>()
private var isCorrect = false

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The class property isCorrect is only assigned and used within the checkButton's setOnClickListener. It should be converted to a local variable (val isCorrect = checkAnswers()) inside that listener (on line 97) to reduce its scope and improve clarity. Please remove this property and declare it locally.

Comment on lines +117 to +239
private fun buildQuestionUI(
context: Context,
container: LinearLayout,
question: NetworkVideoQuestion
) {
val inflater = LayoutInflater.from(context)
when (question.question.type) {
"R" -> {
val radioGroup = RadioGroup(context)
radioGroup.orientation = LinearLayout.VERTICAL
radioGroup.id = View.generateViewId()

val radioButtons = mutableListOf<RadioButton>()

question.question.answers?.forEach { answer ->
val optionView = inflater.inflate(R.layout.list_item_quiz_radio, radioGroup, false)
val radioButton = optionView.findViewById<RadioButton>(R.id.quiz_radio_button)

radioButton.tag = answer
radioButton.text = HtmlCompat.fromHtml(
answer.textHtml,
HtmlCompat.FROM_HTML_MODE_LEGACY
)

radioButton.setOnCheckedChangeListener { buttonView, isChecked ->
if (isChecked) {
radioButtons.forEach { rb ->
if (rb != buttonView) {
rb.isChecked = false
}
}
}
checkButton?.isEnabled = radioButtons.any { it.isChecked }
}

radioButtons.add(radioButton)
radioGroup.addView(optionView)
answerViews.add(optionView)
}
container.addView(radioGroup)
answerViews.add(radioGroup)
}
"C" -> {
question.question.answers?.forEach { answer ->
val optionView = inflater.inflate(R.layout.list_item_quiz_checkbox, container, false)
val checkBox = optionView.findViewById<CheckBox>(R.id.quiz_check_box)

checkBox.tag = answer
checkBox.text = HtmlCompat.fromHtml(
answer.textHtml,
HtmlCompat.FROM_HTML_MODE_LEGACY
)

checkBox.setOnCheckedChangeListener { _, _ ->
val hasSelection = answerViews.any { view ->
view.findViewById<CheckBox>(R.id.quiz_check_box)?.isChecked == true
}
checkButton?.isEnabled = hasSelection
}

container.addView(optionView)
answerViews.add(optionView)
}
}
"G" -> {
val flexboxLayout = FlexboxLayout(context)
flexboxLayout.flexWrap = FlexWrap.WRAP
flexboxLayout.alignItems = com.google.android.flexbox.AlignItems.CENTER

val html = question.question.questionHtml
val cleanText = HtmlCompat.fromHtml(html, HtmlCompat.FROM_HTML_MODE_LEGACY).toString().trim()

val pattern = Pattern.compile("\\[(.*?)\\]")
val matcher = pattern.matcher(cleanText)

val gapFillEditTexts = mutableListOf<EditText>()
var lastEnd = 0
while (matcher.find()) {
val textBefore = cleanText.substring(lastEnd, matcher.start())
if (textBefore.isNotEmpty()) {
val textView = TextView(context)
textView.text = textBefore
textView.textSize = 18f
textView.setTextColor(ContextCompat.getColor(context, R.color.testpress_table_text))
flexboxLayout.addView(textView)
}

val editText = EditText(context)
editText.setSingleLine(true)
editText.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
editText.minEms = 3
editText.setTextColor(ContextCompat.getColor(context, R.color.testpress_table_text))
editText.setBackgroundResource(R.drawable.quiz_gap_border_normal)

gapFillEditTexts.add(editText)
editText.addTextChangedListener(object : android.text.TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
override fun afterTextChanged(s: android.text.Editable?) {
val allFilled = gapFillEditTexts.all { it.text.toString().trim().isNotEmpty() }
checkButton?.isEnabled = allFilled
}
})

flexboxLayout.addView(editText)
answerViews.add(editText)

lastEnd = matcher.end()
}

val textAfter = cleanText.substring(lastEnd)
if (textAfter.isNotEmpty()) {
val textView = TextView(context)
textView.text = textAfter
textView.textSize = 18f
textView.setTextColor(ContextCompat.getColor(context, R.color.testpress_table_text))
flexboxLayout.addView(textView)
}

container.addView(flexboxLayout)
}
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The buildQuestionUI method is quite long and handles the UI creation for three different question types. This reduces its readability and maintainability. To improve the code structure, consider refactoring this into smaller, private methods, each responsible for building a specific question type (e.g., buildRadioQuestionUI, buildCheckboxQuestionUI, buildGapFillQuestionUI).

answerViews.add(optionView)
}
container.addView(radioGroup)
answerViews.add(radioGroup)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The RadioGroup container is being added to answerViews. This list is intended to hold views representing individual answer options. While this doesn't cause a bug in the current implementation, it's semantically incorrect and could lead to unexpected behavior if the logic that iterates over answerViews changes. Please remove this line.

Comment on lines +875 to +881
PlayerMessage.Target target = new PlayerMessage.Target() {
public void handleMessage(int messageType, Object payload) {
int positionInSeconds = messageType;

quizCallbackHandler.obtainMessage(positionInSeconds).sendToTarget();
}
};

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The anonymous inner class for PlayerMessage.Target can be simplified to a lambda expression, which is more concise and improves readability.

        PlayerMessage.Target target = (messageType, payload) -> {
            int positionInSeconds = messageType;

            quizCallbackHandler.obtainMessage(positionInSeconds).sendToTarget();
        };

@testpress testpress deleted a comment from gemini-code-assist bot Nov 4, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant