package yuku.alkitab.base.widget;
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.text.SpannableStringBuilder;
import android.util.Log;
import android.util.SparseBooleanArray;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ListView;
import android.widget.TextView;
import yuku.afw.storage.Preferences;
import yuku.alkitab.base.S;
import yuku.alkitab.base.U;
import yuku.alkitab.base.util.Appearances;
import yuku.alkitab.base.util.Highlights;
import yuku.alkitab.base.util.TargetDecoder;
import yuku.alkitab.debug.R;
import yuku.alkitab.model.PericopeBlock;
import yuku.alkitab.model.SingleChapterVerses;
import yuku.alkitab.util.Ari;
import yuku.alkitab.util.IntArrayList;
public class SingleViewVerseAdapter extends VerseAdapter {
public static final String TAG = SingleViewVerseAdapter.class.getSimpleName();
public static final int TYPE_VERSE_TEXT = 0;
public static final int TYPE_PERICOPE = 1;
private SparseBooleanArray dictionaryModeAris;
public static class DictionaryLinkInfo {
public String orig_text;
public String key;
public DictionaryLinkInfo(final String orig_text, final String key) {
this.orig_text = orig_text;
this.key = key;
}
}
CallbackSpan.OnClickListener<DictionaryLinkInfo> dictionaryListener_;
public SingleViewVerseAdapter(Context context) {
super(context);
}
@Override
public int getViewTypeCount() {
return 2;
}
@Override
public int getItemViewType(final int position) {
final int id = itemPointer_[position];
if (id >= 0) {
return TYPE_VERSE_TEXT;
} else {
return TYPE_PERICOPE;
}
}
@Override public synchronized View getView(int position, View convertView, ViewGroup parent) {
// Need to determine this is pericope or verse
final int id = itemPointer_[position];
if (id >= 0) {
// VERSE. not pericope
final int verse_1 = id + 1;
boolean checked = false;
if (parent instanceof ListView) {
checked = ((ListView) parent).isItemChecked(position);
}
final VerseItem res;
if (convertView == null) {
res = (VerseItem) inflater_.inflate(R.layout.item_verse, parent, false);
} else {
res = (VerseItem) convertView;
}
final int ari = Ari.encodeWithBc(ari_bc_, verse_1);
final String text = verses_.getVerse(id);
final String verseNumberText = verses_.getVerseNumberText(id);
final Highlights.Info highlightInfo = highlightInfoMap_ == null ? null : highlightInfoMap_[id];
final VerseTextView lText = res.lText;
final TextView lVerseNumber = res.lVerseNumber;
final int startVerseTextPos = VerseRenderer.render(lText, lVerseNumber, ari, text, verseNumberText, highlightInfo, checked, inlineLinkSpanFactory_, null);
final float textSizeMult;
if (verses_ instanceof SingleChapterVerses.WithTextSizeMult) {
textSizeMult = ((SingleChapterVerses.WithTextSizeMult) verses_).getTextSizeMult(id);
} else {
textSizeMult = textSizeMult_;
}
Appearances.applyTextAppearance(lText, textSizeMult);
Appearances.applyVerseNumberAppearance(lVerseNumber, textSizeMult);
if (checked) { // override text color with black or white!
final int selectedTextColor = U.getTextColorForSelectedVerse(Preferences.getInt(R.string.pref_selectedVerseBgColor_key, R.integer.pref_selectedVerseBgColor_default));
lText.setTextColor(selectedTextColor);
lVerseNumber.setTextColor(selectedTextColor);
}
final AttributeView attributeView = res.attributeView;
attributeView.setScale(scaleForAttributeView(S.applied.fontSize2dp * textSizeMult_));
attributeView.setBookmarkCount(bookmarkCountMap_ == null ? 0 : bookmarkCountMap_[id]);
attributeView.setNoteCount(noteCountMap_ == null ? 0 : noteCountMap_[id]);
attributeView.setProgressMarkBits(progressMarkBitsMap_ == null ? 0 : progressMarkBitsMap_[id]);
attributeView.setHasMaps(hasMapsMap_ != null && hasMapsMap_[id]);
attributeView.setAttributeListener(attributeListener_, version_, versionId_, ari);
res.setCollapsed(text.length() == 0 && !attributeView.isShowingSomething());
res.setAri(ari);
/*
* Dictionary mode is activated on either of these conditions:
* 1. user manually activate dictionary mode after selecting verses
* 2. automatic lookup is on and this verse is selected (checked)
*/
if ((dictionaryModeAris != null && dictionaryModeAris.get(ari))
|| (checked && Preferences.getBoolean(res.getContext().getString(R.string.pref_autoDictionaryAnalyze_key), res.getContext().getResources().getBoolean(R.bool.pref_autoDictionaryAnalyze_default)))
) {
final ContentResolver cr = res.getContext().getContentResolver();
final CharSequence renderedText = lText.getText();
final SpannableStringBuilder verseText = renderedText instanceof SpannableStringBuilder ? (SpannableStringBuilder) renderedText : new SpannableStringBuilder(renderedText);
// we have to exclude the verse numbers from analyze text
final String analyzeString = verseText.toString().substring(startVerseTextPos);
final Uri uri = Uri.parse("content://org.sabda.kamus.provider/analyze").buildUpon().appendQueryParameter("text", analyzeString).build();
Cursor c = null;
try {
c = cr.query(uri, null, null, null, null);
} catch (Exception e) {
Log.e(TAG, "Error when querying dictionary content provider", e);
}
if (c != null) {
try {
final int col_offset = c.getColumnIndexOrThrow("offset");
final int col_len = c.getColumnIndexOrThrow("len");
final int col_key = c.getColumnIndexOrThrow("key");
while (c.moveToNext()) {
final int offset = c.getInt(col_offset);
final int len = c.getInt(col_len);
final String key = c.getString(col_key);
verseText.setSpan(new CallbackSpan<>(new DictionaryLinkInfo(analyzeString.substring(offset, offset + len), key), dictionaryListener_), startVerseTextPos + offset, startVerseTextPos + offset + len, 0);
}
} finally {
c.close();
}
lText.setText(verseText);
}
}
// { // DUMP
// Log.d(TAG, "==== DUMP verse " + (id + 1));
// SpannedString sb = (SpannedString) lText.getText();
// Object[] spans = sb.getSpans(0, sb.length(), Object.class);
// for (Object span: spans) {
// int start = sb.getSpanStart(span);
// int end = sb.getSpanEnd(span);
// Log.d(TAG, "Span " + span.getClass().getSimpleName() + " " + start + ".." + end + ": " + sb.toString().substring(start, end));
// }
// }
// Do we need to call attention?
if (attentionStart_ != 0 && attentionPositions_ != null && attentionPositions_.contains(position)) {
res.callAttention(attentionStart_);
} else {
res.callAttention(0);
}
return res;
} else {
// PERICOPE. not verse.
final PericopeHeaderItem res;
if (convertView == null) {
res = (PericopeHeaderItem) inflater_.inflate(R.layout.item_pericope_header, parent, false);
} else {
res = (PericopeHeaderItem) convertView;
}
PericopeBlock pericopeBlock = pericopeBlocks_[-id - 1];
TextView lCaption = (TextView) res.findViewById(R.id.lCaption);
TextView lParallels = (TextView) res.findViewById(R.id.lParallels);
lCaption.setText(FormattedTextRenderer.render(pericopeBlock.title));
int paddingTop;
// turn off top padding if the position == 0 OR before this is also a pericope title
if (position == 0 || itemPointer_[position - 1] < 0) {
paddingTop = 0;
} else {
paddingTop = S.applied.pericopeSpacingTop;
}
res.setPadding(0, paddingTop, 0, S.applied.pericopeSpacingBottom);
Appearances.applyPericopeTitleAppearance(lCaption, textSizeMult_);
// make parallel gone if not exist
if (pericopeBlock.parallels.length == 0) {
lParallels.setVisibility(View.GONE);
} else {
lParallels.setVisibility(View.VISIBLE);
SpannableStringBuilder sb = new SpannableStringBuilder("(");
int total = pericopeBlock.parallels.length;
for (int i = 0; i < total; i++) {
String parallel = pericopeBlock.parallels[i];
if (i > 0) {
// force new line for certain parallel patterns
if ((total == 6 && i == 3) || (total == 4 && i == 2) || (total == 5 && i == 3)) {
sb.append("; \n");
} else {
sb.append("; ");
}
}
appendParallel(sb, parallel);
}
sb.append(')');
lParallels.setText(sb, TextView.BufferType.SPANNABLE);
Appearances.applyPericopeParallelTextAppearance(lParallels, textSizeMult_);
}
return res;
}
}
static float scaleForAttributeView(final float fontSizeDp) {
if (fontSizeDp >= 13 /* 72% */ && fontSizeDp < 24 /* 133% */) {
return 1.f;
}
if (fontSizeDp < 8) return 0.5f; // 0 ~ 44%
if (fontSizeDp < 18) return 0.75f; // 44% ~ 72%
if (fontSizeDp >= 36) return 2.f; // 200% ~
return 1.5f; // 24 to 36 // 133% ~ 200%
}
private void appendParallel(SpannableStringBuilder sb, String parallel) {
int sb_len = sb.length();
linked: {
if (parallel.startsWith("@")) {
// look for the end
int targetEndPos = parallel.indexOf(' ', 1);
if (targetEndPos == -1) {
break linked;
}
final String target = parallel.substring(1, targetEndPos);
final IntArrayList ariRanges = TargetDecoder.decode(target);
if (ariRanges == null || ariRanges.size() == 0) {
break linked;
}
final String display = parallel.substring(targetEndPos + 1);
// if we reach this, data and display should have values, and we must not go to fallback below
sb.append(display);
sb.setSpan(new CallbackSpan<>(ariRanges.get(0), parallelListener_), sb_len, sb.length(), 0);
return; // do not remove this
}
}
// fallback if the above code fails
sb.append(parallel);
sb.setSpan(new CallbackSpan<>(parallel, parallelListener_), sb_len, sb.length(), 0);
}
public void setDictionaryModeAris(final SparseBooleanArray aris) {
this.dictionaryModeAris = aris;
notifyDataSetChanged();
}
public void setDictionaryListener(final CallbackSpan.OnClickListener<DictionaryLinkInfo> listener) {
this.dictionaryListener_ = listener;
notifyDataSetChanged();
}
}