Skip to content

Commit

Permalink
Fix #161: Exploration player contentcard supports rich-text part. (#275)
Browse files Browse the repository at this point in the history
* Introduce first pass interface for ExplorationProgressController.

* Fill in the stubbed logic for ExplorationProgressController. Still no
tests to verify correctness.

Also, added a method to facilitate notifying of DataProvider changes on
the UI thread.

* Fix lateinit issue in ExplorationProgressController due to wrongly ordered
initialization.

* Fix a variaty of issues in the exp progress controller, properly hook it up to
the data controller, and start adding tests.

* Created a separate ExplorationRetriever, hooked up
AnswerClassificationController, and attempted to make
ExplorationProgressController thread-safe.

The thread-safety led to significant interface changes in the progress
controller, and led to discovering some issues with the mediator live data
approach to interop coroutines and LiveData. This locking mechanism will
need to change since the optimal solution requires resolving #90.

* Change the locking mechanism for ExplorationProgressController to work
with the current MediatorLiveData implementation (see #90 for more
context). Fix existing progress controller tests and add a few more. All
current progress controller tests are passing.

* Finish tests for ExplorationProgressController and add test classification
support for the second test exploration (about_oppia).

* craeted image parser

* ObservableViewModel

* Load exploration and open ExplorationActivity

* Test case for load exploration

* fetch exploration

* Connection with StateFragment

* update

* update

* working on fetching customization_args

* Update StateFragmentPresenter.kt

* Separated this in its own PR

* Delete UrlImageParser.kt

Separated this in its own PR.

* Update StateFragmentPresenter.kt

* Update build.gradle

* Create HtmlParser.kt

* Update HtmlParser.kt

* Update HtmlParser.kt

* Update HtmlParser.kt

* working on testcases

* Update HtmlParserTestActivty.kt

* Update HtmlParserTestActivty.kt

* added testcases.

* Update AndroidManifest.xml

* Update InteractionAdapter.kt

* Update InteractionAdapter.kt

* Update HtmlParser.kt

* corrected variable names.

* updated variables.

* Update HtmlParser.kt

* Update HtmlParser.kt

* Update HtmlParserTestActivty.kt

* Update HtmlParser.kt

* Delete InteractionAdapter.kt

* deteted unused xml files

* merged html code.

* Update HtmlParser.kt

* Delete UrlImageParserTest.kt

* used dagger injection for UrlImageParser.

* Update HtmlParserTestActivty.kt

* Update HtmlParserTestActivty.kt

* Update HtmlParser.kt

* Update HtmlParserTestActivty.kt

* updated tests

* updated test

* moved to testing package

* Update HtmlParserTestActivty.kt

* Update HtmlParserTestActivty.kt

* fix issues

* fix issues.

* fixed issues

* fixed issues.

* Update activity_test_html_parser.xml

* fixes

* Update HtmlParserTest.kt

* Update HtmlParserTest.kt

* fixed issues.

* fixing issues.

* made use of factory

* Update HtmlParserTest.kt

* Update UrlImageParser.kt

* Update AndroidManifest.xml

* Update HtmlParserTestActivity.kt

* nit

* Update AudioFragmentTest.kt

* updated

* fixes

* Update UrlImageParser.kt

* Not used in this.

* fixes

* Not used.

* Update CellularDataDialogFragmentTest.kt

* reverted

* Update CellularDataDialogFragmentTest.kt

* fixes

* Update ImageLoader.kt

* Update HtmlParser.kt

* Update HtmlParser.kt

* Update build.gradle

* fixed issues.

* Update UrlImageParser.kt

* Update UrlImageParser.kt

* Update UrlImageParser.kt

* Update ImageParsingAnnotations.kt

* working on image loader

* Injected ImageLoader

* updated image loader.

* Update ActivityComponent.kt

* Update HtmlParserTestActivity.kt

* Update GlideImageLoader.kt

* removed annotation

* Update HtmlParser.kt

* line break

* fix

* fix comments

* Update UrlImageParser.kt

* Update UrlImageParser.kt

* injected context

* working on testcase.

* testcase added

* added test case

* testcases

* Update build.gradle

* Update AndroidManifest.xml

* fixes

* Update UrlImageParserTest.kt

* changed to CutomTarget

* Update UrlImageParserTest.kt

* updated.

* Update UrlImageParserTest.kt

* Update HtmlParserTestActivity.kt

* Update misc.xml

* Update GlideImageLoaderModule.kt

