From e74c827c1fd544140c039096cff50bfdc36076fa Mon Sep 17 00:00:00 2001 From: James Xu Date: Mon, 23 Sep 2019 16:07:01 -0700 Subject: [PATCH] Fixes part 1 of #160: initial structure for Concept Card (#148) initial structure for Concept Card --- .../oppia/app/fragment/FragmentComponent.kt | 2 + .../app/fragment/InjectableDialogFragment.kt | 24 +++++++ .../topic/conceptcard/ConceptCardFragment.kt | 35 ++++++++++ .../topic/conceptcard/ConceptCardPresenter.kt | 37 ++++++++++ .../topic/conceptcard/ConceptCardViewModel.kt | 11 +++ app/src/main/res/anim/slide_down.xml | 8 +++ app/src/main/res/anim/slide_up.xml | 8 +++ .../main/res/drawable/ic_close_white_24dp.xml | 9 +++ .../main/res/layout/conceptcard_fragment.xml | 40 +++++++++++ app/src/main/res/values/colors.xml | 3 + app/src/main/res/values/styles.xml | 17 +++++ .../conceptcard/ConceptCardFragmentTest.kt | 69 +++++++++++++++++++ 12 files changed, 263 insertions(+) create mode 100644 app/src/main/java/org/oppia/app/fragment/InjectableDialogFragment.kt create mode 100644 app/src/main/java/org/oppia/app/topic/conceptcard/ConceptCardFragment.kt create mode 100644 app/src/main/java/org/oppia/app/topic/conceptcard/ConceptCardPresenter.kt create mode 100644 app/src/main/java/org/oppia/app/topic/conceptcard/ConceptCardViewModel.kt create mode 100644 app/src/main/res/anim/slide_down.xml create mode 100644 app/src/main/res/anim/slide_up.xml create mode 100644 app/src/main/res/drawable/ic_close_white_24dp.xml create mode 100644 app/src/main/res/layout/conceptcard_fragment.xml create mode 100644 app/src/sharedTest/java/org/oppia/app/topic/conceptcard/ConceptCardFragmentTest.kt diff --git a/app/src/main/java/org/oppia/app/fragment/FragmentComponent.kt b/app/src/main/java/org/oppia/app/fragment/FragmentComponent.kt index 5392b95f3cc..7e48d0893d5 100644 --- a/app/src/main/java/org/oppia/app/fragment/FragmentComponent.kt +++ b/app/src/main/java/org/oppia/app/fragment/FragmentComponent.kt @@ -7,6 +7,7 @@ import org.oppia.app.home.HomeFragment import org.oppia.app.player.exploration.ExplorationFragment import org.oppia.app.player.state.StateFragment import org.oppia.app.player.audio.AudioFragment +import org.oppia.app.topic.conceptcard.ConceptCardFragment /** Root subcomponent for all fragments. */ @Subcomponent @@ -22,4 +23,5 @@ interface FragmentComponent { fun inject(explorationFragment: ExplorationFragment) fun inject(homeFragment: HomeFragment) fun inject(stateFragment: StateFragment) + fun inject(conceptCardFragment: ConceptCardFragment) } diff --git a/app/src/main/java/org/oppia/app/fragment/InjectableDialogFragment.kt b/app/src/main/java/org/oppia/app/fragment/InjectableDialogFragment.kt new file mode 100644 index 00000000000..601c8c6737e --- /dev/null +++ b/app/src/main/java/org/oppia/app/fragment/InjectableDialogFragment.kt @@ -0,0 +1,24 @@ +package org.oppia.app.fragment + +import android.content.Context +import androidx.fragment.app.DialogFragment +import androidx.fragment.app.Fragment +import org.oppia.app.activity.InjectableAppCompatActivity + +/** + * A fragment that facilitates field injection to children. This fragment can only be used with + * [InjectableAppCompatActivity] contexts. + */ +abstract class InjectableDialogFragment: DialogFragment() { + /** + * The [FragmentComponent] corresponding to this fragment. This cannot be used before [onAttach] is called, and can be + * used to inject lateinit fields in child fragments during fragment attachment (which is recommended to be done in an + * override of [onAttach]). + */ + lateinit var fragmentComponent: FragmentComponent + + override fun onAttach(context: Context?) { + super.onAttach(context) + fragmentComponent = (requireActivity() as InjectableAppCompatActivity).createFragmentComponent(this) + } +} 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 new file mode 100644 index 00000000000..f48309853fd --- /dev/null +++ b/app/src/main/java/org/oppia/app/topic/conceptcard/ConceptCardFragment.kt @@ -0,0 +1,35 @@ +package org.oppia.app.topic.conceptcard + +import android.content.Context +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import org.oppia.app.R +import org.oppia.app.fragment.InjectableDialogFragment +import javax.inject.Inject + +/* Fragment that displays a fullscreen dialog for concept cards */ +class ConceptCardFragment : InjectableDialogFragment() { + @Inject lateinit var conceptCardPresenter: ConceptCardPresenter + + override fun onAttach(context: Context?) { + super.onAttach(context) + fragmentComponent.inject(this) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setStyle(STYLE_NORMAL, R.style.FullScreenDialogStyle) + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + super.onCreateView(inflater, container, savedInstanceState) + return conceptCardPresenter.handleCreateView(inflater, container) + } + + override fun onStart() { + super.onStart() + dialog?.window?.setWindowAnimations(R.style.FullScreenDialogStyle) + } +} 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 new file mode 100644 index 00000000000..a3487a857a0 --- /dev/null +++ b/app/src/main/java/org/oppia/app/topic/conceptcard/ConceptCardPresenter.kt @@ -0,0 +1,37 @@ +package org.oppia.app.topic.conceptcard + +import android.view.LayoutInflater +import android.view.View +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.fragment.FragmentScope +import org.oppia.app.viewmodel.ViewModelProvider +import javax.inject.Inject + +/** Presenter for [ConceptCardFragment], sets up bindings from ViewModel */ +@FragmentScope +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) + binding.conceptCardToolbar.setNavigationIcon(R.drawable.ic_close_white_24dp) + binding.conceptCardToolbar.setNavigationOnClickListener { + (fragment as? DialogFragment)?.dismiss() + } + binding.let { + it.viewModel = getConceptCardViewModel() + it.lifecycleOwner = fragment + } + + return binding.root + } + + private fun getConceptCardViewModel(): ConceptCardViewModel { + return viewModelProvider.getForFragment(fragment, ConceptCardViewModel::class.java) + } +} 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 new file mode 100644 index 00000000000..8362ea37b6e --- /dev/null +++ b/app/src/main/java/org/oppia/app/topic/conceptcard/ConceptCardViewModel.kt @@ -0,0 +1,11 @@ +package org.oppia.app.topic.conceptcard + +import androidx.lifecycle.ViewModel +import org.oppia.app.fragment.FragmentScope +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 diff --git a/app/src/main/res/anim/slide_down.xml b/app/src/main/res/anim/slide_down.xml new file mode 100644 index 00000000000..d8f388c2e77 --- /dev/null +++ b/app/src/main/res/anim/slide_down.xml @@ -0,0 +1,8 @@ + + + + diff --git a/app/src/main/res/anim/slide_up.xml b/app/src/main/res/anim/slide_up.xml new file mode 100644 index 00000000000..a52112cb1a4 --- /dev/null +++ b/app/src/main/res/anim/slide_up.xml @@ -0,0 +1,8 @@ + + + + diff --git a/app/src/main/res/drawable/ic_close_white_24dp.xml b/app/src/main/res/drawable/ic_close_white_24dp.xml new file mode 100644 index 00000000000..9f44993f312 --- /dev/null +++ b/app/src/main/res/drawable/ic_close_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/conceptcard_fragment.xml b/app/src/main/res/layout/conceptcard_fragment.xml new file mode 100644 index 00000000000..aefaca6f1c2 --- /dev/null +++ b/app/src/main/res/layout/conceptcard_fragment.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index bd8df0f924a..1f75fbc6809 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -3,6 +3,9 @@ #008577 #00574B #D81B60 + #FFFFF0 + #C55F45 + #A6503A #26A69A #2D4A9D diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index d30d80d47b4..56954dc831d 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -6,4 +6,21 @@ @color/colorPrimaryDark @color/colorPrimary + + 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 new file mode 100644 index 00000000000..5d739a7756f --- /dev/null +++ b/app/src/sharedTest/java/org/oppia/app/topic/conceptcard/ConceptCardFragmentTest.kt @@ -0,0 +1,69 @@ +package org.oppia.app.player.audio + +import android.app.Application +import android.content.Context +import androidx.test.core.app.ActivityScenario +import androidx.test.espresso.Espresso +import androidx.test.espresso.action.ViewActions.click +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.espresso.matcher.ViewMatchers.withText +import androidx.test.ext.junit.runners.AndroidJUnit4 +import dagger.BindsInstance +import dagger.Component +import dagger.Module +import dagger.Provides +import kotlinx.coroutines.CoroutineDispatcher +import org.junit.Test +import org.junit.runner.RunWith +import org.oppia.app.R +import org.oppia.app.home.HomeActivity +import org.oppia.util.threading.BackgroundDispatcher +import org.oppia.util.threading.BlockingDispatcher +import javax.inject.Singleton + +/** Tests for [ConceptCardFragment]. */ +@RunWith(AndroidJUnit4::class) +class ConceptCardFragmentTest { + + @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 { + Espresso.onView(withId(R.id.rich_text_card)).check(matches(withText("Hello World"))) + } + } + + @Module + class TestModule { + @Provides + @Singleton + fun provideContext(application: Application): Context { + return application + } + + // TODO(#89): Introduce a proper IdlingResource for background dispatchers to ensure they all complete before + // proceeding in an Espresso test. This solution should also be interoperative with Robolectric contexts by using a + // test coroutine dispatcher. + + @Singleton + @Provides + @BackgroundDispatcher + fun provideBackgroundDispatcher(@BlockingDispatcher blockingDispatcher: CoroutineDispatcher): CoroutineDispatcher { + return blockingDispatcher + } + } + + @Singleton + @Component(modules = [TestModule::class]) + interface TestApplicationComponent { + @Component.Builder + interface Builder { + @BindsInstance + fun setApplication(application: Application): Builder + + fun build(): TestApplicationComponent + } + } +}