Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Manually apply multiple-single-input-interaction changes to develop [DO NOT MERGE] #266

Closed
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ dependencies {
'androidx.test:core:1.2.0',
'androidx.test.espresso:espresso-core:3.2.0',
'androidx.test.espresso:espresso-intents:3.1.0',
'androidx.test.espresso:espresso-contrib:3.1.0',
veena14cs marked this conversation as resolved.
Show resolved Hide resolved
'androidx.test.ext:junit:1.1.1',
'com.google.truth:truth:0.43',
'org.robolectric:robolectric:4.3',
Expand All @@ -87,6 +88,7 @@ dependencies {
'androidx.test:core:1.2.0',
'androidx.test.espresso:espresso-core:3.2.0',
'androidx.test.espresso:espresso-intents:3.1.0',
'androidx.test.espresso:espresso-contrib:3.1.0',
veena14cs marked this conversation as resolved.
Show resolved Hide resolved
'androidx.test.ext:junit:1.1.1',
'androidx.test:runner:1.2.0',
'com.google.truth:truth:0.43',
Expand Down
18 changes: 18 additions & 0 deletions app/src/main/java/org/oppia/app/home/HomeFragmentPresenter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ import org.oppia.app.viewmodel.ViewModelProvider
import org.oppia.domain.UserAppHistoryController
import org.oppia.domain.exploration.ExplorationDataController
import org.oppia.domain.exploration.TEST_EXPLORATION_ID_5
import org.oppia.domain.exploration.TEST_EXPLORATION_ID_6
import org.oppia.util.data.AsyncResult
import org.oppia.util.logging.Logger
import javax.inject.Inject

private const val EXPLORATION_ID = TEST_EXPLORATION_ID_5
private const val EXPLORATION_ID_1 = TEST_EXPLORATION_ID_6

/** The controller for [HomeFragment]. */
@FragmentScope
Expand Down Expand Up @@ -51,6 +53,7 @@ class HomeFragmentPresenter @Inject constructor(
}

fun playExplorationButton(v: View) {
explorationDataController.stopPlayingExploration()
explorationDataController.startPlayingExploration(
EXPLORATION_ID
).observe(fragment, Observer<AsyncResult<Any?>> { result ->
Expand All @@ -64,4 +67,19 @@ class HomeFragmentPresenter @Inject constructor(
}
})
}
fun playExplorationButton_1(v: View) {
veena14cs marked this conversation as resolved.
Show resolved Hide resolved
explorationDataController.stopPlayingExploration()
explorationDataController.startPlayingExploration(
EXPLORATION_ID_1
).observe(fragment, Observer<AsyncResult<Any?>> { result ->
when {
result.isPending() -> logger.d("HomeFragment", "Loading exploration")
result.isFailure() -> logger.e("HomeFragment", "Failed to load exploration", result.getErrorOrNull()!!)
else -> {
logger.d("HomeFragment", "Successfully loaded exploration")
routeToExplorationListener.routeToExploration(EXPLORATION_ID_1)
}
}
})
}
}
166 changes: 166 additions & 0 deletions app/src/main/java/org/oppia/app/player/state/InteractionAdapter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
package org.oppia.app.player.state

import android.text.Spannable
import android.util.Log
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.databinding.ViewDataBinding
import androidx.databinding.library.baseAdapters.BR
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.item_selection_interaction_items.view.item_selection_checkbox
import kotlinx.android.synthetic.main.item_selection_interaction_items.view.item_selection_contents_text_view
import kotlinx.android.synthetic.main.multiple_choice_interaction_items.view.multiple_choice_content_text_view
import kotlinx.android.synthetic.main.multiple_choice_interaction_items.view.multiple_choice_radio_button
import org.oppia.app.R
import org.oppia.app.databinding.ItemSelectionInteractionItemsBinding
import org.oppia.app.databinding.MultipleChoiceInteractionItemsBinding
import org.oppia.app.model.InteractionObject
import org.oppia.app.model.StringList
import org.oppia.app.player.state.itemviewmodel.SelectionInteractionContentViewModel
import org.oppia.app.player.state.itemviewmodel.SelectionInteractionCustomizationArgsViewModel
import org.oppia.app.player.state.listener.ItemClickListener
import org.oppia.util.parser.HtmlParser

private const val VIEW_TYPE_RADIO_BUTTONS = 1
private const val VIEW_TYPE_CHECKBOXES = 2
private const val INTERACTION_ADAPTER_TAG = "Interaction Adapter"

/**
* Adapter to bind the interactions to the [RecyclerView]. It handles MultipleChoiceInput
* and ItemSelectionInput interaction views.
* */
class InteractionAdapter(
private val htmlParserFactory: HtmlParser.Factory,
private val entityType: String,
private val explorationId: String,
private val itemList: MutableList<SelectionInteractionContentViewModel>,
private val selectionInteractionCustomizationArgsViewModel: SelectionInteractionCustomizationArgsViewModel,
private val itemClickListener: ItemClickListener
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

private var itemSelectedPosition = -1
private var selectedAnswerIndex = -1
private var selectedHtmlStringList = mutableListOf<String>()

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
VIEW_TYPE_RADIO_BUTTONS -> {
val inflater = LayoutInflater.from(parent.context)
val binding =
DataBindingUtil.inflate<MultipleChoiceInteractionItemsBinding>(
inflater,
R.layout.multiple_choice_interaction_items,
parent,
/* attachToParent= */ false
)
MultipleChoiceViewHolder(binding)
}
VIEW_TYPE_CHECKBOXES -> {
val inflater = LayoutInflater.from(parent.context)
val binding =
DataBindingUtil.inflate<ItemSelectionInteractionItemsBinding>(
inflater,
R.layout.item_selection_interaction_items,
parent,
/* attachToParent= */ false
)
ItemSelectionViewHolder(binding)
}
else -> throw IllegalArgumentException("Invalid view type")
}
}

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder.itemViewType) {
VIEW_TYPE_RADIO_BUTTONS -> (holder as MultipleChoiceViewHolder).bind(
itemList[position].htmlContent,
position,
itemSelectedPosition
)
VIEW_TYPE_CHECKBOXES -> (holder as ItemSelectionViewHolder).bind(
itemList[position]
)
}
}

