From 8ca625d5ecd59e5aedcf32acc6a996eb5e6a4d0e Mon Sep 17 00:00:00 2001 From: Ahmed El-Helw Date: Sat, 6 Apr 2019 23:30:05 +0400 Subject: [PATCH] Implement a way to expand tafseer links (#1129) In some tafaseer, a single "entry" gives the translations for multiple ayat. Previously, the app would just show a "same as translation for ayah X." This made many people sad. This patch attemps to fix this by adding the ability to tap the translation to actually display the translation text. --- .../labs/androidquran/common/QuranText.kt | 2 +- .../database/DatabaseHandler.java | 61 ++++++++++++++++++- .../translation/ArabicDatabaseUtils.java | 13 ++-- .../ui/translation/TranslationAdapter.kt | 29 ++++++++- .../labs/androidquran/util/TranslationUtil.kt | 31 +++++++--- .../widgets/InlineTranslationView.java | 9 +-- app/src/main/res/values-ar/strings.xml | 2 +- app/src/main/res/values/strings.xml | 2 +- .../translation/ArabicDatabaseUtilsTest.java | 2 +- 9 files changed, 124 insertions(+), 27 deletions(-) diff --git a/app/src/main/java/com/quran/labs/androidquran/common/QuranText.kt b/app/src/main/java/com/quran/labs/androidquran/common/QuranText.kt index ebdc614de3..6d84216e83 100644 --- a/app/src/main/java/com/quran/labs/androidquran/common/QuranText.kt +++ b/app/src/main/java/com/quran/labs/androidquran/common/QuranText.kt @@ -1,3 +1,3 @@ package com.quran.labs.androidquran.common -class QuranText(val sura: Int, val ayah: Int, val text: String) +class QuranText(val sura: Int, val ayah: Int, val text: String, val extraData: String? = null) diff --git a/app/src/main/java/com/quran/labs/androidquran/database/DatabaseHandler.java b/app/src/main/java/com/quran/labs/androidquran/database/DatabaseHandler.java index e94270d321..59bcf5dda8 100644 --- a/app/src/main/java/com/quran/labs/androidquran/database/DatabaseHandler.java +++ b/app/src/main/java/com/quran/labs/androidquran/database/DatabaseHandler.java @@ -7,6 +7,7 @@ import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabaseCorruptException; import android.provider.BaseColumns; +import android.util.SparseArray; import com.crashlytics.android.Crashlytics; import com.quran.labs.androidquran.R; @@ -14,14 +15,17 @@ import com.quran.labs.androidquran.data.QuranFileConstants; import com.quran.labs.androidquran.data.VerseRange; import com.quran.labs.androidquran.util.QuranFileUtils; +import com.quran.labs.androidquran.util.TranslationUtil; import java.io.File; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import androidx.annotation.IntDef; import androidx.annotation.NonNull; @@ -163,21 +167,74 @@ public Cursor getVerses(int minSura, int minAyah, int maxSura, public List getVerses(VerseRange verses, @TextType int textType) { Cursor cursor = null; List results = new ArrayList<>(); + final Set toLookup = new HashSet<>(); + + String table = textType == TextType.ARABIC ? ARABIC_TEXT_TABLE : VERSE_TABLE; try { - String table = textType == TextType.ARABIC ? ARABIC_TEXT_TABLE : VERSE_TABLE; cursor = getVersesInternal(verses, table); while (cursor != null && cursor.moveToNext()) { int sura = cursor.getInt(1); int ayah = cursor.getInt(2); String text = cursor.getString(3); - results.add(new QuranText(sura, ayah, text)); + + final QuranText quranText = new QuranText(sura, ayah, text, null); + results.add(quranText); + + final Integer hyperlinkId = TranslationUtil.getHyperlinkAyahId(quranText); + if (hyperlinkId != null) { + toLookup.add(hyperlinkId); + } } } finally { DatabaseUtils.closeCursor(cursor); } + + boolean didWrite = false; + if (!toLookup.isEmpty()) { + final StringBuilder toExpandBuilder = new StringBuilder(); + for (Integer id : toLookup) { + if (didWrite) { + toExpandBuilder.append(","); + } else { + didWrite = true; + } + toExpandBuilder.append(id); + } + return expandHyperlinks(table, results, toExpandBuilder.toString()); + } return results; } + private List expandHyperlinks(String table, List data, String rowIds) { + SparseArray expansions = new SparseArray<>(); + + Cursor cursor = null; + try { + cursor = database.query(table, new String[]{ "rowid as _id", COL_TEXT }, + "rowid in (" + rowIds + ")", null, null, null, "rowid"); + while (cursor != null && cursor.moveToNext()) { + int id = cursor.getInt(0); + String text = cursor.getString(1); + expansions.put(id, text); + } + } finally { + DatabaseUtils.closeCursor(cursor); + } + + List result = new ArrayList<>(); + for (int i = 0, size = data.size(); i < size; i++) { + final QuranText ayah = data.get(i); + final Integer linkId = TranslationUtil.getHyperlinkAyahId(ayah); + if (linkId == null) { + result.add(ayah); + } else { + final String expandedText = expansions.get(linkId); + result.add(new QuranText(ayah.getSura(), ayah.getAyah(), ayah.getText(), expandedText)); + } + } + return result; + } + private Cursor getVersesInternal(VerseRange verses, String table) { if (!validDatabase()) { return null; diff --git a/app/src/main/java/com/quran/labs/androidquran/model/translation/ArabicDatabaseUtils.java b/app/src/main/java/com/quran/labs/androidquran/model/translation/ArabicDatabaseUtils.java index 645374cda3..5816ce0f05 100644 --- a/app/src/main/java/com/quran/labs/androidquran/model/translation/ArabicDatabaseUtils.java +++ b/app/src/main/java/com/quran/labs/androidquran/model/translation/ArabicDatabaseUtils.java @@ -28,7 +28,9 @@ @Singleton public class ArabicDatabaseUtils { - public static final String AR_BASMALLAH = "بِسۡمِ ٱللَّهِ ٱلرَّحۡمَـٰنِ ٱلرَّحِیمِ"; + public static final String AR_BASMALLAH = "بِسْمِ اللَّهِ الرَّحْمَٰنِ الرَّحِيمِ"; + static final String AR_BASMALLAH_IN_TEXT = "بِسۡمِ ٱللَّهِ ٱلرَّحۡمَـٰنِ ٱلرَّحِیمِ"; + @VisibleForTesting static final int NUMBER_OF_WORDS = 4; private final Context appContext; @@ -67,7 +69,10 @@ public Single> getVerses(final SuraAyah start, final SuraAyah en cursor = arabicDatabaseHandler.getVerses(start.sura, start.ayah, end.sura, end.ayah, QuranFileConstants.ARABIC_SHARE_TABLE); while (cursor.moveToNext()) { - QuranText verse = new QuranText(cursor.getInt(1), cursor.getInt(2), cursor.getString(3)); + QuranText verse = new QuranText(cursor.getInt(1), + cursor.getInt(2), + cursor.getString(3), + null); verses.add(verse); } } catch (Exception e) { @@ -158,8 +163,8 @@ public static String getAyahWithoutBasmallah(int sura, int ayah, String ayahText // note that ayahText.startsWith check is always true for now - but it's explicitly here so // that if we update quran.ar.db one day to fix this issue and older clients get a new copy of // the database, their code continues to work as before. - if (ayah == 1 && sura != 9 && sura != 1 && ayahText.startsWith(AR_BASMALLAH)) { - return ayahText.substring(AR_BASMALLAH.length() + 1); + if (ayah == 1 && sura != 9 && sura != 1 && ayahText.startsWith(AR_BASMALLAH_IN_TEXT)) { + return ayahText.substring(AR_BASMALLAH_IN_TEXT.length() + 1); } return ayahText; } diff --git a/app/src/main/java/com/quran/labs/androidquran/ui/translation/TranslationAdapter.kt b/app/src/main/java/com/quran/labs/androidquran/ui/translation/TranslationAdapter.kt index d3440b5a7f..ce4b3960db 100644 --- a/app/src/main/java/com/quran/labs/androidquran/ui/translation/TranslationAdapter.kt +++ b/app/src/main/java/com/quran/labs/androidquran/ui/translation/TranslationAdapter.kt @@ -45,10 +45,12 @@ internal class TranslationAdapter(private val context: Context, private var highlightedStartPosition: Int = 0 private val expandedTafaseerAyahs = mutableSetOf>() + private val expandedHyperlinks = mutableSetOf>() private val defaultClickListener = View.OnClickListener { v -> onClickListener.onClick(v) } private val defaultLongClickListener = View.OnLongClickListener { this.selectVerseRows(it) } private val expandClickListener = View.OnClickListener { v -> toggleExpandTafseer(v) } + private val expandHyperlinkClickListener = View.OnClickListener { v -> toggleExpandHyperlink(v) } fun getSelectedVersePopupPosition(): IntArray? { return if (highlightedStartPosition > -1) { @@ -179,6 +181,20 @@ internal class TranslationAdapter(private val context: Context, } } + private fun toggleExpandHyperlink(view: View) { + val position = recyclerView.getChildAdapterPosition(view) + if (position != RecyclerView.NO_POSITION) { + val data = data[position] + val what = data.ayahInfo.ayahId to data.translationIndex + if (expandedHyperlinks.contains(what)) { + expandedHyperlinks.remove(what) + } else { + expandedHyperlinks.add(what) + } + notifyItemChanged(position) + } + } + override fun getItemViewType(position: Int): Int { return data[position].type } @@ -200,10 +216,12 @@ internal class TranslationAdapter(private val context: Context, override fun onBindViewHolder(holder: RowViewHolder, position: Int) { val row = data[position] - when { // a row with text holder.text != null -> { + // reset click listener on the text + holder.text.setOnClickListener(defaultClickListener) + val text: CharSequence? if (row.type == TranslationViewRow.Type.SURA_HEADER) { text = row.data @@ -227,8 +245,15 @@ internal class TranslationAdapter(private val context: Context, // translation text = row.data?.let { rowText -> val length = rowText.length + val expandHyperlink = + expandedHyperlinks.contains(row.ayahInfo.ayahId to row.translationIndex) + + if (row.link != null && !expandHyperlink) { + holder.text.setOnClickListener(expandHyperlinkClickListener) + } + when { - row.link != null -> getAyahLink(row.link) + row.link != null && !expandHyperlink -> getAyahLink(row.link) length > MAX_TAFSEER_LENGTH -> truncateTextIfNeeded(rowText, row.ayahInfo.ayahId, row.translationIndex) else -> rowText diff --git a/app/src/main/java/com/quran/labs/androidquran/util/TranslationUtil.kt b/app/src/main/java/com/quran/labs/androidquran/util/TranslationUtil.kt index b03cca8f31..3ceeccd51e 100644 --- a/app/src/main/java/com/quran/labs/androidquran/util/TranslationUtil.kt +++ b/app/src/main/java/com/quran/labs/androidquran/util/TranslationUtil.kt @@ -15,15 +15,21 @@ open class TranslationUtil(@ColorInt private val color: Int, open fun parseTranslationText(quranText: QuranText): TranslationMetadata { val text = quranText.text - if (text.length < 5) { - val ayahId = text.toIntOrNull() - if (ayahId != null) { - val suraAyah = quranInfo.getSuraAyahFromAyahId(ayahId) - return TranslationMetadata(quranText.sura, quranText.ayah, text, suraAyah) - } + val hyperlinkId = getHyperlinkAyahId(quranText) + + val suraAyah = if (hyperlinkId != null) { + quranInfo.getSuraAyahFromAyahId(hyperlinkId) + } else { + null + } + + val textToParse = if (suraAyah != null && quranText.extraData != null) { + quranText.extraData + } else { + text } - val withoutFooters = footerRegex.replace(text, "") + val withoutFooters = footerRegex.replace(textToParse, "") val spannable = SpannableString(withoutFooters) ayahRegex.findAll(withoutFooters) @@ -32,12 +38,21 @@ open class TranslationUtil(@ColorInt private val color: Int, val range = it.range spannable.setSpan(span, range.start, range.last + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) } - return TranslationMetadata(quranText.sura, quranText.ayah, spannable) + return TranslationMetadata(quranText.sura, quranText.ayah, spannable, suraAyah) } companion object { private val ayahRegex = """([«{﴿][\s\S]*?[﴾}»])""".toRegex() private val footerRegex = """\[\[[\s\S]*?]]""".toRegex() const val MINIMUM_PROCESSING_VERSION = 5 + + @JvmStatic + fun getHyperlinkAyahId(quranText: QuranText): Int? { + val text = quranText.text + if (text.length < 5) { + return text.toIntOrNull() + } + return null + } } } diff --git a/app/src/main/java/com/quran/labs/androidquran/widgets/InlineTranslationView.java b/app/src/main/java/com/quran/labs/androidquran/widgets/InlineTranslationView.java index d54b5b6714..f999552be5 100644 --- a/app/src/main/java/com/quran/labs/androidquran/widgets/InlineTranslationView.java +++ b/app/src/main/java/com/quran/labs/androidquran/widgets/InlineTranslationView.java @@ -18,7 +18,6 @@ import com.quran.labs.androidquran.common.LocalTranslation; import com.quran.labs.androidquran.common.QuranAyahInfo; import com.quran.labs.androidquran.common.TranslationMetadata; -import com.quran.labs.androidquran.data.SuraAyah; import com.quran.labs.androidquran.util.QuranSettings; import java.util.List; @@ -138,12 +137,8 @@ private void addTextForAyah(LocalTranslation[] translations, QuranAyahInfo ayah) builder.append("\n\n"); } - final SuraAyah link = translationMetadata.getLink(); - if (link != null) { - builder.append(context.getString(R.string.see_tafseer_of_verse, link.ayah)); - } else { - builder.append(translationText); - } + // irrespective of whether it's a link or not, show the text + builder.append(translationText); } } ayahView.append(builder); diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 158f5f0395..31d4bb502b 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -138,7 +138,7 @@ موافق - انظر تفسير الآية %d. + تفسير الآية تابع للآية %d (انقر للعرض). هناك تحديثات diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e4d1047ba6..6bbb23c8ff 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -233,7 +233,7 @@ المزيد… - See tafseer for ayah %d. + This ayah\'s tafseer is included with the tafseer of ayah %d (Click to expand). Update Available diff --git a/app/src/test/java/com/quran/labs/androidquran/model/translation/ArabicDatabaseUtilsTest.java b/app/src/test/java/com/quran/labs/androidquran/model/translation/ArabicDatabaseUtilsTest.java index 34107530b7..ae2be3dcd4 100644 --- a/app/src/test/java/com/quran/labs/androidquran/model/translation/ArabicDatabaseUtilsTest.java +++ b/app/src/test/java/com/quran/labs/androidquran/model/translation/ArabicDatabaseUtilsTest.java @@ -109,7 +109,7 @@ private String makeText(int words) { @Test public void testGetAyahWithoutBasmallah() { - String basmallah = ArabicDatabaseUtils.AR_BASMALLAH; + String basmallah = ArabicDatabaseUtils.AR_BASMALLAH_IN_TEXT; String original = basmallah + " first ayah"; assertThat(ArabicDatabaseUtils.getAyahWithoutBasmallah(1, 1, original)).isSameAs(original);