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

Attempt to rebase content-card onto develop [DO NOT MERGE] #265

Closed
Show file tree
Hide file tree
Changes from all 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: 1 addition & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,4 @@
<activity android:name=".testing.HtmlParserTestActivity" />
<activity android:name=".testing.UrlImageParserTestActivity" />
</application>
</manifest>
</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.app.Application
import dagger.BindsInstance
import dagger.Component
import org.oppia.app.activity.ActivityComponent
import org.oppia.util.parser.HtmlParserEntityTypeModule
import org.oppia.data.backends.gae.NetworkModule
import org.oppia.domain.classify.InteractionsModule
import org.oppia.domain.classify.rules.continueinteraction.ContinueModule
Expand All @@ -26,8 +27,9 @@ import javax.inject.Singleton
@Component(modules = [
ApplicationModule::class, DispatcherModule::class, NetworkModule::class, LoggerModule::class,
ContinueModule::class, FractionInputModule::class, ItemSelectionInputModule::class, MultipleChoiceInputModule::class,
NumberWithUnitsRuleModule::class, NumericInputRuleModule::class, TextInputRuleModule::class, InteractionsModule::class,
GcsResourceModule::class, ImageParsingModule::class, GlideImageLoaderModule::class
NumberWithUnitsRuleModule::class, NumericInputRuleModule::class, TextInputRuleModule::class,
InteractionsModule::class, GcsResourceModule::class, GlideImageLoaderModule::class, ImageParsingModule::class,
HtmlParserEntityTypeModule::class
])
interface ApplicationComponent {
@Component.Builder
Expand Down
44 changes: 39 additions & 5 deletions app/src/main/java/org/oppia/app/player/state/StateAdapter.kt
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
package org.oppia.app.player.state

import android.text.Spannable
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
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.state_button_item.view.*
import org.oppia.app.R
import org.oppia.app.databinding.ContentItemBinding
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.player.state.itemviewmodel.ContentViewModel
import org.oppia.util.parser.HtmlParser

@Suppress("unused")
private const val VIEW_TYPE_CONTENT = 1
Expand All @@ -25,12 +30,13 @@ private const val VIEW_TYPE_STATE_BUTTON = 5
/** Adapter to inflate different items/views inside [RecyclerView]. The itemList consists of various ViewModels. */
class StateAdapter(
private val itemList: MutableList<Any>,
private val buttonInteractionListener: ButtonInteractionListener
private val buttonInteractionListener: ButtonInteractionListener,
private val htmlParserFactory: HtmlParser.Factory,
private val entityType: String,
private val explorationId: String
) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {

lateinit var stateButtonViewModel: StateButtonViewModel

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 @@ -45,6 +51,17 @@ class StateAdapter(
)
StateButtonViewHolder(binding, buttonInteractionListener)
}
VIEW_TYPE_CONTENT -> {
val inflater = LayoutInflater.from(parent.context)
val binding =
DataBindingUtil.inflate<ContentItemBinding>(
inflater,
R.layout.content_item,
parent,
/* attachToParent= */false
)
ContentViewHolder(binding)
}
else -> throw IllegalArgumentException("Invalid view type") as Throwable
}
}
Expand All @@ -54,16 +71,21 @@ class StateAdapter(
VIEW_TYPE_STATE_BUTTON -> {
(holder as StateButtonViewHolder).bind(itemList[position] as StateButtonViewModel)
}
VIEW_TYPE_CONTENT -> {
(holder as ContentViewHolder).bind((itemList[position] as ContentViewModel).htmlContent)
}
}
}

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

Expand All @@ -89,4 +111,16 @@ class StateAdapter(
binding.executePendingBindings()
}
}

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
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@ import org.oppia.app.model.AnswerOutcome
import org.oppia.app.model.CellularDataPreference
import org.oppia.app.model.EphemeralState
import org.oppia.app.model.InteractionObject
import org.oppia.app.model.SubtitledHtml
import org.oppia.app.player.audio.AudioFragment
import org.oppia.app.player.audio.CellularDataDialogFragment
import org.oppia.app.player.exploration.ExplorationActivity
import org.oppia.app.player.state.itemviewmodel.ContentViewModel
import org.oppia.app.player.state.itemviewmodel.StateButtonViewModel
import org.oppia.app.player.state.listener.ButtonInteractionListener
import org.oppia.app.viewmodel.ViewModelProvider
Expand All @@ -28,6 +30,8 @@ import org.oppia.domain.exploration.ExplorationDataController
import org.oppia.domain.exploration.ExplorationProgressController
import org.oppia.util.data.AsyncResult
import org.oppia.util.logging.Logger
import org.oppia.util.parser.ExplorationHtmlParserEntityType
import org.oppia.util.parser.HtmlParser
import javax.inject.Inject

