Skip to content

Commit

Permalink
Fix #142: Topic review tab low fi (#224)
Browse files Browse the repository at this point in the history
* XML files

* Basic structure

* Review funtional implementation

* Test case added

* Test case added

* Final ui

* Nit changes

* Configuration change test case added

* Removed TopicReviewViewModel

* Test cases updated

* Nit changes

* Glide library added

* Nit changes

* Test-case changes

* Test-case changes

* Linked to ConceptCardFragment

* ImageBindingAdapter changes

* Updated test-cases

* Updated test-cases
  • Loading branch information
rt4914 authored Oct 24, 2019
1 parent 901633d commit d5f0e12
Show file tree
Hide file tree
Showing 16 changed files with 339 additions and 20 deletions.
1 change: 1 addition & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ dependencies {
'androidx.lifecycle:lifecycle-runtime-ktx:2.2.0-alpha03',
'androidx.multidex:multidex:2.0.1',
'androidx.recyclerview:recyclerview:1.0.0',
'com.github.bumptech.glide:glide:4.9.0',
'com.google.dagger:dagger:2.24',
'com.google.guava:guava:28.1-android',
"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package org.oppia.app.databinding

import android.widget.ImageView
import androidx.annotation.DrawableRes
import androidx.databinding.BindingAdapter
import com.bumptech.glide.request.RequestOptions
import com.bumptech.glide.Glide
import org.oppia.app.R
import org.oppia.app.model.LessonThumbnailGraphic

/**
* Allows binding drawables to an [ImageView] via "android:src". Source: https://stackoverflow.com/a/35809319/3689782.
*/
@BindingAdapter("android:src")
fun setImageDrawable(imageView: ImageView, imageUrl: String) {
val requestOptions = RequestOptions().placeholder(R.drawable.review_placeholder)

Glide.with(imageView.context)
.load(imageUrl)
.apply(requestOptions)
.into(imageView)
}

/**
* Allows binding drawables to an [ImageView] via "android:src". Source: https://stackoverflow.com/a/35809319/3689782.
*/
@BindingAdapter("android:src")
fun setImageDrawable(imageView: ImageView, @DrawableRes drawableResourceId: Int) {
imageView.setImageResource(drawableResourceId)
}

/**
* Binds the specified [LessonThumbnailGraphic] as the source for the [ImageView]. The view should be specified to have
* no width/height (when sized in a constraint layout), and use centerCrop for the image to appear correctly.
*/
@BindingAdapter("android:src")
fun setImageDrawable(imageView: ImageView, thumbnailGraphic: LessonThumbnailGraphic) {
setImageDrawable(imageView, when (thumbnailGraphic) {
LessonThumbnailGraphic.BAKER -> R.drawable.lesson_thumbnail_graphic_baker
LessonThumbnailGraphic.CHILD_WITH_BOOK -> R.drawable.lesson_thumbnail_graphic_child_with_book
LessonThumbnailGraphic.CHILD_WITH_CUPCAKES -> R.drawable.lesson_thumbnail_graphic_child_with_cupcakes
LessonThumbnailGraphic.CHILD_WITH_FRACTIONS_HOMEWORK ->
R.drawable.lesson_thumbnail_graphic_child_with_fractions_homework
LessonThumbnailGraphic.DUCK_AND_CHICKEN -> R.drawable.lesson_thumbnail_graphic_duck_and_chicken
LessonThumbnailGraphic.PERSON_WITH_PIE_CHART -> R.drawable.lesson_thumbnail_graphic_person_with_pie_chart
else -> R.drawable.lesson_thumbnail_default
})
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package org.oppia.app.topic

/** Listener for when an [TopicActivity] should route to a [ConceptCardFragment]. */
interface RouteToConceptCardListener {
fun routeToConceptCard(skillId: String)
}
23 changes: 21 additions & 2 deletions app/src/main/java/org/oppia/app/topic/TopicActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@ package org.oppia.app.topic

import android.os.Bundle
import org.oppia.app.activity.InjectableAppCompatActivity
import org.oppia.app.topic.conceptcard.ConceptCardFragment
import org.oppia.app.topic.questionplayer.QuestionPlayerActivity
import javax.inject.Inject

//internal const val TAG_CONCEPT_CARD_DIALOG = "CONCEPT_CARD_DIALOG"

/** The activity for tabs in Topic. */
class TopicActivity : InjectableAppCompatActivity(), RouteToQuestionPlayerListener {
@Inject lateinit var topicActivityPresenter: TopicActivityPresenter
class TopicActivity : InjectableAppCompatActivity(), RouteToQuestionPlayerListener, RouteToConceptCardListener {
@Inject
lateinit var topicActivityPresenter: TopicActivityPresenter

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Expand All @@ -18,4 +22,19 @@ class TopicActivity : InjectableAppCompatActivity(), RouteToQuestionPlayerListen
override fun routeToQuestionPlayer(skillIdList: ArrayList<String>) {
startActivity(QuestionPlayerActivity.createQuestionPlayerActivityIntent(this, skillIdList))
}

override fun routeToConceptCard(skillId: String) {
if (getConceptCardFragment() == null) {
val conceptCardFragment: ConceptCardFragment = ConceptCardFragment.newInstance(skillId)
conceptCardFragment.showNow(supportFragmentManager, TAG_CONCEPT_CARD_DIALOG)
}
}

private fun getConceptCardFragment(): ConceptCardFragment? {
return supportFragmentManager.findFragmentByTag(TAG_CONCEPT_CARD_DIALOG) as ConceptCardFragment?
}

companion object {
internal const val TAG_CONCEPT_CARD_DIALOG = "CONCEPT_CARD_DIALOG"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package org.oppia.app.topic.review

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.databinding.library.baseAdapters.BR
import androidx.recyclerview.widget.RecyclerView
import org.oppia.app.databinding.TopicReviewSummaryViewBinding
import org.oppia.app.model.SkillSummary
import org.oppia.app.R

// TODO(#216): Make use of generic data-binding-enabled RecyclerView adapter.
/** Adapter to bind skills to [RecyclerView] inside [TopicReviewFragment]. */
class ReviewSkillSelectionAdapter(private val reviewSkillSelector: ReviewSkillSelector) :
RecyclerView.Adapter<ReviewSkillSelectionAdapter.SkillViewHolder>() {

private var skillList: List<SkillSummary> = ArrayList()

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SkillViewHolder {
val skillListItemBinding = DataBindingUtil.inflate<TopicReviewSummaryViewBinding>(
LayoutInflater.from(parent.context),
R.layout.topic_review_summary_view, parent,
/* attachToRoot= */ false
)
return SkillViewHolder(skillListItemBinding)
}

override fun onBindViewHolder(skillViewHolder: SkillViewHolder, i: Int) {
skillViewHolder.bind(skillList[i], i)
}

override fun getItemCount(): Int {
return skillList.size
}

fun setSkillList(skillList: List<SkillSummary>) {
this.skillList = skillList
notifyDataSetChanged()
}

inner class SkillViewHolder(val binding: TopicReviewSummaryViewBinding) : RecyclerView.ViewHolder(binding.root) {
internal fun bind(skill: SkillSummary, @Suppress("UNUSED_PARAMETER") position: Int) {
binding.setVariable(BR.skill, skill)
binding.root.setOnClickListener {
reviewSkillSelector.onTopicReviewSummaryClicked(skill)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.oppia.app.topic.review

import org.oppia.app.model.SkillSummary

/** Listener for when a skill is selected for review. */
interface ReviewSkillSelector {
fun onTopicReviewSummaryClicked(skillSummary: SkillSummary)
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import org.oppia.app.fragment.InjectableFragment
import org.oppia.app.model.SkillSummary
import javax.inject.Inject

/** Fragment that card for topic review. */
class TopicReviewFragment : InjectableFragment() {
class TopicReviewFragment : InjectableFragment(), ReviewSkillSelector {
@Inject
lateinit var topicReviewFragmentPresenter: TopicReviewFragmentPresenter

Expand All @@ -21,4 +22,8 @@ class TopicReviewFragment : InjectableFragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return topicReviewFragmentPresenter.handleCreateView(inflater, container)
}

override fun onTopicReviewSummaryClicked(skillSummary: SkillSummary) {
topicReviewFragmentPresenter.onTopicReviewSummaryClicked(skillSummary)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,77 @@ package org.oppia.app.topic.review
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import androidx.lifecycle.Transformations
import androidx.recyclerview.widget.GridLayoutManager
import org.oppia.app.databinding.TopicReviewFragmentBinding
import org.oppia.app.fragment.FragmentScope
import org.oppia.app.model.SkillSummary
import org.oppia.app.model.Topic
import org.oppia.app.topic.RouteToConceptCardListener
import org.oppia.domain.topic.TEST_TOPIC_ID_0
import org.oppia.domain.topic.TopicController
import org.oppia.util.data.AsyncResult
import org.oppia.util.logging.Logger
import javax.inject.Inject

/** The presenter for [TopicReviewFragment]. */
@FragmentScope
class TopicReviewFragmentPresenter @Inject constructor(
private val fragment: Fragment
) {
activity: AppCompatActivity,
private val fragment: Fragment,
private val logger: Logger,
private val topicController: TopicController
) : ReviewSkillSelector {

private val routeToReviewListener = activity as RouteToConceptCardListener

private lateinit var reviewSkillSelectionAdapter: ReviewSkillSelectionAdapter

fun handleCreateView(inflater: LayoutInflater, container: ViewGroup?): View? {
val binding = TopicReviewFragmentBinding.inflate(inflater, container, /* attachToRoot= */ false)

reviewSkillSelectionAdapter = ReviewSkillSelectionAdapter(this)
binding.reviewSkillRecyclerView.apply {
adapter = reviewSkillSelectionAdapter
// https://stackoverflow.com/a/50075019/3689782
layoutManager = GridLayoutManager(context, /* spanCount= */ 2)
}
binding.let {
it.lifecycleOwner = fragment
}
subscribeToTopicLiveData()
return binding.root
}

override fun onTopicReviewSummaryClicked(skillSummary: SkillSummary) {
routeToReviewListener.routeToConceptCard(skillSummary.skillId)
}

private val topicLiveData: LiveData<Topic> by lazy { getTopicList() }

// TODO(#135): Get this topic-id or get skillList from [TopicFragment].
private val topicResultLiveData: LiveData<AsyncResult<Topic>> by lazy {
topicController.getTopic(TEST_TOPIC_ID_0)
}

private fun subscribeToTopicLiveData() {
topicLiveData.observe(fragment, Observer<Topic> { result ->
reviewSkillSelectionAdapter.setSkillList(result.skillList)
})
}

private fun getTopicList(): LiveData<Topic> {
return Transformations.map(topicResultLiveData, ::processTopicResult)
}

private fun processTopicResult(topic: AsyncResult<Topic>): Topic {
if (topic.isFailure()) {
logger.e("TopicReviewFragment", "Failed to retrieve topic", topic.getErrorOrNull()!!)
}
return topic.getOrDefault(Topic.getDefaultInstance())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.oppia.app.topic.review

import androidx.lifecycle.ViewModel
import org.oppia.app.fragment.FragmentScope
import javax.inject.Inject

/** [ViewModel] for showing a list of topic review-skills. */
@FragmentScope
class TopicReviewViewModel @Inject constructor() : ViewModel()
4 changes: 4 additions & 0 deletions app/src/main/res/drawable/lesson_thumbnail_default.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
<solid android:color="@color/colorPrimary" />
</shape>
Binary file added app/src/main/res/drawable/review_placeholder.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions app/src/main/res/drawable/topic_review_item_border.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid
android:color="@color/white"/>
<stroke
android:width="2dp"
android:color="@color/oppiaBrown"/>
<corners
android:radius="8dp"/>
</shape>
22 changes: 9 additions & 13 deletions app/src/main/res/layout/topic_review_fragment.xml
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/dummy_text_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="@color/oppiaBackground">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/review_skill_recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="This is dummy TextView for testing"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
android:padding="16dp"/>
</FrameLayout>
</layout>
48 changes: 48 additions & 0 deletions app/src/main/res/layout/topic_review_summary_view.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="skill"
type="org.oppia.app.model.SkillSummary"/>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="180dp"
android:layout_margin="16dp"
android:background="@drawable/topic_review_item_border"
android:clickable="true"
android:focusable="true">
<ImageView
android:id="@+id/skill_image_view"
android:src="@{skill.thumbnailUrl}"
android:layout_width="0dp"
android:layout_height="105dp"
android:importantForAccessibility="no"
android:scaleType="centerCrop"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<View
android:id="@+id/separator_view"
android:layout_width="match_parent"
android:layout_height="2dp"
android:background="@color/oppiaBrown"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/skill_image_view"/>
<TextView
android:id="@+id/skill_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-medium"
android:padding="24dp"
android:text="@{skill.description}"
android:textColor="@color/oppiaPrimaryText"
android:textSize="14sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/separator_view"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
2 changes: 2 additions & 0 deletions app/src/main/res/values/colors.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
<color name="oppiaLightGreen">#F0FFFF</color>
<color name="oppiaLightBlue">#4DA5D3EC</color>
<color name="oppiaLightYellow">#FFFFF0</color>
<color name="oppiaBrown">#C55F45</color>
<color name="oppiaBackground">#F2F2F2</color>
<color name="oppiaStrokeBlue">#4D395FD0</color>
<color name="oppiaStrokeBlack">#CC333333</color>
<!-- BASIC COLORS -->
Expand Down
Loading

0 comments on commit d5f0e12

Please sign in to comment.