* updated with image parser

* Update StateFragmentContentCardTest.kt

* Update HtmlParserTest.kt

* deleted test class for loading image and added todo

* updated

* Update build.gradle

* Update GlideImageLoader.kt

* Update build.gradle

* Update UrlImageParser.kt

* updated.

* update

* update

* Update StateFragmentPresenter.kt

* ContentCardTestActivity introduced

* updated test.

* Update AndroidManifest.xml

* Update AndroidManifest.xml

* Update StateFragmentContentCardTest.kt

* renamed file.

* Update state_fragment.xml

* moved test case to stateFragmentTest

* Update StateFragmentTest.kt

*  changes.

* Update content_item.xml

* Update ContentViewModel.kt

* Update StateFragmentTest.kt

* KDoc added
  • Loading branch information
veena14cs authored and rt4914 committed Nov 5, 2019
1 parent b1f8e8f commit a50d6d8
Show file tree
Hide file tree
Showing 13 changed files with 221 additions and 9 deletions.
1 change: 1 addition & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
android:supportsRtl="true"
android:theme="@style/OppiaTheme">
<activity android:name=".topic.conceptcard.testing.ConceptCardFragmentTestActivity" />
<activity android:name=".testing.ContentCardTestActivity" />
<activity android:name=".player.state.testing.StateFragmentTestActivity" />
<activity android:name=".player.exploration.ExplorationActivity" />
<activity android:name=".topic.questionplayer.QuestionPlayerActivity" />
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/java/org/oppia/app/activity/ActivityComponent.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import org.oppia.app.player.state.testing.StateFragmentTestActivity
import org.oppia.app.profile.ProfileActivity
import org.oppia.app.story.StoryActivity
import org.oppia.app.testing.BindableAdapterTestActivity
import org.oppia.app.testing.ContentCardTestActivity
import org.oppia.app.testing.HtmlParserTestActivity
import org.oppia.app.topic.TopicActivity
import org.oppia.app.topic.questionplayer.QuestionPlayerActivity
Expand All @@ -32,6 +33,7 @@ interface ActivityComponent {
fun inject(audioFragmentTestActivity: AudioFragmentTestActivity)
fun inject(bindableAdapterTestActivity: BindableAdapterTestActivity)
fun inject(conceptCardFragmentTestActivity: ConceptCardFragmentTestActivity)
fun inject(contentCardTestActivity: ContentCardTestActivity)
fun inject(explorationActivity: ExplorationActivity)
fun inject(homeActivity: HomeActivity)
fun inject(htmlParserTestActivty: HtmlParserTestActivity)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import org.oppia.domain.classify.rules.textinput.TextInputRuleModule
import org.oppia.util.gcsresource.GcsResourceModule
import org.oppia.util.logging.LoggerModule
import org.oppia.util.parser.GlideImageLoaderModule
import org.oppia.util.parser.HtmlParserEntityTypeModule
import org.oppia.util.parser.ImageParsingModule
import org.oppia.util.threading.DispatcherModule
import javax.inject.Provider
Expand All @@ -27,7 +28,8 @@ import javax.inject.Singleton
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
GcsResourceModule::class, ImageParsingModule::class, GlideImageLoaderModule::class,
HtmlParserEntityTypeModule::class
])
interface ApplicationComponent {
@Component.Builder
Expand Down
39 changes: 37 additions & 2 deletions app/src/main/java/org/oppia/app/player/state/StateAdapter.kt
100644 → 100755
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.*
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,7 +30,10 @@ 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>() {

Expand All @@ -45,7 +53,18 @@ class StateAdapter(
)
StateButtonViewHolder(binding, buttonInteractionListener)
}
else -> throw IllegalArgumentException("Invalid view type") as Throwable
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")
}
}

