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 @@ + + +