// Determines the appropriate ViewType according to the interaction type.
override fun getItemViewType(position: Int): Int {
return if (selectionInteractionCustomizationArgsViewModel.interactionId == "ItemSelectionInput") {
if (selectionInteractionCustomizationArgsViewModel.maxAllowableSelectionCount > 1) {
VIEW_TYPE_CHECKBOXES
} else {
VIEW_TYPE_RADIO_BUTTONS
}
} else {
VIEW_TYPE_RADIO_BUTTONS
}
}

override fun getItemCount(): Int {
return itemList.size
}

inner class ItemSelectionViewHolder(val binding: ViewDataBinding) : RecyclerView.ViewHolder(binding.root) {
veena14cs marked this conversation as resolved.
Show resolved Hide resolved
internal fun bind(selectionInteractionContentViewModel: SelectionInteractionContentViewModel) {
binding.setVariable(BR.htmlContent, selectionInteractionContentViewModel.htmlContent)
binding.executePendingBindings()
val htmlResult: Spannable = htmlParserFactory.create(entityType, explorationId).parseOppiaHtml(
selectionInteractionContentViewModel.htmlContent,
binding.root.item_selection_contents_text_view
)
binding.root.item_selection_contents_text_view.text = htmlResult
binding.root.item_selection_checkbox.isChecked = selectionInteractionContentViewModel.isAnswerSelected
binding.root.setOnClickListener {
if (binding.root.item_selection_checkbox.isChecked) {
itemList[adapterPosition].isAnswerSelected = false
selectedHtmlStringList.remove(binding.root.item_selection_contents_text_view.text.toString())
} else {
if (selectedHtmlStringList.size != selectionInteractionCustomizationArgsViewModel.maxAllowableSelectionCount) {
itemList[adapterPosition].isAnswerSelected = true
selectedHtmlStringList.add(binding.root.item_selection_contents_text_view.text.toString())
} else {
Log.d(
INTERACTION_ADAPTER_TAG,
"You cannot select more than ${selectionInteractionCustomizationArgsViewModel.maxAllowableSelectionCount} options"
)
}
}
notifyDataSetChanged()
val interactionObjectBuilder = InteractionObject.newBuilder()
if (selectedHtmlStringList.size >= 0) {
interactionObjectBuilder.setOfHtmlString = StringList.newBuilder().addAllHtml(selectedHtmlStringList).build()
} else {
if (selectedAnswerIndex >= 0) {
interactionObjectBuilder.nonNegativeInt = selectedAnswerIndex
}

}
itemClickListener.onItemClick(interactionObjectBuilder.build())
}
}
}

