Skip to content

Commit

Permalink
Implement a way to expand tafseer links (#1129)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
ahmedre authored Apr 6, 2019
1 parent 7951fb9 commit 8ca625d
Show file tree
Hide file tree
Showing 9 changed files with 124 additions and 27 deletions.
Original file line number Diff line number Diff line change
@@ -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)
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,25 @@
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;
import com.quran.labs.androidquran.common.QuranText;
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;
Expand Down Expand Up @@ -163,21 +167,74 @@ public Cursor getVerses(int minSura, int minAyah, int maxSura,
public List<QuranText> getVerses(VerseRange verses, @TextType int textType) {
Cursor cursor = null;
List<QuranText> results = new ArrayList<>();
final Set<Integer> 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<QuranText> expandHyperlinks(String table, List<QuranText> data, String rowIds) {
SparseArray<String> 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<QuranText> 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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -67,7 +69,10 @@ public Single<List<QuranText>> 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) {
Expand Down Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,12 @@ internal class TranslationAdapter(private val context: Context,
private var highlightedStartPosition: Int = 0

private val expandedTafaseerAyahs = mutableSetOf<Pair<Int, Int>>()
private val expandedHyperlinks = mutableSetOf<Pair<Int, Int>>()

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) {
Expand Down Expand Up @@ -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
}
Expand All @@ -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
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/res/values-ar/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@
<string name="dialog_ok">موافق</string>

<!-- tafaseer -->
<string name="see_tafseer_of_verse">انظر تفسير الآية %d.</string>
<string name="see_tafseer_of_verse">تفسير الآية تابع للآية %d (انقر للعرض).</string>

<!-- translation updates -->
<string name="update_available">هناك تحديثات</string>
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@

<!-- tafaseer -->
<string name="more_arabic" translatable="false">المزيد…</string>
<string name="see_tafseer_of_verse">See tafseer for ayah %d.</string>
<string name="see_tafseer_of_verse">This ayah\'s tafseer is included with the tafseer of ayah %d (Click to expand).</string>

<!-- translation updates -->
<string name="update_available">Update Available</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down

0 comments on commit 8ca625d

Please sign in to comment.