Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #161: Exploration player contentcard supports rich-text #189

Closed
wants to merge 26 commits into from
Closed
Changes from 5 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
c3be8cf
added ContentListFragment and imageparser
veena14cs Sep 26, 2019
be5ab3d
Update UrlImageParser.kt
veena14cs Sep 26, 2019
ae58b05
Update UrlImageParser.kt
veena14cs Sep 26, 2019
46dda13
remove debug code
veena14cs Sep 26, 2019
c27f1a9
updated comment
veena14cs Sep 26, 2019
ed7eee3
corrected variable name
veena14cs Sep 27, 2019
b0464ab
added punctuation to the comments
veena14cs Sep 27, 2019
cfa8e51
broke condition in multiple lines
veena14cs Sep 27, 2019
73d4a8f
removed log and unused imports
veena14cs Sep 27, 2019
e0f2265
removed blank spaces
veena14cs Sep 27, 2019
7fd2e74
removed blank spaces
veena14cs Sep 27, 2019
a7e6544
updated code
veena14cs Sep 30, 2019
1f87e82
fixed issues in xmls
veena14cs Sep 30, 2019
cea90f7
passing entitytype and entity id as parameters to load image
veena14cs Sep 30, 2019
78ef537
Update ContentCardAdapter.kt
veena14cs Sep 30, 2019
f3a0d33
Update ContentCardAdapter.kt
veena14cs Sep 30, 2019
9719d1a
Update ContentCardAdapter.kt
veena14cs Sep 30, 2019
1dcd935
Update ContentListFragmentPresenter.kt
veena14cs Sep 30, 2019
4bce854
working on testcase
veena14cs Oct 1, 2019
5649d81
Update ContentListFragmentTest.kt
veena14cs Oct 1, 2019
74889c1
fixed issues
veena14cs Oct 3, 2019
630581e
added testcases
veena14cs Oct 3, 2019
70eeea8
Update ContentCardAdapter.kt
veena14cs Oct 3, 2019
e02ca5e
Merge branch 'develop' into exploration-player-2-content-card
veena14cs Oct 14, 2019
0580ed2
show content card from exploration controller
veena14cs Oct 14, 2019
3977321
updated json
veena14cs Oct 14, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
@@ -64,7 +64,8 @@ dependencies {
'com.google.dagger:dagger:2.24',
"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version",
"org.jetbrains.kotlinx:kotlinx-coroutines-core:1.2.1",
"org.jetbrains.kotlinx:kotlinx-coroutines-android:1.2.1"
"org.jetbrains.kotlinx:kotlinx-coroutines-android:1.2.1",
veena14cs marked this conversation as resolved.
Show resolved Hide resolved
'androidx.recyclerview:recyclerview:1.0.0',
)
testImplementation(
'androidx.test:core:1.2.0',
Original file line number Diff line number Diff line change
@@ -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.player.content.ContentListFragment
import org.oppia.app.topic.conceptcard.ConceptCardFragment

/** Root subcomponent for all fragments. */
@@ -24,4 +25,5 @@ interface FragmentComponent {
fun inject(homeFragment: HomeFragment)
fun inject(stateFragment: StateFragment)
fun inject(conceptCardFragment: ConceptCardFragment)
fun inject(conceptCardFragment: ContentListFragment)
veena14cs marked this conversation as resolved.
Show resolved Hide resolved
}
124 changes: 124 additions & 0 deletions app/src/main/java/org/oppia/app/player/content/ContentCardAdapter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package org.oppia.app.player.content

import android.content.Context
import android.text.Html
import android.text.Spannable
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.TextView
import androidx.databinding.DataBindingUtil
import androidx.databinding.ViewDataBinding
import androidx.databinding.library.baseAdapters.BR
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.content_card_items.view.*
import org.oppia.app.R
import org.oppia.app.databinding.ContentCardItemsBinding
import org.oppia.app.databinding.LearnersCardItemBinding
import org.oppia.data.backends.gae.model.GaeSubtitledHtml
import org.oppia.util.data.UrlImageParser

/** Adapter to bind the contents to the [RecyclerView].It handles rich-text content */
veena14cs marked this conversation as resolved.
Show resolved Hide resolved
class ContentCardAdapter(
veena14cs marked this conversation as resolved.
Show resolved Hide resolved
private val context: Context,
val contentList: MutableList<GaeSubtitledHtml>
veena14cs marked this conversation as resolved.
Show resolved Hide resolved
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

private val VIEW_TYPE_CONTENT = 1
private val VIEW_TYPE_LEARNER = 2
veena14cs marked this conversation as resolved.
Show resolved Hide resolved

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {

return when (viewType) {
VIEW_TYPE_CONTENT -> {
val inflater = LayoutInflater.from(parent.getContext())
val binding =
DataBindingUtil.inflate<ContentCardItemsBinding>(inflater, R.layout.content_card_items, parent, false)
ContentViewHolder(binding)
}
VIEW_TYPE_LEARNER -> {
val inflater = LayoutInflater.from(parent.getContext())
val binding =
DataBindingUtil.inflate<LearnersCardItemBinding>(inflater, R.layout.learners_card_item, parent, false)
LearnersViewHolder(binding)
}
else -> throw IllegalArgumentException("Invalid view type")
}
}

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {

veena14cs marked this conversation as resolved.
Show resolved Hide resolved
when (holder.itemViewType) {
VIEW_TYPE_CONTENT -> (holder as ContentViewHolder).bind(contentList!!.get(position).html)
VIEW_TYPE_LEARNER -> (holder as LearnersViewHolder).bind(contentList!!.get(position).html)
}
}

// Determines the appropriate ViewType according to the sender of the message.
override fun getItemViewType(position: Int): Int {

return if (!contentList!!.get(position).contentId!!.contains("content") && !contentList!!.get(position).contentId!!.contains(
veena14cs marked this conversation as resolved.
Show resolved Hide resolved
"Feedback"
)
) {
VIEW_TYPE_LEARNER
} else {
VIEW_TYPE_CONTENT
}
}

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

inner class ContentViewHolder(val binding: ViewDataBinding) : RecyclerView.ViewHolder(binding.root) {
veena14cs marked this conversation as resolved.
Show resolved Hide resolved

fun bind(rawString: String?) {

binding.setVariable(BR.htmlContent, rawString)
binding.executePendingBindings();

val html: Spannable = parseHtml(rawString, binding.root.tvContents)

binding.root.tvContents.text = html
}
}

private fun parseHtml(rawString: String?, tvContents: TextView): Spannable {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to my comment in your other PR--let's move this to a general utility so that we don't need to repeat it.

val html: Spannable
var htmlContent = rawString

var CUSTOM_TAG = "oppia-noninteractive-image"
var HTML_TAG = "img"
var CUSTOM_ATTRIBUTE = "filepath-with-value"
var HTML_ATTRIBUTE = "src"
veena14cs marked this conversation as resolved.
Show resolved Hide resolved

if (htmlContent!!.contains(CUSTOM_TAG)) {

veena14cs marked this conversation as resolved.
Show resolved Hide resolved
htmlContent = htmlContent.replace(CUSTOM_TAG, HTML_TAG, false);
htmlContent = htmlContent.replace(CUSTOM_ATTRIBUTE, HTML_ATTRIBUTE, false);
htmlContent = htmlContent.replace("&amp;quot;", "")
}

var imageGetter = UrlImageParser(tvContents, context)

if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
html = Html.fromHtml(htmlContent, Html.FROM_HTML_MODE_LEGACY, imageGetter, null) as Spannable
} else {
html = Html.fromHtml(htmlContent, imageGetter, null) as Spannable
}
return html
}

inner class LearnersViewHolder(val binding: ViewDataBinding) : RecyclerView.ViewHolder(binding.root) {

fun bind(rawString: String?) {

veena14cs marked this conversation as resolved.
Show resolved Hide resolved
binding.setVariable(BR.htmlContent, rawString)
binding.executePendingBindings();

val html: Spannable = parseHtml(rawString, binding.root.tvContents)

veena14cs marked this conversation as resolved.
Show resolved Hide resolved
binding.root.tvContents.text = html
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package org.oppia.app.player.content

import android.content.Context
import android.os.Bundle
import android.util.Log
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button

import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import org.oppia.app.R
import org.oppia.app.fragment.InjectableFragment
import org.oppia.data.backends.gae.NetworkModule
import org.oppia.data.backends.gae.model.GaeExplorationContainer
import org.oppia.data.backends.gae.model.GaeState
import org.oppia.data.backends.gae.model.GaeSubtitledHtml
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import java.lang.Exception

import java.util.ArrayList
import javax.inject.Inject

/** Fragment that displays contents that supports rich-text. */
class ContentListFragment : InjectableFragment() {

@Inject
lateinit var contentListFragmentPresenter: ContentListFragmentPresenter

override fun onAttach(context: Context?) {
super.onAttach(context)
fragmentComponent.inject(this)
}

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return contentListFragmentPresenter.handleCreateView(inflater, container)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package org.oppia.app.player.content

import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import org.oppia.app.application.ApplicationContext
import org.oppia.app.databinding.ContentListFragmentBinding
import org.oppia.data.backends.gae.model.GaeSubtitledHtml
import javax.inject.Inject

/** Presenter for [ContentListFragment]. */
class ContentListFragmentPresenter @Inject constructor(
veena14cs marked this conversation as resolved.
Show resolved Hide resolved
@ApplicationContext private val context: Context,
private val fragment: Fragment
) {

private lateinit var binding: ContentListFragmentBinding
var contentCardAdapter: ContentCardAdapter? = null
var contentList: MutableList<GaeSubtitledHtml> = ArrayList()

fun handleCreateView(inflater: LayoutInflater, container: ViewGroup?): View? {

veena14cs marked this conversation as resolved.
Show resolved Hide resolved
binding = ContentListFragmentBinding.inflate(inflater, container, /* attachToRoot= */ false)

binding.recyclerView.apply {

binding.recyclerView.layoutManager = LinearLayoutManager(context)
contentCardAdapter = ContentCardAdapter(context, contentList);
binding.contentCardAdapter = ContentCardAdapter(context, contentList);
}
fetchDummyExplorations()

return binding.root
}

//TODO (#121) :Replace this once interface for ExplorationDataController is available
veena14cs marked this conversation as resolved.
Show resolved Hide resolved
private fun fetchDummyExplorations() {
veena14cs marked this conversation as resolved.
Show resolved Hide resolved

contentList.add(
GaeSubtitledHtml(
"\u003cp\u003eMeet Matthew!\u003c/p\u003e\u003coppia-noninteractive-image alt-with-value=\"\u0026amp;quot;A boy with a red shirt and blue trousers.\u0026amp;quot;\" caption-with-value=\"\u0026amp;quot;\u0026amp;quot;\" filepath-with-value=\"\u0026amp;quot;img_20180121_113315_pqwqhf863w_height_565_width_343.png\u0026amp;quot;\"\u003e\u003c/oppia-noninteractive-image\u003e\u003cp\u003eMatthew is a young man who likes friends, sports, and eating cake! He also likes learning new things. We\u2019ll follow Matthew as he learns about fractions and how to use them.\u003cbr\u003e\u003c/p\u003e\u003cp\u003eYou should know the following before going on:\u003cbr\u003e\u003c/p\u003e\u003cul\u003e\u003cli\u003eThe counting numbers (1, 2, 3, 4, 5 \u2026.)\u003cbr\u003e\u003c/li\u003e\u003cli\u003eHow to tell whether one counting number is bigger or smaller than another\u003cbr\u003e\u003c/li\u003e\u003c/ul\u003e\u003cp\u003eYou should also get some paper and a pen or pencil to write with, and find a quiet place to work. Take your time, and go through the story at your own pace. Understanding is more important than speed!\u003cbr\u003e\u003c/p\u003e\u003cp\u003eOnce you\u2019re ready, click \u003cstrong\u003eContinue\u003c/strong\u003e to get started!\u003cbr\u003e\u003c/p\u003e",
"content"
)
)
contentList.add(
GaeSubtitledHtml(
"\u003cp\u003e\"OK!\" said Mr. Baker. \"Here's a question for you.\"\u003cbr\u003e\u003c/p\u003e\u003cp\u003e\u003cstrong\u003eQuestion 1\u003c/strong\u003e: If we talk about wanting \u003coppia-noninteractive-math raw_latex-with-value=\"\u0026amp;quot;\\\\frac{4}{7}\u0026amp;quot;\"\u003e\u003c/oppia-noninteractive-math\u003e of a cake, what does the 7 represent?\u003c/p\u003e",
"content"
)
)
contentList.add(
GaeSubtitledHtml(
"\u003cp\u003eThe number of pieces of cake I want.\u003c/p\u003e" +
"\u003cp\u003eThe number of pieces the whole cake is cut into.\u003c/p\u003e" +
"\u003cp\u003eI don't remember!\u003c/p\u003e",
"content"
)
)
contentList.add(GaeSubtitledHtml("\u003cp\u003eThe number of pieces of cake I want.\u003c/p\u003e", "Textinput"))
contentList.add(GaeSubtitledHtml("\u003cp\u003eNot quite. Let's look at it again.\u003c/p\u003e", "Feedback"))

contentList.add(GaeSubtitledHtml("\u003cp\u003eI don't remember!\u003c/p\u003e", "Textinput"))
contentList.add(GaeSubtitledHtml("\u003cp\u003eThat's OK. Let's look at it again.\u003c/p\u003e", "Feedback"))
}
}
11 changes: 11 additions & 0 deletions app/src/main/res/drawable/bg_blue_card.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:padding="16dp"
android:shape="rectangle">
<stroke
android:width="1.5dp"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1.5 dp why? I suggest not using such values, probably we should be them in multiple of 4 or sometimes 2 if required.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks too thick if I update to 2.

android:color="@color/blue_200"/>
<corners android:radius="10dp"/>
veena14cs marked this conversation as resolved.
Show resolved Hide resolved
<solid
android:color="@color/blue_100"/>
</shape>
11 changes: 11 additions & 0 deletions app/src/main/res/drawable/bg_white_card.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:padding="16dp"
android:shape="rectangle">
<stroke
android:width="1.5dp"
veena14cs marked this conversation as resolved.
Show resolved Hide resolved
android:color="@color/black"/>
<corners android:radius="10dp"/>
veena14cs marked this conversation as resolved.
Show resolved Hide resolved
<solid
android:color="@color/white"/>
</shape>
22 changes: 22 additions & 0 deletions app/src/main/res/layout/content_card_items.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="htmlContent"
type="String"/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp"
veena14cs marked this conversation as resolved.
Show resolved Hide resolved
android:background="@drawable/bg_blue_card"
android:orientation="vertical">
<TextView
android:id="@+id/tvContents"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp"
veena14cs marked this conversation as resolved.
Show resolved Hide resolved
android:gravity="left"
android:padding="8dp"
android:text="@{htmlContent}"/>
</LinearLayout>
</layout>
29 changes: 29 additions & 0 deletions app/src/main/res/layout/content_list_fragment.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?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"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="contentCardAdapter"
type="org.oppia.app.player.content.ContentCardAdapter"/>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:context=".player.content.ContentListFragment">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_marginStart="8dp"
android:layout_marginTop="4dp" android:layout_marginEnd="8dp"
veena14cs marked this conversation as resolved.
Show resolved Hide resolved
android:adapter="@{contentCardAdapter}"
veena14cs marked this conversation as resolved.
Show resolved Hide resolved
android:divider="@android:color/transparent"
android:dividerHeight="8dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
25 changes: 25 additions & 0 deletions app/src/main/res/layout/learners_card_item.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="htmlContent"
type="String"/>
</data>
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="22dp"
veena14cs marked this conversation as resolved.
Show resolved Hide resolved
android:layout_marginTop="5dp"
android:layout_marginRight="5dp"
android:layout_marginBottom="5dp"
veena14cs marked this conversation as resolved.
Show resolved Hide resolved
android:orientation="vertical">
<TextView
android:id="@+id/tvContents"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:background="@drawable/bg_white_card"
android:gravity="right|center"
android:padding="8dp"
android:text="@{htmlContent}"/>
</RelativeLayout>
</layout>
4 changes: 4 additions & 0 deletions app/src/main/res/values/colors.xml
Original file line number Diff line number Diff line change
@@ -11,6 +11,10 @@
<color name="oppiaDarkBlue">#2D4A9D</color>
<!-- BASIC COLORS -->
<color name="white">#FFFFFF</color>
<color name="black">#000000</color>
<!-- AUDIO COMPONENT -->
<color name="audioComponentBackground">@color/oppiaDarkBlue</color>

<color name="blue_100">#0F0086FB</color>
<color name="blue_200">#6B0086FB</color>
</resources>
3 changes: 2 additions & 1 deletion utility/build.gradle
Original file line number Diff line number Diff line change
@@ -43,7 +43,8 @@ dependencies {
'androidx.appcompat:appcompat:1.0.2',
'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0-alpha03',
'com.google.dagger:dagger:2.24',
"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version",
'com.github.bumptech.glide:glide:4.9.0',
)
testImplementation(
'androidx.test.ext:junit:1.1.1',
Loading