package yuku.alkitab.base.widget; import android.content.ContentResolver; import android.content.Context; import android.database.Cursor; import android.net.Uri; import android.support.annotation.Nullable; import android.util.Log; import android.view.LayoutInflater; import android.widget.BaseAdapter; import gnu.trove.set.TIntSet; import gnu.trove.set.hash.TIntHashSet; import yuku.alkitab.base.App; import yuku.alkitab.base.S; import yuku.alkitab.base.util.Highlights; import yuku.alkitab.model.PericopeBlock; import yuku.alkitab.model.ProgressMark; import yuku.alkitab.model.SingleChapterVerses; import yuku.alkitab.model.Version; import yuku.alkitab.util.Ari; import java.util.Arrays; public abstract class VerseAdapter extends BaseAdapter { public static final String TAG = VerseAdapter.class.getSimpleName(); // # field ctor CallbackSpan.OnClickListener<Object> parallelListener_; VersesView.AttributeListener attributeListener_; VerseInlineLinkSpan.Factory inlineLinkSpanFactory_; final float density_; // # field setData int ari_bc_; SingleChapterVerses verses_; PericopeBlock[] pericopeBlocks_; Version version_; String versionId_; float textSizeMult_; /** * For each element, if 0 or more, it refers to the 0-based verse number. * If negative, -1 is the index 0 of pericope, -2 (a) is index 1 (b) of pericope, etc. * * Convert a to b: b = -a-1; * Convert b to a: a = -b-1; */ int[] itemPointer_; int[] bookmarkCountMap_; int[] noteCountMap_; Highlights.Info[] highlightInfoMap_; int[] progressMarkBitsMap_; boolean[] hasMapsMap_; LayoutInflater inflater_; // For calling attention. All attentioned verses have the same start time. // The last call to callAttentionForVerse() decides as when the animation starts. // Calling setData* methods clears the attentioned verses. long attentionStart_; TIntSet attentionPositions_; public VerseAdapter(Context context) { density_ = context.getResources().getDisplayMetrics().density; inflater_ = LayoutInflater.from(context); } /* non-public */ synchronized void setData(int ariBc, SingleChapterVerses verses, int[] pericopeAris, PericopeBlock[] pericopeBlocks, int nblock, @Nullable final Version version, @Nullable final String versionId) { ari_bc_ = ariBc; verses_ = verses; pericopeBlocks_ = pericopeBlocks; itemPointer_ = makeItemPointer(verses_.getVerseCount(), pericopeAris, pericopeBlocks, nblock); version_ = version; versionId_ = versionId; calculateTextSizeMult(); attentionStart_ = 0; if (attentionPositions_ != null) { attentionPositions_.clear(); } notifyDataSetChanged(); } /* non-public */ synchronized void setDataEmpty() { ari_bc_ = 0; verses_ = null; pericopeBlocks_ = null; itemPointer_ = null; attentionStart_ = 0; if (attentionPositions_ != null) { attentionPositions_.clear(); } notifyDataSetChanged(); } public synchronized void reloadAttributeMap() { // book_ can be empty when the selected (book, chapter) is not available in this version if (ari_bc_ == 0) return; // 1/3: Bookmarks/Notes/Highlights final int[] bookmarkCountMap; final int[] noteCountMap; final Highlights.Info[] highlightColorMap; final int verseCount = verses_.getVerseCount(); final int ariBc = ari_bc_; if (S.getDb().countMarkersForBookChapter(ariBc) > 0) { bookmarkCountMap = new int[verseCount]; noteCountMap = new int[verseCount]; highlightColorMap = new Highlights.Info[verseCount]; S.getDb().putAttributes(ariBc, bookmarkCountMap, noteCountMap, highlightColorMap); } else { bookmarkCountMap = null; noteCountMap = null; highlightColorMap = null; } final int ariMin = ariBc & 0x00ffff00; final int ariMax = ariBc | 0x000000ff; // 2/3: Progress marks int[] progressMarkBitsMap = null; for (final ProgressMark progressMark: S.getDb().listAllProgressMarks()) { final int ari = progressMark.ari; if (ari < ariMin || ari >= ariMax) { continue; } if (progressMarkBitsMap == null) { progressMarkBitsMap = new int[verseCount]; } int mapOffset = Ari.toVerse(ari) - 1; if (mapOffset >= progressMarkBitsMap.length) { Log.e(TAG, "(for progressMarkBitsMap:) mapOffset out of bounds: " + mapOffset + " happened on ari 0x" + Integer.toHexString(ari)); } else { progressMarkBitsMap[mapOffset] |= 1 << (progressMark.preset_id + AttributeView.PROGRESS_MARK_BITS_START); } } // 3/3: Location indicators // Look up for maps locations. // If the app is installed, query its content provider to see which verses has locations on the map. boolean[] hasMapsMap = null; { final ContentResolver cr = App.context.getContentResolver(); final Uri uri = Uri.parse("content://palki.maps/exists?ari=" + ariBc); try (Cursor c = cr.query(uri, null, null, null, null)) { if (c != null) { final int col_aris = c.getColumnIndexOrThrow("aris"); if (c.moveToNext()) { final String aris_json = c.getString(col_aris); final int[] aris = App.getDefaultGson().fromJson(aris_json, int[].class); if (aris != null) { hasMapsMap = new boolean[verseCount]; for (final int ari : aris) { int mapOffset = Ari.toVerse(ari) - 1; if (mapOffset >= hasMapsMap.length) { Log.e(TAG, "(for hasMapsMap:) mapOffset out of bounds: " + mapOffset + " happened on ari 0x" + Integer.toHexString(ari)); } else { hasMapsMap[mapOffset] = true; } } } } } } } // Finish calculating bookmarkCountMap_ = bookmarkCountMap; noteCountMap_ = noteCountMap; highlightInfoMap_ = highlightColorMap; progressMarkBitsMap_ = progressMarkBitsMap; hasMapsMap_ = hasMapsMap; notifyDataSetChanged(); } @Override public synchronized int getCount() { if (verses_ == null) return 0; return itemPointer_.length; } @Override public synchronized String getItem(int position) { int id = itemPointer_[position]; if (id >= 0) { return verses_.getVerse(position); } else { return pericopeBlocks_[-id - 1].toString(); } } @Override public synchronized long getItemId(int position) { return position; } public void setParallelListener(CallbackSpan.OnClickListener<Object> parallelListener) { parallelListener_ = parallelListener; notifyDataSetChanged(); } public VersesView.AttributeListener getAttributeListener() { return attributeListener_; } public void setAttributeListener(VersesView.AttributeListener attributeListener) { attributeListener_ = attributeListener; notifyDataSetChanged(); } public void setInlineLinkSpanFactory(final VerseInlineLinkSpan.Factory inlineLinkSpanFactory) { inlineLinkSpanFactory_ = inlineLinkSpanFactory; notifyDataSetChanged(); } /** * For example, when pos=0 is a pericope and pos=1 is the first verse, * this method returns 0. * * @return position on this adapter, or -1 if not found */ public int getPositionOfPericopeBeginningFromVerse(int verse_1) { if (itemPointer_ == null) return -1; int verse_0 = verse_1 - 1; for (int i = 0, len = itemPointer_.length; i < len; i++) { if (itemPointer_[i] == verse_0) { // we've found it, but if we can move back to pericopes, it is better. for (int j = i - 1; j >= 0; j--) { if (itemPointer_[j] < 0) { // it's still pericope, so let's continue i = j; } else { // no longer a pericope (means, we are on the previous verse) break; } } return i; } } return -1; } /** * Let's say pos 0 is pericope and pos 1 is verse_1 1; * then this method called with verse_1=1 returns 1. * * @return position or -1 if not found */ public int getPositionIgnoringPericopeFromVerse(int verse_1) { if (itemPointer_ == null) return -1; int verse_0 = verse_1 - 1; for (int i = 0, len = itemPointer_.length; i < len; i++) { if (itemPointer_[i] == verse_0) return i; } return -1; } /** * @return verse_1 or 0 if doesn't make sense */ public int getVerseFromPosition(int position) { if (itemPointer_ == null) return 0; if (position >= itemPointer_.length) { position = itemPointer_.length - 1; } int id = itemPointer_[position]; if (id >= 0) { return id + 1; } // it's a pericope. Let's move forward until we get a verse for (int i = position + 1; i < itemPointer_.length; i++) { id = itemPointer_[i]; if (id >= 0) { return id + 1; } } Log.w(TAG, "pericope title at the last position? does not make sense."); return 0; } void callAttentionForVerse(final int verse_1) { final int pos = getPositionIgnoringPericopeFromVerse(verse_1); if (pos != -1) { TIntSet ap = attentionPositions_; if (ap == null) { attentionPositions_ = ap = new TIntHashSet(); } ap.add(pos); attentionStart_ = System.currentTimeMillis(); notifyDataSetChanged(); } } /** * Similar to {@link #getVerseFromPosition(int)}, but returns 0 if the specified position is a pericope or doesn't make sense. */ public int getVerseOrPericopeFromPosition(int position) { if (itemPointer_ == null) return 0; if (position < 0 || position >= itemPointer_.length) { return 0; } int id = itemPointer_[position]; if (id >= 0) { return id + 1; } else { return 0; } } @Nullable public String getVerseText(int verse_1) { if (verses_ == null) return null; if (verse_1 < 1 || verse_1 > verses_.getVerseCount()) return null; return verses_.getVerse(verse_1 - 1); } public int getVerseCount() { if (verses_ == null) return 0; return verses_.getVerseCount(); } @Override public boolean areAllItemsEnabled() { return false; } @Override public boolean isEnabled(int position) { final int[] _itemPointer = this.itemPointer_; // guard against wild ListView.onInitializeAccessibilityNodeInfoForItem if (_itemPointer == null) return false; if (position >= _itemPointer.length) return false; return _itemPointer[position] >= 0; } private static int[] makeItemPointer(int nverse, int[] pericopeAris, PericopeBlock[] pericopeBlocks, int nblock) { int[] res = new int[nverse + nblock]; int pos_block = 0; int pos_verse = 0; int pos_itemPointer = 0; while (true) { // check if we still have pericopes remaining if (pos_block < nblock) { // still possible if (Ari.toVerse(pericopeAris[pos_block]) - 1 == pos_verse) { // We have a pericope. res[pos_itemPointer++] = -pos_block - 1; pos_block++; continue; } } // check if there is no verses remaining if (pos_verse >= nverse) { break; } // there is no more pericopes, OR not the time yet for pericopes. So we insert a verse. res[pos_itemPointer++] = pos_verse; pos_verse++; } if (res.length != pos_itemPointer) { throw new RuntimeException("Algorithm to insert pericopes error!! pos_itemPointer=" + pos_itemPointer + " pos_verse=" + pos_verse + " pos_block=" + pos_block + " nverse=" + nverse + " nblock=" + nblock + " pericopeAris:" + Arrays.toString(pericopeAris) + " pericopeBlocks:" + Arrays.toString(pericopeBlocks)); } return res; } /* non-public */ void calculateTextSizeMult() { textSizeMult_ = versionId_ == null ? 1.f : S.getDb().getPerVersionSettings(versionId_).fontSizeMultiplier; } }