diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 145a22f5d93..23f858a90b0 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -10,10 +10,11 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/OppiaTheme">
+
+
-
diff --git a/app/src/main/java/org/oppia/app/activity/ActivityComponent.kt b/app/src/main/java/org/oppia/app/activity/ActivityComponent.kt
index 05c42ee16ad..80c035c8a9a 100644
--- a/app/src/main/java/org/oppia/app/activity/ActivityComponent.kt
+++ b/app/src/main/java/org/oppia/app/activity/ActivityComponent.kt
@@ -6,6 +6,7 @@ import dagger.Subcomponent
import org.oppia.app.fragment.FragmentComponent
import org.oppia.app.home.HomeActivity
import org.oppia.app.player.exploration.ExplorationActivity
+import org.oppia.app.topic.conceptcard.testing.ConceptCardFragmentTestActivity
import org.oppia.app.player.state.testing.StateFragmentTestActivity
import org.oppia.app.testing.BindableAdapterTestActivity
import org.oppia.app.topic.TopicActivity
@@ -27,6 +28,7 @@ interface ActivityComponent {
fun inject(bindableAdapterTestActivity: BindableAdapterTestActivity)
fun inject(explorationActivity: ExplorationActivity)
fun inject(homeActivity: HomeActivity)
+ fun inject(conceptCardFragmentTestActivity: ConceptCardFragmentTestActivity)
fun inject(questionPlayerActivity: QuestionPlayerActivity)
fun inject(topicActivity: TopicActivity)
fun inject(stateFragmentTestActivity: StateFragmentTestActivity)
diff --git a/app/src/main/java/org/oppia/app/topic/conceptcard/ConceptCardFragment.kt b/app/src/main/java/org/oppia/app/topic/conceptcard/ConceptCardFragment.kt
index f48309853fd..a9c7b5f4c41 100644
--- a/app/src/main/java/org/oppia/app/topic/conceptcard/ConceptCardFragment.kt
+++ b/app/src/main/java/org/oppia/app/topic/conceptcard/ConceptCardFragment.kt
@@ -9,8 +9,26 @@ import org.oppia.app.R
import org.oppia.app.fragment.InjectableDialogFragment
import javax.inject.Inject
+private const val KEY_SKILL_ID = "SKILL_ID"
+
/* Fragment that displays a fullscreen dialog for concept cards */
class ConceptCardFragment : InjectableDialogFragment() {
+
+ companion object {
+ /**
+ * Creates a new instance of a DialogFragment to display content
+ * @param skillId Used in TopicController to get correct concept card data.
+ * @return [ConceptCardFragment]: DialogFragment
+ */
+ fun newInstance(skillId: String): ConceptCardFragment {
+ val conceptCardFrag = ConceptCardFragment()
+ val args = Bundle()
+ args.putString(KEY_SKILL_ID, skillId)
+ conceptCardFrag.arguments = args
+ return conceptCardFrag
+ }
+ }
+
@Inject lateinit var conceptCardPresenter: ConceptCardPresenter
override fun onAttach(context: Context?) {
@@ -25,7 +43,9 @@ class ConceptCardFragment : InjectableDialogFragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
super.onCreateView(inflater, container, savedInstanceState)
- return conceptCardPresenter.handleCreateView(inflater, container)
+ val args = checkNotNull(arguments) { "Expected arguments to be passed to ConceptCardFragment" }
+ val skillId = checkNotNull(args.getString(KEY_SKILL_ID)) { "Expected skillId to be passed to ConceptCardFragment" }
+ return conceptCardPresenter.handleCreateView(inflater, container, skillId)
}
override fun onStart() {
diff --git a/app/src/main/java/org/oppia/app/topic/conceptcard/ConceptCardPresenter.kt b/app/src/main/java/org/oppia/app/topic/conceptcard/ConceptCardPresenter.kt
index a3487a857a0..7b70abf9c6c 100644
--- a/app/src/main/java/org/oppia/app/topic/conceptcard/ConceptCardPresenter.kt
+++ b/app/src/main/java/org/oppia/app/topic/conceptcard/ConceptCardPresenter.kt
@@ -6,8 +6,11 @@ import android.view.ViewGroup
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import org.oppia.app.R
-import org.oppia.app.databinding.ConceptcardFragmentBinding
+import org.oppia.app.databinding.ConceptCardExampleViewBinding
+import org.oppia.app.databinding.ConceptCardFragmentBinding
import org.oppia.app.fragment.FragmentScope
+import org.oppia.app.model.SubtitledHtml
+import org.oppia.app.recyclerview.BindableAdapter
import org.oppia.app.viewmodel.ViewModelProvider
import javax.inject.Inject
@@ -17,21 +20,37 @@ class ConceptCardPresenter @Inject constructor(
private val fragment: Fragment,
private val viewModelProvider: ViewModelProvider
){
- fun handleCreateView(inflater: LayoutInflater, container: ViewGroup?): View? {
- val binding = ConceptcardFragmentBinding.inflate(inflater, container, /* attachToRoot= */ false)
+
+ /** Sets up data binding and adapter for RecyclerView */
+ fun handleCreateView(inflater: LayoutInflater, container: ViewGroup?, skillId: String): View? {
+ val viewModel = getConceptCardViewModel()
+ viewModel.setSkillId(skillId)
+ val binding = ConceptCardFragmentBinding.inflate(inflater, container, /* attachToRoot= */ false)
binding.conceptCardToolbar.setNavigationIcon(R.drawable.ic_close_white_24dp)
binding.conceptCardToolbar.setNavigationOnClickListener {
(fragment as? DialogFragment)?.dismiss()
}
+ binding.workedExamples.apply {
+ adapter = createRecyclerViewAdapter()
+ }
+
binding.let {
- it.viewModel = getConceptCardViewModel()
+ it.viewModel = viewModel
it.lifecycleOwner = fragment
}
-
return binding.root
}
private fun getConceptCardViewModel(): ConceptCardViewModel {
return viewModelProvider.getForFragment(fragment, ConceptCardViewModel::class.java)
}
+
+ private fun createRecyclerViewAdapter(): BindableAdapter {
+ return BindableAdapter.Builder
+ .newBuilder()
+ .registerViewDataBinder(
+ inflateDataBinding = ConceptCardExampleViewBinding::inflate,
+ setViewModel = ConceptCardExampleViewBinding::setSubtitledHtml)
+ .build()
+ }
}
diff --git a/app/src/main/java/org/oppia/app/topic/conceptcard/ConceptCardViewModel.kt b/app/src/main/java/org/oppia/app/topic/conceptcard/ConceptCardViewModel.kt
index 8362ea37b6e..73a3822f1d3 100644
--- a/app/src/main/java/org/oppia/app/topic/conceptcard/ConceptCardViewModel.kt
+++ b/app/src/main/java/org/oppia/app/topic/conceptcard/ConceptCardViewModel.kt
@@ -1,11 +1,63 @@
package org.oppia.app.topic.conceptcard
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.Transformations
import androidx.lifecycle.ViewModel
import org.oppia.app.fragment.FragmentScope
+import org.oppia.app.model.ConceptCard
+import org.oppia.app.model.SubtitledHtml
+import org.oppia.domain.topic.TopicController
+import org.oppia.util.data.AsyncResult
+import org.oppia.util.logging.Logger
import javax.inject.Inject
/** [ViewModel] for concept card, providing rich text and worked examples */
@FragmentScope
-class ConceptCardViewModel @Inject constructor() : ViewModel() {
- fun getDummyText() = "hello world"
-}
\ No newline at end of file
+class ConceptCardViewModel @Inject constructor(
+ private val topicController: TopicController,
+ private val logger: Logger
+) : ViewModel() {
+
+ private lateinit var skillId: String
+
+ /** Live Data for concept card explanation */
+ val conceptCardLiveData: LiveData by lazy {
+ processConceptCardLiveData()
+ }
+
+ /** Live Data for concept card worked examples. */
+ val workedExamplesLiveData: LiveData> by lazy {
+ processWorkedExamplesLiveData()
+ }
+
+ /** Sets the value of skillId. Must be called before setting ViewModel to binding. */
+ fun setSkillId(id: String) {
+ skillId = id
+ }
+
+ private val conceptCardResultLiveData: LiveData> by lazy {
+ topicController.getConceptCard(skillId)
+ }
+
+ private fun processConceptCardLiveData(): LiveData {
+ return Transformations.map(conceptCardResultLiveData, ::processConceptCardResult)
+ }
+
+ private fun processWorkedExamplesLiveData(): LiveData> {
+ return Transformations.map(conceptCardResultLiveData, ::processConceptCardWorkExamples)
+ }
+
+ private fun processConceptCardResult(conceptCardResult: AsyncResult): ConceptCard {
+ if (conceptCardResult.isFailure()) {
+ logger.e("ConceptCardFragment", "Failed to retrieve Concept Card: " + conceptCardResult.getErrorOrNull())
+ }
+ return conceptCardResult.getOrDefault(ConceptCard.getDefaultInstance())
+ }
+
+ private fun processConceptCardWorkExamples(conceptCardResult: AsyncResult): List {
+ if (conceptCardResult.isFailure()) {
+ logger.e("ConceptCardFragment", "Failed to retrieve Concept Card: " + conceptCardResult.getErrorOrNull())
+ }
+ return conceptCardResult.getOrDefault(ConceptCard.getDefaultInstance()).workedExampleList
+ }
+}
diff --git a/app/src/main/java/org/oppia/app/topic/conceptcard/testing/ConceptCardFragmentTestActivity.kt b/app/src/main/java/org/oppia/app/topic/conceptcard/testing/ConceptCardFragmentTestActivity.kt
new file mode 100644
index 00000000000..24b6055970c
--- /dev/null
+++ b/app/src/main/java/org/oppia/app/topic/conceptcard/testing/ConceptCardFragmentTestActivity.kt
@@ -0,0 +1,17 @@
+package org.oppia.app.topic.conceptcard.testing
+
+import android.os.Bundle
+import org.oppia.app.activity.InjectableAppCompatActivity
+import javax.inject.Inject
+
+/** Test Activity used for testing ConceptCardFragment */
+class ConceptCardFragmentTestActivity : InjectableAppCompatActivity() {
+
+ @Inject lateinit var conceptCardFragmentTestActivityController: ConceptCardFragmentTestActivityPresenter
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ activityComponent.inject(this)
+ conceptCardFragmentTestActivityController.handleOnCreate()
+ }
+}
diff --git a/app/src/main/java/org/oppia/app/topic/conceptcard/testing/ConceptCardFragmentTestActivityPresenter.kt b/app/src/main/java/org/oppia/app/topic/conceptcard/testing/ConceptCardFragmentTestActivityPresenter.kt
new file mode 100644
index 00000000000..68d16f32bf4
--- /dev/null
+++ b/app/src/main/java/org/oppia/app/topic/conceptcard/testing/ConceptCardFragmentTestActivityPresenter.kt
@@ -0,0 +1,26 @@
+package org.oppia.app.topic.conceptcard.testing
+
+import androidx.appcompat.app.AppCompatActivity
+import kotlinx.android.synthetic.main.concept_card_fragment_test_activity.*
+import org.oppia.app.R
+import org.oppia.app.topic.conceptcard.ConceptCardFragment
+import org.oppia.domain.topic.TEST_SKILL_ID_1
+import org.oppia.domain.topic.TEST_SKILL_ID_2
+import javax.inject.Inject
+
+private const val TAG_CONCEPT_CARD_DIALOG = "CONCEPT_CARD_DIALOG"
+
+/** The presenter for [ConceptCardFragmentTestActivity] */
+class ConceptCardFragmentTestActivityPresenter @Inject constructor(private val activity: AppCompatActivity) {
+ fun handleOnCreate() {
+ activity.setContentView(R.layout.concept_card_fragment_test_activity)
+ activity.open_dialog_1.setOnClickListener {
+ val frag = ConceptCardFragment.newInstance(TEST_SKILL_ID_1)
+ frag.showNow(activity.supportFragmentManager, TAG_CONCEPT_CARD_DIALOG)
+ }
+ activity.open_dialog_2.setOnClickListener {
+ val frag = ConceptCardFragment.newInstance(TEST_SKILL_ID_2)
+ frag.showNow(activity.supportFragmentManager, TAG_CONCEPT_CARD_DIALOG)
+ }
+ }
+}
diff --git a/app/src/main/res/layout/concept_card_example_view.xml b/app/src/main/res/layout/concept_card_example_view.xml
new file mode 100644
index 00000000000..a903c7e0c76
--- /dev/null
+++ b/app/src/main/res/layout/concept_card_example_view.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/conceptcard_fragment.xml b/app/src/main/res/layout/concept_card_fragment.xml
similarity index 58%
rename from app/src/main/res/layout/conceptcard_fragment.xml
rename to app/src/main/res/layout/concept_card_fragment.xml
index aefaca6f1c2..fe81eacd92b 100644
--- a/app/src/main/res/layout/conceptcard_fragment.xml
+++ b/app/src/main/res/layout/concept_card_fragment.xml
@@ -1,17 +1,15 @@
-
+
-
-
+ android:orientation="vertical"
+ android:paddingTop="10dp">
+
+
+
-
-
\ No newline at end of file
+
diff --git a/app/src/main/res/layout/concept_card_fragment_test_activity.xml b/app/src/main/res/layout/concept_card_fragment_test_activity.xml
new file mode 100644
index 00000000000..8505adfd9d1
--- /dev/null
+++ b/app/src/main/res/layout/concept_card_fragment_test_activity.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
diff --git a/app/src/sharedTest/java/org/oppia/app/topic/conceptcard/ConceptCardFragmentTest.kt b/app/src/sharedTest/java/org/oppia/app/topic/conceptcard/ConceptCardFragmentTest.kt
index 7a02c220535..33678f58ab7 100644
--- a/app/src/sharedTest/java/org/oppia/app/topic/conceptcard/ConceptCardFragmentTest.kt
+++ b/app/src/sharedTest/java/org/oppia/app/topic/conceptcard/ConceptCardFragmentTest.kt
@@ -2,21 +2,25 @@ package org.oppia.app.topic.conceptcard
import android.app.Application
import android.content.Context
+import android.content.res.Configuration
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.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import dagger.BindsInstance
import dagger.Component
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.CoroutineDispatcher
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.oppia.app.R
-import org.oppia.app.home.HomeActivity
+import org.oppia.app.topic.conceptcard.testing.ConceptCardFragmentTestActivity
import org.oppia.util.threading.BackgroundDispatcher
import org.oppia.util.threading.BlockingDispatcher
import javax.inject.Singleton
@@ -25,12 +29,47 @@ import javax.inject.Singleton
@RunWith(AndroidJUnit4::class)
class ConceptCardFragmentTest {
+ private lateinit var activityScenario: ActivityScenario
+
+ @Before
+ fun setUp() {
+ activityScenario = ActivityScenario.launch(ConceptCardFragmentTestActivity::class.java)
+ }
+
+ @Test
+ fun testConceptCardFragment_openDialogFragmentWithSkill1_explanationIsDisplayed() {
+ onView(withId(R.id.open_dialog_1)).perform(click())
+ onView(withId(R.id.explanation)).check(matches(withText("Explanation with rich text.")))
+ }
+
+ @Test
+ fun testConceptCardFragment_openDialogFragmentWithSkill1_workedExamplesAreDisplayed() {
+ onView(withId(R.id.open_dialog_1)).perform(click())
+ onView(withText("Worked example with rich text.")).check(matches(isDisplayed()))
+ }
+
+ @Test
+ fun testConceptCardFragment_openDialogFragmentWithSkill2_explanationIsDisplayed() {
+ onView(withId(R.id.open_dialog_2)).perform(click())
+ onView(withId(R.id.explanation)).check(matches(withText("Explanation without rich text.")))
+ }
+
+ @Test
+ fun testConceptCardFragment_openDialogFragmentWithSkill2_workedExamplesAreDisplayed() {
+ onView(withId(R.id.open_dialog_2)).perform(click())
+ onView(withText("Worked example without rich text.")).check(matches(isDisplayed()))
+ onView(withText("Second worked example.")).check(matches(isDisplayed()))
+ }
+
@Test
- fun testConceptCardFragment_loadFragment_textIsDisplayed() {
- // I'm not sure how to launch just the fragment because it doesn't have its own activity
- ActivityScenario.launch(HomeActivity::class.java).use {
- onView(withId(R.id.rich_text_card)).check(matches(withText("Hello World")))
+ fun testConceptCardFragment_openDialogFragmentWithSkill2_afterConfigurationChange_workedExamplesAreDisplayed() {
+ onView(withId(R.id.open_dialog_2)).perform(click())
+ activityScenario.onActivity { activity ->
+ activity.requestedOrientation = Configuration.ORIENTATION_LANDSCAPE
}
+ activityScenario.recreate()
+ onView(withText("Worked example without rich text.")).check(matches(isDisplayed()))
+ onView(withText("Second worked example.")).check(matches(isDisplayed()))
}
@Module