Skip to content

Commit

Permalink
Fixes part 1 of #160: initial structure for Concept Card (#148)
Browse files Browse the repository at this point in the history
initial structure for Concept Card
  • Loading branch information
jamesxu0 authored Sep 23, 2019
1 parent 8fb73eb commit e74c827
Show file tree
Hide file tree
Showing 12 changed files with 263 additions and 0 deletions.
2 changes: 2 additions & 0 deletions app/src/main/java/org/oppia/app/fragment/FragmentComponent.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -22,4 +23,5 @@ interface FragmentComponent {
fun inject(explorationFragment: ExplorationFragment)
fun inject(homeFragment: HomeFragment)
fun inject(stateFragment: StateFragment)
fun inject(conceptCardFragment: ConceptCardFragment)
}
Original file line number Diff line number Diff line change
@@ -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)
}
}
Original file line number Diff line number Diff line change
@@ -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)
}
}
Original file line number Diff line number Diff line change
@@ -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<ConceptCardViewModel>
){
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)
}
}
Original file line number Diff line number Diff line change
@@ -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"
}
8 changes: 8 additions & 0 deletions app/src/main/res/anim/slide_down.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="300"
android:fromYDelta="0%p"
android:interpolator="@android:anim/accelerate_interpolator"
android:toYDelta="100%p" />
</set>
8 changes: 8 additions & 0 deletions app/src/main/res/anim/slide_up.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="300"
android:fromYDelta="100%"
android:interpolator="@android:anim/accelerate_interpolator"
android:toXDelta="0" />
</set>
9 changes: 9 additions & 0 deletions app/src/main/res/drawable/ic_close_white_24dp.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
</vector>
40 changes: 40 additions & 0 deletions app/src/main/res/layout/conceptcard_fragment.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">

<data>
<variable
name="viewModel"
type="org.oppia.app.topic.conceptcard.ConceptCardViewModel"/>
</data>

<androidx.constraintlayout.widget.ConstraintLayout
android:layout_height="match_parent"
android:layout_width="match_parent"

android:background="@color/colorConceptCardBackground">
<androidx.appcompat.widget.Toolbar
android:id="@+id/concept_card_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorConceptToolbarBackground"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toBottomOf="@+id/concept_card_toolbar"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toBottomOf="parent">
<TextView
android:id="@+id/rich_text_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@android:color/black"
android:text="@{viewModel.getDummyText()}" />
</ScrollView>

</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
3 changes: 3 additions & 0 deletions app/src/main/res/values/colors.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
<color name="colorPrimary">#008577</color>
<color name="colorPrimaryDark">#00574B</color>
<color name="colorAccent">#D81B60</color>
<color name="colorConceptCardBackground">#FFFFF0</color>
<color name="colorConceptToolbarBackground">#C55F45</color>
<color name="colorConceptToolbarHeading">#A6503A</color>
<color name="ic_launcher_background">#26A69A</color>
<!-- OPPIA COLORS -->
<color name="oppiaDarkBlue">#2D4A9D</color>
Expand Down
17 changes: 17 additions & 0 deletions app/src/main/res/values/styles.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,21 @@
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorPrimary</item>
</style>

<style name="FullScreenDialogStyle" parent="Theme.AppCompat.Dialog">
<item name="android:windowNoTitle">true</item>
<item name="colorPrimaryDark">@color/colorConceptToolbarHeading</item>

<!-- Set this to true if you want Full Screen without status bar -->
<item name="android:windowFullscreen">false</item>

<item name="android:windowIsFloating">false</item>

<!-- This is important! Don't forget to set window background -->
<item name="android:windowBackground">@color/colorConceptCardBackground</item>

<!-- Additionally if you want animations when dialog opening -->
<item name="android:windowEnterAnimation">@anim/slide_up</item>
<item name="android:windowExitAnimation">@anim/slide_down</item>
</style>
</resources>
Original file line number Diff line number Diff line change
@@ -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
}
}
}

0 comments on commit e74c827

Please sign in to comment.