inner class MultipleChoiceViewHolder(val binding: ViewDataBinding) : RecyclerView.ViewHolder(binding.root) {
veena14cs marked this conversation as resolved.
Show resolved Hide resolved
internal fun bind(rawString: String, position: Int, selectedPosition: Int) {
binding.setVariable(BR.htmlContent, rawString)
binding.executePendingBindings()
val htmlResult: Spannable = htmlParserFactory.create(entityType, explorationId).parseOppiaHtml(
rawString,
binding.root.multiple_choice_content_text_view
)
binding.root.multiple_choice_content_text_view.text = htmlResult
binding.root.multiple_choice_radio_button.isChecked = selectedPosition == position
binding.root.setOnClickListener {
itemSelectedPosition = adapterPosition
selectedAnswerIndex = adapterPosition
notifyDataSetChanged()
val interactionObjectBuilder = InteractionObject.newBuilder()
if (selectedAnswerIndex >= 0) {
interactionObjectBuilder.nonNegativeInt = selectedAnswerIndex
}
itemClickListener.onItemClick(interactionObjectBuilder.build())
}
}
}
}
87 changes: 72 additions & 15 deletions app/src/main/java/org/oppia/app/player/state/StateAdapter.kt
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,19 @@ import androidx.databinding.ViewDataBinding
import androidx.recyclerview.widget.RecyclerView
import androidx.databinding.library.baseAdapters.BR
import kotlinx.android.synthetic.main.content_item.view.content_text_view
import kotlinx.android.synthetic.main.selection_interaction_item.view.selection_interaction_frameLayout
import kotlinx.android.synthetic.main.state_button_item.view.*
import org.oppia.app.R
import org.oppia.app.databinding.ContentItemBinding
import org.oppia.app.databinding.SelectionInteractionItemBinding
import org.oppia.app.player.state.itemviewmodel.StateButtonViewModel
import org.oppia.app.player.state.listener.ButtonInteractionListener
import org.oppia.app.databinding.StateButtonItemBinding
import org.oppia.app.model.InteractionObject
import org.oppia.app.player.state.itemviewmodel.ContentViewModel
import org.oppia.app.player.state.itemviewmodel.SelectionInteractionCustomizationArgsViewModel
import org.oppia.app.player.state.itemviewmodel.SelectionInteractionContentViewModel
import org.oppia.app.player.state.listener.ItemClickListener
import org.oppia.util.parser.HtmlParser

@Suppress("unused")
Expand All @@ -26,6 +32,7 @@ private const val VIEW_TYPE_NUMERIC_INPUT_INTERACTION = 3
@Suppress("unused")
private const val VIEW_TYPE_TEXT_INPUT_INTERACTION = 4
private const val VIEW_TYPE_STATE_BUTTON = 5
const val VIEW_TYPE_SELECTION_INTERACTION = 6

