Commit e825ec5e authored by Niklas Fix's avatar Niklas Fix 🎓

add Latex support

parent 327d4d93
......@@ -52,6 +52,7 @@ dependencies {
implementation 'com.squareup.okhttp3:okhttp:3.12.1'
implementation 'com.github.bumptech.glide:glide:4.8.0'
implementation 'org.greenrobot:eventbus:3.2.0'
implementation 'io.github.sidvenu.mathjaxview:mathjaxview:1.0.7'
// LANGUAGE
implementation "com.github.YarikSOffice:lingver:1.3.0"
......
package de.akamu.tudarmstadt.custom.views;
import android.content.Context;
import android.util.AttributeSet;
import io.github.sidvenu.mathjaxview.MathJaxView;
public class AkamuLatexView extends MathJaxView {
String htmlBody;
String css;
String plainText;
String config = "MathJax.Hub.Config({" +
" extensions: ['fast-preview.js']," +
" messageStyle: 'none'," +
" \"fast-preview\": {" +
" disabled: false" +
" }," +
" CommonHTML: {" +
" linebreaks: { automatic: true, width: \"container\" }" +
" }," +
" tex2jax: {" +
" inlineMath: [ ['$','$'] ]," +
" displayMath: [ ['$$','$$'] ]," +
" processEscapes: true" +
" }," +
" TeX: {" +
" extensions: [\"file:///android_asset/MathJax/extensions/TeX/mhchem.js\"]," +
" mhchem: {legacy: false}" +
" }" +
"});";
String preDefinedConfig = "TeX-MML-AM_CHTML";
public AkamuLatexView(Context context, AttributeSet attrs) {
super(context, attrs);
}
/**
* Renders MathJax code that is found in the passed-in string
*
* @param htmlBody Text that contains the MathJax to be rendered
* @param css Styling of the webView
*/
public void load(String htmlBody, String css) {
loadDataWithBaseURL("about:blank",
"<html><head>" +
"<style>" +
css +
"</style>" +
"<script type=\"text/x-mathjax-config\">" +
config +
"</script>" +
"<script type=\"text/javascript\" async src=\"file:///android_asset/MathJax/MathJax.js?config=" + preDefinedConfig + "\"></script>" +
"</head>" +
"<body>" +
htmlBody +
"</body>" +
"</html>", "text/html", "utf-8", "");
}
public String getHtmlBody() {
return htmlBody;
}
public String getCss() {
return css;
}
public String getPlainText() {
return plainText;
}
public void setPlainText(String plainText) {
this.plainText = plainText;
}
}
......@@ -4,6 +4,7 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.preference.PreferenceManager
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import de.akamu.tudarmstadt.R
import de.akamu.tudarmstadt.util.Constants
......@@ -15,18 +16,33 @@ class ExplanationBottomSheetFragment : BottomSheetDialogFragment() {
lateinit var onExplanationContinueHandler: OnExplanationContinueHandler
lateinit var mExplanation: String
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.bottom_sheet_fragment_explanation, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
mExplanation = arguments?.getString(Constants.KEY_EXPLANATION)!!
textView_explanation_content.text = mExplanation
textView_explanation_content.plainText = mExplanation
val isNightMode =
PreferenceManager.getDefaultSharedPreferences(requireContext())
.getBoolean(Constants.IS_NIGHT_MODE, false)
val css = if (isNightMode) {
"body { color: white }"
} else {
"body { color: #555555 }"
}
textView_explanation_content.load(mExplanation, css)
val userAnswers = arguments?.getIntegerArrayList(Constants.KEY_ANSWERED_CORRECTLY)!!
val helper: ArrayList<Boolean> = arrayListOf()
userAnswers.forEach { a -> if (a == 1) helper.add(true) else helper.add(false) }
recyclerView_explanation_indicator.adapter = ExplanationIndicatorAdapter(requireActivity(), helper)
recyclerView_explanation_indicator.adapter =
ExplanationIndicatorAdapter(requireActivity(), helper)
button_continue_explanation.setOnClickListener { onExplanationContinueHandler.onExplanationContinueClicked() }
}
......@@ -35,7 +51,10 @@ class ExplanationBottomSheetFragment : BottomSheetDialogFragment() {
}
companion object {
fun newInstance(explanation: String, answers: ArrayList<Boolean>): ExplanationBottomSheetFragment {
fun newInstance(
explanation: String,
answers: ArrayList<Boolean>
): ExplanationBottomSheetFragment {
val f = ExplanationBottomSheetFragment()
val args = Bundle()
......
......@@ -16,12 +16,15 @@ import android.widget.FrameLayout
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.preference.PreferenceManager
import de.akamu.tudarmstadt.R
import de.akamu.tudarmstadt.custom.views.AkamuLatexView
import de.akamu.tudarmstadt.data.duel.DuelDataSourceImpl
import de.akamu.tudarmstadt.features.dashboard.MainActivity
import de.akamu.tudarmstadt.features.questions.answers.*
import de.akamu.tudarmstadt.interactors.DuelInteractor
import de.akamu.tudarmstadt.model.*
import de.akamu.tudarmstadt.util.AkamuResource
import de.akamu.tudarmstadt.util.Constants
import de.akamu.tudarmstadt.util.Constants.Companion.KEY_DUEL
import de.akamu.tudarmstadt.util.Constants.Companion.KEY_POOL
......@@ -126,7 +129,17 @@ class QuestionActivity :
}
override fun showQuestion(question: Question) {
textview_questionactivity_questiontext.text = question.text
val isNightMode =
PreferenceManager.getDefaultSharedPreferences(this)
.getBoolean(Constants.IS_NIGHT_MODE, false)
val css = if (isNightMode) {
"body { color: white }"
} else {
"body { color: #555555 }"
}
textview_questionactivity_questiontext.plainText = question.text
textview_questionactivity_questiontext.load(question.text, css)
// AkamuResource.loadCSS(textview_questionactivity_questiontext, css)
}
override fun showAnswer(answer: Answer) {
......
......@@ -3,6 +3,7 @@ package de.akamu.tudarmstadt.features.questions.answers
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.animation.AnimationUtils
......@@ -13,14 +14,17 @@ import de.akamu.tudarmstadt.features.questions.QuestionActivity
import de.akamu.tudarmstadt.features.questions.QuestionContract
import de.akamu.tudarmstadt.model.MultipleChoiceAnswer
import de.akamu.tudarmstadt.model.MultipleChoiceAnswerItem
import de.akamu.tudarmstadt.util.AkamuResource
import de.akamu.tudarmstadt.util.Constants
import kotlinx.android.synthetic.main.fragment_long_options_answer.*
class LongOptionsAnswerFragment : AnswerFragment(), View.OnClickListener {
class LongOptionsAnswerFragment : AnswerFragment(), View.OnClickListener, View.OnTouchListener {
private var listener: OnLongOptionsAnswerClickHandler? = null
lateinit var answer: MultipleChoiceAnswer
lateinit var presenter: QuestionContract.Presenter
private val MAX_TOUCH_DURATION = 1000
private var m_DownTime: Long = 0
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
......@@ -36,24 +40,57 @@ class LongOptionsAnswerFragment : AnswerFragment(), View.OnClickListener {
presenter = (activity as QuestionContract.View).presenter
answer = arguments?.getParcelable(Constants.KEY_OPTION_ANSWER)!!
button_questionlongansfragment_answer1.text = answer.items[0].text
val css = """
body { color: white; }
.answer {
display: flex;
justify-content: center;
text-align: center;
align-items: center;
height: 100%;
}
""".trimIndent()
button_questionlongansfragment_answer1.plainText = answer.items[0].text
button_questionlongansfragment_answer1.load("<div class=\"answer\">" + answer.items[0].text + "</div>", css)
button_questionlongansfragment_answer1.visibility = View.VISIBLE
button_questionlongansfragment_answer2.text = answer.items[1].text
button_questionlongansfragment_answer2.plainText = answer.items[1].text
button_questionlongansfragment_answer2.load("<div class=\"answer\">" + answer.items[1].text + "</div>", css)
button_questionlongansfragment_answer2.visibility = View.VISIBLE
if (answer.items.size > 2) {
button_questionlongansfragment_answer3.text = answer.items[2].text
button_questionlongansfragment_answer3.plainText = answer.items[2].text
button_questionlongansfragment_answer3.load("<div class=\"answer\">" + answer.items[2].text + "</div>", css)
button_questionlongansfragment_answer3.visibility = View.VISIBLE
button_questionlongansfragment_answer3.setOnClickListener(this)
button_questionlongansfragment_answer3.setOnTouchListener(this)
if (answer.items.size > 3) {
button_questionlongansfragment_answer4.text = answer.items[3].text
button_questionlongansfragment_answer4.plainText = answer.items[3].text
button_questionlongansfragment_answer4.load("<div class=\"answer\">" + answer.items[3].text + "</div>", css)
button_questionlongansfragment_answer4.visibility = View.VISIBLE
button_questionlongansfragment_answer4.setOnClickListener(this)
button_questionlongansfragment_answer4.setOnTouchListener(this)
}
}
button_questionlongansfragment_answer1.setOnClickListener(this)
button_questionlongansfragment_answer2.setOnClickListener(this)
button_questionlongansfragment_answer1.setOnTouchListener(this)
button_questionlongansfragment_answer2.setOnTouchListener(this)
}
override fun onTouch(view: View?, motionEvent: MotionEvent?): Boolean {
when (motionEvent?.action) {
MotionEvent.ACTION_DOWN -> {
m_DownTime = motionEvent.eventTime
return true
}
MotionEvent.ACTION_UP ->
if (motionEvent.eventTime - m_DownTime <= MAX_TOUCH_DURATION) {
onClick(view)
return true
}
else -> {
// no-op
}
}
return false
}
@Suppress("DEPRECATION")
......@@ -108,16 +145,16 @@ class LongOptionsAnswerFragment : AnswerFragment(), View.OnClickListener {
private fun findViewForCorrectAnswerItem(correctAnswerItem: MultipleChoiceAnswerItem): View? {
return when (correctAnswerItem.text) {
button_questionlongansfragment_answer1.text -> {
button_questionlongansfragment_answer1.plainText -> {
button_questionlongansfragment_answer1
}
button_questionlongansfragment_answer2.text -> {
button_questionlongansfragment_answer2.plainText -> {
button_questionlongansfragment_answer2
}
button_questionlongansfragment_answer3.text -> {
button_questionlongansfragment_answer3.plainText -> {
button_questionlongansfragment_answer3
}
button_questionlongansfragment_answer4.text -> {
button_questionlongansfragment_answer4.plainText -> {
button_questionlongansfragment_answer4
}
else -> null
......@@ -163,10 +200,10 @@ class LongOptionsAnswerFragment : AnswerFragment(), View.OnClickListener {
}
}
fun deactivateButtons() {
button_questionlongansfragment_answer1.isClickable = false
button_questionlongansfragment_answer2.isClickable = false
button_questionlongansfragment_answer3.isClickable = false
button_questionlongansfragment_answer4.isClickable = false
private fun deactivateButtons() {
button_questionlongansfragment_answer1.setOnTouchListener(null)
button_questionlongansfragment_answer2.setOnTouchListener(null)
button_questionlongansfragment_answer3.setOnTouchListener(null)
button_questionlongansfragment_answer4.setOnTouchListener(null)
}
}
......@@ -3,6 +3,7 @@ package de.akamu.tudarmstadt.features.questions.answers
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.animation.AnimationUtils
......@@ -14,13 +15,16 @@ import de.akamu.tudarmstadt.features.questions.QuestionContract
import de.akamu.tudarmstadt.model.MultipleChoiceAnswer
import de.akamu.tudarmstadt.model.MultipleChoiceAnswerItem
import de.akamu.tudarmstadt.util.Constants
import kotlinx.android.synthetic.main.fragment_long_options_answer.*
import kotlinx.android.synthetic.main.fragment_short_options_answer.*
class ShortOptionsAnswerFragment : AnswerFragment(), View.OnClickListener {
class ShortOptionsAnswerFragment : AnswerFragment(), View.OnClickListener, View.OnTouchListener {
private var listener: OnShortOptionsAnswerClickHandler? = null
lateinit var answer: MultipleChoiceAnswer
lateinit var presenter: QuestionContract.Presenter
private val MAX_TOUCH_DURATION = 1000
private var m_DownTime: Long = 0
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
......@@ -36,15 +40,48 @@ class ShortOptionsAnswerFragment : AnswerFragment(), View.OnClickListener {
presenter = (activity as QuestionContract.View).presenter
answer = arguments?.getParcelable(Constants.KEY_OPTION_ANSWER)!!
button_questionshortansfragment_answer1.text = answer.items[0].text
button_questionshortansfragment_answer2.text = answer.items[1].text
button_questionshortansfragment_answer3.text = answer.items[2].text
button_questionshortansfragment_answer4.text = answer.items[3].text
button_questionshortansfragment_answer1.setOnClickListener(this)
button_questionshortansfragment_answer2.setOnClickListener(this)
button_questionshortansfragment_answer3.setOnClickListener(this)
button_questionshortansfragment_answer4.setOnClickListener(this)
val css = """
body { color: white; }
.answer {
display: flex;
justify-content: center;
text-align: center;
align-items: center;
height: 100%;
}
""".trimIndent()
button_questionshortansfragment_answer1.plainText = answer.items[0].text
button_questionshortansfragment_answer1.load("<div class=\"answer\">" + answer.items[0].text + "</div>", css)
button_questionshortansfragment_answer2.plainText = answer.items[1].text
button_questionshortansfragment_answer2.load("<div class=\"answer\">" + answer.items[1].text + "</div>", css)
button_questionshortansfragment_answer3.plainText = answer.items[2].text
button_questionshortansfragment_answer3.load("<div class=\"answer\">" + answer.items[2].text + "</div>", css)
button_questionshortansfragment_answer4.plainText = answer.items[3].text
button_questionshortansfragment_answer4.load("<div class=\"answer\">" + answer.items[3].text + "</div>", css)
button_questionshortansfragment_answer1.setOnTouchListener(this)
button_questionshortansfragment_answer2.setOnTouchListener(this)
button_questionshortansfragment_answer3.setOnTouchListener(this)
button_questionshortansfragment_answer4.setOnTouchListener(this)
}
override fun onTouch(view: View?, motionEvent: MotionEvent?): Boolean {
when (motionEvent?.action) {
MotionEvent.ACTION_DOWN -> {
m_DownTime = motionEvent.eventTime
return true
}
MotionEvent.ACTION_UP ->
if (motionEvent.eventTime - m_DownTime <= MAX_TOUCH_DURATION) {
onClick(view)
return true
}
else -> {
// no-op
}
}
return false
}
@Suppress("DEPRECATION")
......@@ -99,16 +136,16 @@ class ShortOptionsAnswerFragment : AnswerFragment(), View.OnClickListener {
private fun findViewForCorrectAnswerItem(correctAnswerItem: MultipleChoiceAnswerItem): View? {
return when (correctAnswerItem.text) {
button_questionshortansfragment_answer1.text -> {
button_questionshortansfragment_answer1.plainText -> {
button_questionshortansfragment_answer1
}
button_questionshortansfragment_answer2.text -> {
button_questionshortansfragment_answer2.plainText -> {
button_questionshortansfragment_answer2
}
button_questionshortansfragment_answer3.text -> {
button_questionshortansfragment_answer3.plainText -> {
button_questionshortansfragment_answer3
}
button_questionshortansfragment_answer4.text -> {
button_questionshortansfragment_answer4.plainText -> {
button_questionshortansfragment_answer4
}
else -> null
......@@ -125,10 +162,10 @@ class ShortOptionsAnswerFragment : AnswerFragment(), View.OnClickListener {
}*/
fun deactivateButtons() {
button_questionshortansfragment_answer1.isClickable = false
button_questionshortansfragment_answer2.isClickable = false
button_questionshortansfragment_answer3.isClickable = false
button_questionshortansfragment_answer4.isClickable = false
button_questionshortansfragment_answer1.setOnTouchListener(null)
button_questionshortansfragment_answer2.setOnTouchListener(null)
button_questionshortansfragment_answer3.setOnTouchListener(null)
button_questionshortansfragment_answer4.setOnTouchListener(null)
}
override fun onAttach(context: Context) {
......
......@@ -7,7 +7,11 @@ import android.view.ViewGroup
import android.widget.BaseAdapter
import android.widget.TextView
import de.akamu.tudarmstadt.R
import de.akamu.tudarmstadt.custom.views.AkamuLatexView
import de.akamu.tudarmstadt.model.MultipleChoiceAnswerItem
import de.akamu.tudarmstadt.util.AkamuResource
import io.github.sidvenu.mathjaxview.MathJaxView
import kotlinx.android.synthetic.main.fragment_long_options_answer.*
class SwipeAnswerAdapter(
......@@ -28,7 +32,7 @@ class SwipeAnswerAdapter(
holder = view.tag as SwipeItemHolder
}
holder.answerTextView.text = this.answerItems[position].text
holder.bind(this.answerItems[position].text)
return view!!
}
......@@ -46,7 +50,22 @@ class SwipeAnswerAdapter(
}
inner class SwipeItemHolder(itemView: View) {
var answerTextView : TextView = itemView.findViewById(R.id.textView_swipe_answer_text)
var answerTextView : AkamuLatexView = itemView.findViewById(R.id.textView_swipe_answer_text)
var css: String = """
body { color: white; }
.answer {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
}
""".trimIndent()
// AkamuResource.loadCSS(answerTextView, css)
fun bind(text: String) {
answerTextView.plainText = text
answerTextView.load("<div class=\"answer\">$text</div>", css)
}
}
}
\ No newline at end of file
......@@ -8,9 +8,12 @@ import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.RecyclerView
import de.akamu.tudarmstadt.R
import de.akamu.tudarmstadt.custom.views.AkamuLatexView
import de.akamu.tudarmstadt.model.MultipleChoiceAnswerItem
import de.akamu.tudarmstadt.util.Constants
class SwipeAnswerShortSummaryAdapter(
private var mContext: Context,
......@@ -39,13 +42,22 @@ class SwipeAnswerShortSummaryAdapter(
inner class SwipeShortSummaryViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private var answerTextView : TextView = itemView.findViewById(R.id.textView_swipe_answer_short_summary_answerText)
private var answerTextView : AkamuLatexView = itemView.findViewById(R.id.textView_swipe_answer_short_summary_answerText)
private var rightIndicator : View = itemView.findViewById(R.id.view_swipe_answer_shortSummary_right_indicator)
private var leftIndicator : View = itemView.findViewById(R.id.view_swipe_answer_shortSummary_left_indicator)
private var finalIndicator : ImageView = itemView.findViewById(R.id.imageView_swipe_answer_shortSummary_final_indicator)
fun bind(answeredCorrectly: Boolean, answerItem: MultipleChoiceAnswerItem) {
answerTextView.text = answerItem.text
answerTextView.plainText = answerItem.text
val isNightMode =
PreferenceManager.getDefaultSharedPreferences(mContext)
.getBoolean(Constants.IS_NIGHT_MODE, false)
val css = if (isNightMode) {
"body { color: white }"
} else {
"body { color: #555555 }"
}
answerTextView.load("<div class=\"answer\">" + answerItem.text + "</div>", css)
if (answeredCorrectly) {
finalIndicator.setBackgroundResource(R.drawable.ic_checked_akamu_green)
} else {
......
......@@ -2,10 +2,14 @@ package de.akamu.tudarmstadt.util;
import android.app.Activity
import android.content.Context
import android.webkit.WebView
import android.webkit.WebViewClient
import android.widget.ImageView
import com.bumptech.glide.Glide
import com.bumptech.glide.load.model.GlideUrl
import de.akamu.tudarmstadt.api.V2API
import io.github.sidvenu.mathjaxview.MathJaxView
import kotlinx.android.synthetic.main.activity_question.*
class AkamuResource {
......@@ -26,6 +30,15 @@ class AkamuResource {
.load(glideUrl)
.into(targetView)
}
fun loadCSS(latexView: MathJaxView, css: String) {
latexView.webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView?, url: String?) {
latexView.loadUrl(css)
super.onPageFinished(view, url)
}
}
}
}
}
......@@ -71,23 +71,23 @@
android:layout_width="0dp"
android:layout_height="0dp"
android:scrollbars="vertical"
android:paddingTop="6dp"
android:paddingBottom="6dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/progressBar_questionactivity_timer">
app:layout_constraintTop_toBottomOf="@+id/textview_questionactivity_roundnumber">
<TextView
<de.akamu.tudarmstadt.custom.views.AkamuLatexView
android:id="@+id/textview_questionactivity_questiontext"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="30dp"
android:paddingTop="45dp"
android:paddingEnd="30dp"
android:paddingBottom="10dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:text="This is an example question text. What is 2 + 2? It is 4. What is the question?"
android:textColor="@color/darkText"
android:textSize="18sp"
android:textStyle="bold"
android:textColor="@color/darkText"
tools:ignore="HardcodedText" />