Expand All @@ -54,11 +73,15 @@ 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 ContentViewModel -> VIEW_TYPE_CONTENT
is StateButtonViewModel -> {
stateButtonViewModel = itemList[position] as StateButtonViewModel
VIEW_TYPE_STATE_BUTTON
Expand All @@ -71,6 +94,18 @@ class StateAdapter(
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 Down
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,9 @@ 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 +107,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 @@ -171,7 +176,7 @@ class StateFragmentPresenter @Inject constructor(
ephemeralStateLiveData.observe(fragment, Observer<EphemeralState> { result ->
itemList.clear()
currentEphemeralState = result

checkAndAddContentItem()
updateDummyStateName()

val interactionId = result.state.interaction.id
Expand Down Expand Up @@ -328,6 +333,23 @@ class StateFragmentPresenter @Inject constructor(
oldStateNameList.add(currentEphemeralState.state.name)
}
}

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
contentViewModel.contentId = contentSubtitledHtml.contentId
contentViewModel.htmlContent = contentSubtitledHtml.html
itemList.add(contentViewModel)
stateAdapter.notifyDataSetChanged()
}

private fun updateNavigationButtonVisibility(
interactionId: String,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
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 = ""
}
17 changes: 17 additions & 0 deletions app/src/main/java/org/oppia/app/testing/ContentCardTestActivity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.oppia.app.testing

import android.os.Bundle
import org.oppia.app.activity.InjectableAppCompatActivity
import javax.inject.Inject

/** Activity to test the functionality of content-card used in [StateFragment]. */
class ContentCardTestActivity : InjectableAppCompatActivity() {
@Inject
lateinit var contentCardTestPresenter: ContentCardTestActivityPresenter

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activityComponent.inject(this)
contentCardTestPresenter.handleOnCreate()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package org.oppia.app.testing

import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer
import org.oppia.app.R
import org.oppia.app.activity.ActivityScope
import org.oppia.app.player.state.StateFragment
import org.oppia.domain.exploration.ExplorationDataController
import org.oppia.domain.exploration.TEST_EXPLORATION_ID_5
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

/** The presenter for [ContentCardTestActivity]. */
@ActivityScope
class ContentCardTestActivityPresenter @Inject constructor(
private val activity: AppCompatActivity,
private val explorationDataController: ExplorationDataController,
private val logger: Logger
) {
fun handleOnCreate() {
activity.setContentView(R.layout.content_card_test_activity)
loadDummyExplorationAtStart()
}

private fun getStateFragment(): StateFragment? {
return activity.supportFragmentManager.findFragmentById(R.id.state_fragment_placeholder) as StateFragment?
}

private fun loadDummyExplorationAtStart() {
explorationDataController.startPlayingExploration(
EXPLORATION_ID
).observe(activity, Observer<AsyncResult<Any?>> { result ->
when {
result.isPending() -> logger.d("ContentCardTest", "Loading exploration")
result.isFailure() -> logger.e("ContentCardTest", "Failed to load exploration", result.getErrorOrNull()!!)
else -> {
logger.d("ContentCardTest", "Successfully loaded exploration")

if (getStateFragment() == null) {
val stateFragment = StateFragment.newInstance(EXPLORATION_ID)
activity.supportFragmentManager.beginTransaction().add(
R.id.state_fragment_placeholder,
stateFragment
).commitNow()
}
}
}
})
}
}
7 changes: 7 additions & 0 deletions app/src/main/res/layout/content_card_test_activity.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/state_fragment_placeholder"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".testing.ContentCardTestActivity" />
28 changes: 28 additions & 0 deletions app/src/main/res/layout/content_item.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<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:paddingStart="8dp"
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
Expand Up @@ -23,6 +23,7 @@ import dagger.Component
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.CoroutineDispatcher
import org.hamcrest.CoreMatchers.containsString
import org.hamcrest.CoreMatchers.not
import org.junit.After
import org.junit.Before
Expand All @@ -35,6 +36,7 @@ import org.oppia.app.player.exploration.ExplorationActivity
import org.oppia.app.player.state.testing.StateFragmentTestActivity
import org.oppia.app.recyclerview.RecyclerViewMatcher.Companion.atPosition
import org.oppia.app.recyclerview.RecyclerViewMatcher.Companion.atPositionOnView
import org.oppia.app.testing.ContentCardTestActivity
import org.oppia.util.threading.BackgroundDispatcher
import org.oppia.util.threading.BlockingDispatcher
import javax.inject.Singleton
Expand Down Expand Up @@ -364,6 +366,16 @@ class StateFragmentTest {
intended(hasComponent(HomeActivity::class.java.name))
}

@Test
fun testContentCard_forDemoExploration_withCustomOppiaTags_displaysParsedHtml() {
ActivityScenario.launch(ContentCardTestActivity::class.java).use {
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?"
onView(atPositionOnView(R.id.state_recycler_view, 0,R.id.content_text_view)).check(matches(hasDescendant(withText(htmlResult))))
}
}

@After
fun tearDown() {
Intents.release()
Expand Down
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
Loading

0 comments on commit a50d6d8

Please sign in to comment.