/** Adapter to inflate different items/views inside [RecyclerView]. The itemList consists of various ViewModels. */
class StateAdapter(
Expand All @@ -37,6 +44,9 @@ class StateAdapter(
) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {

lateinit var stateButtonViewModel: StateButtonViewModel
private var interactionObjectBuilder: InteractionObject = InteractionObject.newBuilder().build()

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
// TODO(#249): Generalize this binding to make adding future interactions easier.
Expand All @@ -58,11 +68,22 @@ class StateAdapter(
inflater,
R.layout.content_item,
parent,
/* attachToParent= */false
/* attachToParent= */ false
)
ContentViewHolder(binding)
}
else -> throw IllegalArgumentException("Invalid view type") as Throwable
VIEW_TYPE_SELECTION_INTERACTION -> {
val inflater = LayoutInflater.from(parent.context)
val binding =
DataBindingUtil.inflate<SelectionInteractionItemBinding>(
inflater,
R.layout.selection_interaction_item,
parent,
/* attachToParent= */ false
)
SelectionInteractionViewHolder(binding)
}
else -> throw IllegalArgumentException("Invalid view type")
}
}

Expand All @@ -74,25 +95,40 @@ class StateAdapter(
VIEW_TYPE_CONTENT -> {
(holder as ContentViewHolder).bind((itemList[position] as ContentViewModel).htmlContent)
}
VIEW_TYPE_SELECTION_INTERACTION -> {
(holder as SelectionInteractionViewHolder).bind(itemList[position] as SelectionInteractionCustomizationArgsViewModel)
}
}
}

override fun getItemViewType(position: Int): Int {
return when (itemList[position]) {
is ContentViewModel -> VIEW_TYPE_CONTENT
is SelectionInteractionCustomizationArgsViewModel -> VIEW_TYPE_SELECTION_INTERACTION
is StateButtonViewModel -> {
stateButtonViewModel = itemList[position] as StateButtonViewModel
VIEW_TYPE_STATE_BUTTON
}
is ContentViewModel -> {
VIEW_TYPE_CONTENT
}
else -> throw IllegalArgumentException("Invalid type of data at $position: ${itemList[position]}")
else -> throw IllegalArgumentException("Invalid type of data $position")
}
}

override fun getItemCount(): Int {
return itemList.size
}

inner class ContentViewHolder(val binding: ViewDataBinding) : RecyclerView.ViewHolder(binding.root) {
internal fun bind(rawString: String) {
binding.setVariable(BR.htmlContent, rawString)
binding.executePendingBindings()
val htmlResult: Spannable = htmlParserFactory.create(entityType, explorationId).parseOppiaHtml(
rawString,
binding.root.content_text_view
)
binding.root.content_text_view.text = htmlResult
}
}

private class StateButtonViewHolder(
val binding: ViewDataBinding,
private val buttonInteractionListener: ButtonInteractionListener
Expand All @@ -112,15 +148,36 @@ class StateAdapter(
}
}

inner class ContentViewHolder(val binding: ViewDataBinding) : RecyclerView.ViewHolder(binding.root) {
internal fun bind(rawString: String) {
binding.setVariable(BR.htmlContent, rawString)
binding.executePendingBindings()
val htmlResult: Spannable = htmlParserFactory.create(entityType, explorationId).parseOppiaHtml(
rawString,
binding.root.content_text_view
)
binding.root.content_text_view.text = htmlResult
inner class SelectionInteractionViewHolder(
private val binding: ViewDataBinding
) : RecyclerView.ViewHolder(binding.root), ItemClickListener {

override fun onItemClick(interactionObject: InteractionObject) {
interactionObjectBuilder = interactionObject
}

internal fun bind(customizationArgs: SelectionInteractionCustomizationArgsViewModel) {
val items: Array<String>?
val choiceInteractionContentList: MutableList<SelectionInteractionContentViewModel> = ArrayList()
val gaeCustomArgsInString = customizationArgs.choiceItems.toString().replace("[", "").replace("]", "")
items = gaeCustomArgsInString.split(",").toTypedArray()
for (values in items) {
val selectionContentViewModel = SelectionInteractionContentViewModel()
selectionContentViewModel.htmlContent = values
selectionContentViewModel.isAnswerSelected = false
choiceInteractionContentList.add(selectionContentViewModel)
}
val interactionAdapter =
InteractionAdapter(
htmlParserFactory,
entityType,
explorationId,
choiceInteractionContentList,
customizationArgs,
this as ItemClickListener
)
binding.root.selection_interaction_frameLayout.setAdapter(interactionAdapter)
}
}

}
Loading