const val STATE_FRAGMENT_EXPLORATION_ID_ARGUMENT_KEY = "STATE_FRAGMENT_EXPLORATION_ID_ARGUMENT_KEY"
Expand All @@ -52,14 +56,16 @@ private const val DEFAULT_CONTINUE_INTERACTION_TEXT_ANSWER = "Please continue."
/** The presenter for [StateFragment]. */
@FragmentScope
class StateFragmentPresenter @Inject constructor(
@ExplorationHtmlParserEntityType private val entityType: String,
private val activity: AppCompatActivity,
private val fragment: Fragment,
private val cellularDialogController: CellularDialogController,
private val stateButtonViewModelProvider: ViewModelProvider<StateButtonViewModel>,
private val viewModelProvider: ViewModelProvider<StateViewModel>,
private val explorationDataController: ExplorationDataController,
private val explorationProgressController: ExplorationProgressController,
private val logger: Logger
private val logger: Logger,
private val htmlParserFactory: HtmlParser.Factory
) : ButtonInteractionListener {

private var showCellularDataDialog = true
Expand Down Expand Up @@ -90,9 +96,8 @@ class StateFragmentPresenter @Inject constructor(
useCellularData = prefs.useCellularData
}
})

stateAdapter = StateAdapter(itemList, this as ButtonInteractionListener)

explorationId = fragment.arguments!!.getString(STATE_FRAGMENT_EXPLORATION_ID_ARGUMENT_KEY)!!
stateAdapter = StateAdapter(itemList, this as ButtonInteractionListener, htmlParserFactory, entityType, explorationId)
binding = StateFragmentBinding.inflate(inflater, container, /* attachToRoot= */ false)
binding.stateRecyclerView.apply {
adapter = stateAdapter
Expand All @@ -101,7 +106,6 @@ class StateFragmentPresenter @Inject constructor(
it.stateFragment = fragment as StateFragment
it.viewModel = getStateViewModel()
}
explorationId = checkNotNull(fragment.arguments!!.getString(STATE_FRAGMENT_EXPLORATION_ID_ARGUMENT_KEY))

subscribeToCurrentState()

Expand Down Expand Up @@ -172,6 +176,7 @@ class StateFragmentPresenter @Inject constructor(
itemList.clear()
currentEphemeralState = result

checkAndAddContentItem()
updateDummyStateName()

val interactionId = result.state.interaction.id
Expand Down Expand Up @@ -329,6 +334,27 @@ class StateFragmentPresenter @Inject constructor(
}
}

private fun checkAndAddContentItem() {
if (currentEphemeralState.state.hasContent()) {
addContentItem()
} else {
logger.e("StateFragment", "checkAndAddContentItem: State does not have content.")
}
}

private fun addContentItem() {
val contentViewModel = ContentViewModel()
val contentSubtitledHtml: SubtitledHtml = currentEphemeralState.state.content
if (contentSubtitledHtml.contentId != "") {
contentViewModel.contentId = contentSubtitledHtml.contentId
} else {
contentViewModel.contentId = "content"
}
contentViewModel.htmlContent = contentSubtitledHtml.html
itemList.add(contentViewModel)
stateAdapter.notifyDataSetChanged()
}

private fun updateNavigationButtonVisibility(
interactionId: String,
hasPreviousState: Boolean,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.oppia.app.player.state.itemviewmodel

import androidx.lifecycle.ViewModel
import org.oppia.app.fragment.FragmentScope
import javax.inject.Inject

/** [ViewModel] for content-card state. */
@FragmentScope
class ContentViewModel @Inject constructor() : ViewModel() {

var contentId = ""
var htmlContent = ""
}
23 changes: 23 additions & 0 deletions app/src/main/res/layout/content_item.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="htmlContent"
type="String"/>
</data>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="4dp"
android:paddingEnd="8dp">
<TextView
android:id="@+id/content_text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="4dp"
android:background="@drawable/content_blue_background"
android:padding="12dp"
android:text="@{htmlContent}"
android:textColor="@color/oppiaPrimaryText"
android:textSize="16sp"/>
</FrameLayout>
</layout>
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.oppia.app.player.state

import androidx.test.core.app.ActivityScenario
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.hasDescendant
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.oppia.app.R
import org.oppia.app.home.HomeActivity
import org.oppia.app.recyclerview.RecyclerViewMatcher.Companion.atPosition

// TODO(#205): Add test case for image parsing once PR #205 is merged.
/** Tests for [VIEW_TYPE_CONTENT]. */
@RunWith(AndroidJUnit4::class)
class StateFragmentContentCardTest {

@Test
fun testContentCard_forDemoExploration_withCustomOppiaTags_displaysParsedHtml() {
ActivityScenario.launch(HomeActivity::class.java).use {
onView(withId(R.id.play_exploration_button)).perform(click())
val htmlResult = "Hi, welcome to Oppia! is a tool that helps you create interactive learning activities that can be continually improved over time.\n\n" +
"Incidentally, do you know where the name 'Oppia' comes from?\n\n"
onView(atPosition(R.id.state_recycler_view, 0)).check(matches(hasDescendant(withText(htmlResult))))
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.oppia.util.parser

import javax.inject.Qualifier

/** Qualifier for injecting the entity type for exploration. */
@Qualifier
annotation class ExplorationHtmlParserEntityType
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.oppia.util.parser

import dagger.Module
import dagger.Provides

/** Provides Html parsing entity type dependencies. */
@Module
class HtmlParserEntityTypeModule {
@Provides
@ExplorationHtmlParserEntityType
fun provideExplorationHtmlParserEntityType(): String {
return "exploration"
}
}