package com.quran.labs.androidquran.presenter.translation; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import com.quran.labs.androidquran.common.LocalTranslation; import com.quran.labs.androidquran.common.QuranAyahInfo; import com.quran.labs.androidquran.common.QuranText; import com.quran.labs.androidquran.data.SuraAyah; import com.quran.labs.androidquran.data.SuraAyahIterator; import com.quran.labs.androidquran.data.VerseRange; import com.quran.labs.androidquran.database.TranslationsDBAdapter; import com.quran.labs.androidquran.model.translation.TranslationModel; import com.quran.labs.androidquran.presenter.Presenter; import com.quran.labs.androidquran.util.QuranSettings; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import io.reactivex.Observable; import io.reactivex.Single; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; class BaseTranslationPresenter<T> implements Presenter<T> { private final TranslationModel translationModel; private final TranslationsDBAdapter translationsAdapter; private final Map<String, LocalTranslation> translationMap; @Nullable T translationScreen; Disposable disposable; BaseTranslationPresenter(TranslationModel translationModel, TranslationsDBAdapter translationsAdapter) { this.translationMap = new HashMap<>(); this.translationModel = translationModel; this.translationsAdapter = translationsAdapter; } Single<ResultHolder> getVerses(boolean getArabic, List<String> translations, VerseRange verseRange) { Single<List<List<QuranText>>> translationsObservable = Observable.fromIterable(translations) .concatMapEager(db -> translationModel.getTranslationFromDatabase(verseRange, db) .map(texts -> ensureProperTranslations(verseRange, texts)) .onErrorReturnItem(new ArrayList<>()) .toObservable()) .toList(); Single<List<QuranText>> arabicObservable = !getArabic ? Single.just(new ArrayList<>()) : translationModel.getArabicFromDatabase(verseRange).onErrorReturnItem(new ArrayList<>()); return Single.zip(arabicObservable, translationsObservable, getTranslationMapSingle(), (arabic, texts, map) -> { List<QuranAyahInfo> ayahInfo = combineAyahData(verseRange, arabic, texts); String[] translationNames = getTranslationNames(translations, map); return new ResultHolder(translationNames, ayahInfo); }) .subscribeOn(Schedulers.io()); } List<String> getTranslations(QuranSettings quranSettings) { List<String> results = new ArrayList<>(); results.addAll(quranSettings.getActiveTranslations()); return results; } String[] getTranslationNames(@NonNull List<String> translations, @NonNull Map<String, LocalTranslation> translationMap) { int translationCount = translations.size(); String[] result = new String[translationCount]; for (int i = 0; i < translationCount; i++) { String translation = translations.get(i); LocalTranslation localTranslation = translationMap.get(translation); result[i] = localTranslation == null ? translation : localTranslation.getTranslatorName(); } return result; } @NonNull List<QuranAyahInfo> combineAyahData(@NonNull VerseRange verseRange, @NonNull List<QuranText> arabic, @NonNull List<List<QuranText>> texts) { final int arabicSize = arabic.size(); final int translationCount = texts.size(); List<QuranAyahInfo> result = new ArrayList<>(); if (translationCount > 0) { final int verses = arabicSize == 0 ? verseRange.versesInRange : arabicSize; for (int i = 0; i < verses; i++) { QuranText element = arabicSize == 0 ? null : arabic.get(i); final List<String> ayahTranslations = new ArrayList<>(); for (int j = 0; j < translationCount; j++) { QuranText item = texts.get(j).size() > i ? texts.get(j).get(i) : null; if (item != null) { ayahTranslations.add(texts.get(j).get(i).text); element = item; } else { // this keeps the translations aligned with their translators // even when a particular translator doesn't load. ayahTranslations.add(""); } } if (element != null) { String arabicText = arabicSize == 0 ? null : arabic.get(i).text; result.add( new QuranAyahInfo(element.sura, element.ayah, arabicText, ayahTranslations)); } } } else if (arabicSize > 0) { for (int i = 0; i < arabicSize; i++) { QuranText arabicItem = arabic.get(i); result.add(new QuranAyahInfo(arabicItem.sura, arabicItem.ayah, arabicItem.text, Collections.emptyList())); } } return result; } /** * Ensures that the list of translations is valid * In this case, valid means that the number of verses that we have translations for is either * the same as the verse range, or that it's 0 (i.e. due to an error querying the database). If * the list has a non-zero length that is less than what the verseRange says, it adds empty * entries for those. * * @param verseRange the range of verses we're trying to get * @param texts the data we got back from the database * @return a list of QuranText with a length of either 0 or the verse range */ @NonNull List<QuranText> ensureProperTranslations(@NonNull VerseRange verseRange, @NonNull List<QuranText> texts) { int expectedVerses = verseRange.versesInRange; int textSize = texts.size(); if (textSize == 0 || textSize == expectedVerses) { return texts; } // missing some entries for some ayat - this is a work around for bad data in some databases // ex. ibn katheer is missing 3 records, 1 in each of suras 5, 17, and 87. SuraAyah start = new SuraAyah(verseRange.startSura, verseRange.startAyah); SuraAyah end = new SuraAyah(verseRange.endingSura, verseRange.endingAyah); SuraAyahIterator iterator = new SuraAyahIterator(start, end); int i = 0; while (iterator.next()) { QuranText item = texts.size() > i ? texts.get(i) : null; if (item == null || item.sura != iterator.getSura() || item.ayah != iterator.getAyah()) { texts.add(i, new QuranText(iterator.getSura(), iterator.getAyah(), "")); } i++; } return texts; } @NonNull private Single<Map<String, LocalTranslation>> getTranslationMapSingle() { if (this.translationMap.size() == 0) { return Single.fromCallable(translationsAdapter::getTranslations) .map(translations -> { Map<String, LocalTranslation> map = new HashMap<>(); for (int i = 0, size = translations.size(); i < size; i++) { LocalTranslation translation = translations.get(i); map.put(translation.filename, translation); } return map; }) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .doOnSuccess(map -> { this.translationMap.clear(); this.translationMap.putAll(map); }); } else { return Single.just(this.translationMap); } } static class ResultHolder { final String[] translations; final List<QuranAyahInfo> ayahInformation; ResultHolder(String[] translations, List<QuranAyahInfo> ayahInformation) { this.translations = translations; this.ayahInformation = ayahInformation; } } @Override public void bind(T what) { translationScreen = what; } @Override public void unbind(T what) { translationScreen = null; if (disposable != null) { disposable.dispose(); } } }