package yuku.alkitab.base; import android.annotation.TargetApi; import android.app.PendingIntent; import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.database.Cursor; import android.graphics.Point; import android.graphics.drawable.Drawable; import android.net.Uri; import android.nfc.NdefMessage; import android.nfc.NdefRecord; import android.nfc.NfcAdapter; import android.os.Build; import android.os.Bundle; import android.os.Parcelable; import android.support.design.widget.Snackbar; import android.support.v4.app.FragmentManager; import android.support.v4.app.ShareCompat; import android.support.v4.content.res.ResourcesCompat; import android.support.v4.graphics.ColorUtils; import android.support.v4.widget.DrawerLayout; import android.support.v7.app.ActionBar; import android.support.v7.view.ActionMode; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.SwitchCompat; import android.support.v7.widget.Toolbar; import android.text.Html; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.format.DateFormat; import android.text.style.ForegroundColorSpan; import android.text.style.RelativeSizeSpan; import android.text.style.URLSpan; import android.util.Log; import android.util.SparseBooleanArray; import android.view.Gravity; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.view.WindowManager; import android.widget.FrameLayout; import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; import com.afollestad.materialdialogs.MaterialDialog; import org.json.JSONException; import org.json.JSONObject; import yuku.afw.V; import yuku.afw.storage.Preferences; import yuku.alkitab.base.ac.GotoActivity; import yuku.alkitab.base.ac.MarkerListActivity; import yuku.alkitab.base.ac.MarkersActivity; import yuku.alkitab.base.ac.NoteActivity; import yuku.alkitab.base.ac.SearchActivity; import yuku.alkitab.base.ac.SettingsActivity; import yuku.alkitab.base.ac.ShareActivity; import yuku.alkitab.base.ac.base.BaseLeftDrawerActivity; import yuku.alkitab.base.config.AppConfig; import yuku.alkitab.base.dialog.ProgressMarkListDialog; import yuku.alkitab.base.dialog.ProgressMarkRenameDialog; import yuku.alkitab.base.dialog.TypeBookmarkDialog; import yuku.alkitab.base.dialog.TypeHighlightDialog; import yuku.alkitab.base.dialog.VersesDialog; import yuku.alkitab.base.dialog.XrefDialog; import yuku.alkitab.base.model.MVersion; import yuku.alkitab.base.model.MVersionDb; import yuku.alkitab.base.model.MVersionInternal; import yuku.alkitab.base.model.VersionImpl; import yuku.alkitab.base.storage.Prefkey; import yuku.alkitab.base.util.Announce; import yuku.alkitab.base.util.Appearances; import yuku.alkitab.base.util.CurrentReading; import yuku.alkitab.base.util.ExtensionManager; import yuku.alkitab.base.util.Highlights; import yuku.alkitab.base.util.History; import yuku.alkitab.base.util.Jumper; import yuku.alkitab.base.util.LidToAri; import yuku.alkitab.base.util.OtherAppIntegration; import yuku.alkitab.base.util.ShareUrl; import yuku.alkitab.base.util.Sqlitil; import yuku.alkitab.base.widget.CallbackSpan; import yuku.alkitab.base.widget.Floater; import yuku.alkitab.base.widget.FormattedTextRenderer; import yuku.alkitab.base.widget.GotoButton; import yuku.alkitab.base.widget.LabeledSplitHandleButton; import yuku.alkitab.base.widget.LeftDrawer; import yuku.alkitab.base.widget.MaterialDialogAdapterHelper; import yuku.alkitab.base.widget.ScrollbarSetter; import yuku.alkitab.base.widget.SingleViewVerseAdapter; import yuku.alkitab.base.widget.SplitHandleButton; import yuku.alkitab.base.widget.TextAppearancePanel; import yuku.alkitab.base.widget.TwofingerLinearLayout; import yuku.alkitab.base.widget.VerseInlineLinkSpan; import yuku.alkitab.base.widget.VerseRenderer; import yuku.alkitab.base.widget.VersesView; import yuku.alkitab.debug.BuildConfig; import yuku.alkitab.debug.R; import yuku.alkitab.model.Book; import yuku.alkitab.model.FootnoteEntry; import yuku.alkitab.model.Label; import yuku.alkitab.model.Marker; 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 yuku.alkitab.util.IntArrayList; import yuku.devoxx.flowlayout.FlowLayout; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; import java.util.List; import java.util.Locale; import static yuku.alkitab.base.util.Literals.Array; public class IsiActivity extends BaseLeftDrawerActivity implements XrefDialog.XrefDialogListener, LeftDrawer.Text.Listener, ProgressMarkListDialog.Listener { public static final String TAG = IsiActivity.class.getSimpleName(); public static final String ACTION_ATTRIBUTE_MAP_CHANGED = "yuku.alkitab.action.ATTRIBUTE_MAP_CHANGED"; public static final String ACTION_ACTIVE_VERSION_CHANGED = IsiActivity.class.getName() + ".action.ACTIVE_VERSION_CHANGED"; public static final String ACTION_NIGHT_MODE_CHANGED = IsiActivity.class.getName() + ".action.NIGHT_MODE_CHANGED"; public static final String ACTION_NEEDS_RESTART = IsiActivity.class.getName() + ".action.NEEDS_RESTART"; private static final int REQCODE_goto = 1; private static final int REQCODE_share = 7; private static final int REQCODE_textAppearanceGetFonts = 9; private static final int REQCODE_textAppearanceCustomColors = 10; private static final int REQCODE_edit_note_1 = 11; private static final int REQCODE_edit_note_2 = 12; private static final String EXTRA_verseUrl = "verseUrl"; private boolean uncheckVersesWhenActionModeDestroyed = true; private boolean needsRestart; // whether this activity needs to be restarted private GotoButton.FloaterDragListener bGoto_floaterDrag = new GotoButton.FloaterDragListener() { final int[] floaterLocationOnScreen = {0, 0}; @Override public void onFloaterDragStart(final float screenX, final float screenY) { floater.show(activeBook.bookId, chapter_1); floater.onDragStart(S.activeVersion); } @Override public void onFloaterDragMove(final float screenX, final float screenY) { floater.getLocationOnScreen(floaterLocationOnScreen); floater.onDragMove(screenX - floaterLocationOnScreen[0], screenY - floaterLocationOnScreen[1]); } @Override public void onFloaterDragComplete(final float screenX, final float screenY) { floater.hide(); floater.onDragComplete(screenX - floaterLocationOnScreen[0], screenY - floaterLocationOnScreen[1]); } }; final Floater.Listener floater_listener = new Floater.Listener() { @Override public void onSelectComplete(final int ari) { jumpToAri(ari); history.add(ari); } }; TwofingerLinearLayout.Listener splitRoot_listener = new TwofingerLinearLayout.Listener() { float startFontSize; float startDx = Float.MIN_VALUE; float chapterSwipeCellWidth; // initted later boolean moreSwipeYAllowed = true; // to prevent setting and unsetting fullscreen many times within one gesture @Override public void onOnefingerLeft() { App.trackEvent("text_onefinger_left"); bRight_click(); } @Override public void onOnefingerRight() { App.trackEvent("text_onefinger_right"); bLeft_click(); } @Override public void onTwofingerStart() { chapterSwipeCellWidth = 24.f * getResources().getDisplayMetrics().density; startFontSize = Preferences.getFloat(Prefkey.ukuranHuruf2, (float) App.context.getResources().getInteger(R.integer.pref_ukuranHuruf2_default)); } @Override public void onTwofingerScale(final float scale) { float nowFontSize = startFontSize * scale; if (nowFontSize < 2.f) nowFontSize = 2.f; if (nowFontSize > 42.f) nowFontSize = 42.f; Preferences.setFloat(Prefkey.ukuranHuruf2, nowFontSize); applyPreferences(); if (textAppearancePanel != null) { textAppearancePanel.displayValues(); } } @Override public void onTwofingerDragX(final float dx) { if (startDx == Float.MIN_VALUE) { // just started startDx = dx; if (dx < 0) { bRight_click(); } else { bLeft_click(); } } else { // more // more to the left while (dx < startDx - chapterSwipeCellWidth) { startDx -= chapterSwipeCellWidth; bRight_click(); } while (dx > startDx + chapterSwipeCellWidth) { startDx += chapterSwipeCellWidth; bLeft_click(); } } } @Override public void onTwofingerDragY(final float dy) { if (!moreSwipeYAllowed) return; if (dy < 0) { App.trackEvent("text_twofinger_up"); setFullScreen(true); leftDrawer.getHandle().setFullScreen(true); moreSwipeYAllowed = false; } else { App.trackEvent("text_twofinger_down"); setFullScreen(false); leftDrawer.getHandle().setFullScreen(false); moreSwipeYAllowed = false; } } @Override public void onTwofingerEnd(final TwofingerLinearLayout.Mode mode) { startFontSize = 0; startDx = Float.MIN_VALUE; moreSwipeYAllowed = true; } }; DrawerLayout drawerLayout; LeftDrawer.Text leftDrawer; FrameLayout overlayContainer; ViewGroup root; Toolbar toolbar; VersesView lsSplit0; VersesView lsSplit1; TextView tSplitEmpty; TwofingerLinearLayout splitRoot; LabeledSplitHandleButton splitHandleButton; GotoButton bGoto; ImageButton bLeft; ImageButton bRight; TextView bVersion; Floater floater; Book activeBook; int chapter_1 = 0; boolean fullScreen; Toast fullScreenToast; History history; NfcAdapter nfcAdapter; ActionMode actionMode; boolean dictionaryMode; TextAppearancePanel textAppearancePanel; // temporary states Boolean hasEsvsbAsal; Version activeSplitVersion; String activeSplitVersionId; final CallbackSpan.OnClickListener<Object> parallelListener = (widget, data) -> { if (data instanceof String) { final int ari = jumpTo((String) data); if (ari != 0) { history.add(ari); } } else if (data instanceof Integer) { final int ari = (Integer) data; jumpToAri(ari); history.add(ari); } }; final CallbackSpan.OnClickListener<SingleViewVerseAdapter.DictionaryLinkInfo> dictionaryListener = (widget, data) -> { final ContentResolver cr = getContentResolver(); final Uri uri = Uri.parse("content://org.sabda.kamus.provider/define").buildUpon() .appendQueryParameter("key", data.key) .appendQueryParameter("mode", "snippet") .build(); final Cursor c = cr.query(uri, null, null, null, null); if (c == null) { OtherAppIntegration.askToInstallDictionary(this); } else { try { if (c.getCount() == 0) { new MaterialDialog.Builder(this) .content(R.string.dict_no_results) .positiveText(R.string.ok) .show(); } else { c.moveToNext(); final Spanned rendered = Html.fromHtml(c.getString(c.getColumnIndexOrThrow("definition"))); final SpannableStringBuilder sb = rendered instanceof SpannableStringBuilder ? (SpannableStringBuilder) rendered : new SpannableStringBuilder(rendered); // remove links for (final URLSpan span : sb.getSpans(0, sb.length(), URLSpan.class)) { sb.removeSpan(span); } new MaterialDialog.Builder(this) .title(data.orig_text) .content(sb) .positiveText(R.string.dict_open_full) .onPositive((dialog, which) -> { final Intent intent = new Intent("org.sabda.kamus.action.VIEW"); intent.putExtra("key", data.key); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); try { startActivity(intent); } catch (ActivityNotFoundException e) { OtherAppIntegration.askToInstallDictionary(IsiActivity.this); } }) .show(); } } finally { c.close(); } } }; final ViewTreeObserver.OnGlobalLayoutListener splitRoot_globalLayout = new ViewTreeObserver.OnGlobalLayoutListener() { Point lastSize; @Override public void onGlobalLayout() { if (lastSize != null && lastSize.x == splitRoot.getWidth() && lastSize.y == splitRoot.getHeight()) { return; // no need to layout now } if (activeSplitVersion == null) { return; // we are not splitting } configureSplitSizes(); if (lastSize == null) { lastSize = new Point(); } lastSize.x = splitRoot.getWidth(); lastSize.y = splitRoot.getHeight(); } }; static class IntentResult { public int ari; public boolean selectVerse; public int selectVerseCount; public IntentResult(final int ari) { this.ari = ari; } } public static Intent createIntent() { return new Intent(App.context, IsiActivity.class); } public static Intent createIntent(int ari) { Intent res = new Intent(App.context, IsiActivity.class); res.setAction("yuku.alkitab.action.VIEW"); res.putExtra("ari", ari); return res; } final BroadcastReceiver reloadAttributeMapReceiver = new BroadcastReceiver() { @Override public void onReceive(final Context context, final Intent intent) { reloadBothAttributeMaps(); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_isi); drawerLayout = V.get(this, R.id.drawerLayout); leftDrawer = V.get(this, R.id.left_drawer); leftDrawer.configure(this, drawerLayout); toolbar = V.get(this, R.id.toolbar); setSupportActionBar(toolbar); final ActionBar ab = getSupportActionBar(); assert ab != null; ab.setDisplayHomeAsUpEnabled(true); ab.setDisplayShowTitleEnabled(false); ab.setHomeAsUpIndicator(R.drawable.ic_menu_white_24dp); bGoto = V.get(this, R.id.bGoto); bLeft = V.get(this, R.id.bLeft); bRight = V.get(this, R.id.bRight); bVersion = V.get(this, R.id.bVersion); overlayContainer = V.get(this, R.id.overlayContainer); root = V.get(this, R.id.root); lsSplit0 = V.get(this, R.id.lsSplit0); lsSplit1 = V.get(this, R.id.lsSplit1); tSplitEmpty = V.get(this, R.id.tSplitEmpty); splitRoot = V.get(this, R.id.splitRoot); splitRoot.getViewTreeObserver().addOnGlobalLayoutListener(splitRoot_globalLayout); splitHandleButton = V.get(this, R.id.splitHandleButton); floater = V.get(this, R.id.floater); // If layout is changed, updateToolbarLocation must be updated as well. This will be called in DEBUG to make sure // updateToolbarLocation is also updated when layout is updated. if (BuildConfig.DEBUG) { if (root.getChildCount() != 2 || root.getChildAt(0).getId() != R.id.toolbar || root.getChildAt(1).getId() != R.id.splitRoot) { throw new RuntimeException("Layout changed and this is no longer compatible with updateToolbarLocation"); } } updateToolbarLocation(); lsSplit0.setName("lsSplit0"); lsSplit1.setName("lsSplit1"); splitRoot.setListener(splitRoot_listener); bGoto.setOnClickListener(v -> bGoto_click()); bGoto.setOnLongClickListener(v -> { bGoto_longClick(); return true; }); bGoto.setFloaterDragListener(bGoto_floaterDrag); bLeft.setOnClickListener(v -> bLeft_click()); bRight.setOnClickListener(v -> bRight_click()); bVersion.setOnClickListener(v -> bVersion_click()); floater.setListener(floater_listener); lsSplit0.setOnKeyListener((v, keyCode, event) -> { int action = event.getAction(); if (action == KeyEvent.ACTION_DOWN) { return press(keyCode); } else if (action == KeyEvent.ACTION_MULTIPLE) { return press(keyCode); } return false; }); // listeners lsSplit0.setParallelListener(parallelListener); lsSplit0.setAttributeListener(new AttributeListener()); // have to be distinct from lsSplit1 lsSplit0.setInlineLinkSpanFactory(new VerseInlineLinkSpanFactory(lsSplit0)); lsSplit0.setSelectedVersesListener(lsSplit0_selectedVerses); lsSplit0.setOnVerseScrollListener(lsSplit0_verseScroll); lsSplit0.setDictionaryListener(dictionaryListener); // additional setup for split1 lsSplit1.setVerseSelectionMode(VersesView.VerseSelectionMode.multiple); lsSplit1.setEmptyView(tSplitEmpty); lsSplit1.setParallelListener(parallelListener); lsSplit1.setAttributeListener(new AttributeListener()); // have to be distinct from lsSplit0 lsSplit1.setInlineLinkSpanFactory(new VerseInlineLinkSpanFactory(lsSplit1)); lsSplit1.setSelectedVersesListener(lsSplit1_selectedVerses); lsSplit1.setOnVerseScrollListener(lsSplit1_verseScroll); lsSplit1.setDictionaryListener(dictionaryListener); // for splitting splitHandleButton.setListener(splitHandleButton_listener); splitHandleButton.setButtonPressListener(splitHandleButton_labelPressed); // migrate old history? History.migrateOldHistoryWhenNeeded(); history = History.getInstance(); initNfcIfAvailable(); final IntentResult intentResult = processIntent(getIntent(), "onCreate"); final int openingAri; final boolean selectVerse; final int selectVerseCount; if (intentResult == null) { // restore the last (version; book; chapter and verse). final int lastBookId = Preferences.getInt(Prefkey.lastBookId, 0); final int lastChapter = Preferences.getInt(Prefkey.lastChapter, 0); final int lastVerse = Preferences.getInt(Prefkey.lastVerse, 0); openingAri = Ari.encode(lastBookId, lastChapter, lastVerse); selectVerse = false; selectVerseCount = 1; Log.d(TAG, "Going to the last: bookId=" + lastBookId + " chapter=" + lastChapter + " verse=" + lastVerse); } else { openingAri = intentResult.ari; selectVerse = intentResult.selectVerse; selectVerseCount = intentResult.selectVerseCount; } final String lastVersionId = Preferences.getString(Prefkey.lastVersionId); final MVersion mv = getVersionFromVersionId(lastVersionId); if (mv != null) { loadVersion(mv, false); } else { loadVersion(S.getMVersionInternal(), false); } { // load book final Book book = S.activeVersion.getBook(Ari.toBook(openingAri)); if (book != null) { this.activeBook = book; } else { // can't load last book or bookId 0 this.activeBook = S.activeVersion.getFirstBook(); } if (this.activeBook == null) { // version failed to load, so books are also failed to load. Fallback! S.activeVersion = VersionImpl.getInternalVersion(); S.activeVersionId = MVersionInternal.getVersionInternalId(); this.activeBook = S.activeVersion.getFirstBook(); } } // load chapter and verse display(Ari.toChapter(openingAri), Ari.toVerse(openingAri)); if (intentResult != null) { // also add to history if not opening the last seen verse history.add(openingAri); } { // load last split version. This must be after load book, chapter, and verse. final String lastSplitVersionId = Preferences.getString(Prefkey.lastSplitVersionId, null); if (lastSplitVersionId != null) { final String splitOrientation = Preferences.getString(Prefkey.lastSplitOrientation); if (LabeledSplitHandleButton.Orientation.horizontal.name().equals(splitOrientation)) { splitHandleButton.setOrientation(LabeledSplitHandleButton.Orientation.horizontal); } else { splitHandleButton.setOrientation(LabeledSplitHandleButton.Orientation.vertical); } final MVersion splitMv = getVersionFromVersionId(lastSplitVersionId); final MVersion splitMvActual = splitMv == null ? S.getMVersionInternal() : splitMv; if (loadSplitVersion(splitMvActual)) { openSplitDisplay(); displaySplitFollowingMaster(Ari.toVerse(openingAri)); } } } if (selectVerse) { for (int i = 0; i < selectVerseCount; i++) { final int verse_1 = Ari.toVerse(openingAri) + i; callAttentionForVerseToBothSplits(verse_1); } } App.getLbm().registerReceiver(reloadAttributeMapReceiver, new IntentFilter(ACTION_ATTRIBUTE_MAP_CHANGED)); Announce.checkAnnouncements(); App.getLbm().registerReceiver(needsRestartReceiver, new IntentFilter(ACTION_NEEDS_RESTART)); } void callAttentionForVerseToBothSplits(final int verse_1) { lsSplit0.callAttentionForVerse(verse_1); if (activeSplitVersion != null) { lsSplit1.callAttentionForVerse(verse_1); } } final BroadcastReceiver needsRestartReceiver = new BroadcastReceiver() { @Override public void onReceive(final Context context, final Intent intent) { needsRestart = true; } }; @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); processIntent(intent, "onNewIntent"); } @Override protected void onDestroy() { super.onDestroy(); App.getLbm().unregisterReceiver(reloadAttributeMapReceiver); App.getLbm().unregisterReceiver(needsRestartReceiver); } /** * @return non-null if the intent is handled by any of the intent handler (e.g. nfc or VIEW) */ private IntentResult processIntent(Intent intent, String via) { Log.d(TAG, "Got intent via " + via); Log.d(TAG, " action: " + intent.getAction()); Log.d(TAG, " data uri: " + intent.getData()); Log.d(TAG, " component: " + intent.getComponent()); Log.d(TAG, " flags: 0x" + Integer.toHexString(intent.getFlags())); Log.d(TAG, " mime: " + intent.getType()); Bundle extras = intent.getExtras(); Log.d(TAG, " extras: " + (extras == null? "null": extras.size())); if (extras != null) { for (String key: extras.keySet()) { Log.d(TAG, " " + key + " = " + extras.get(key)); } } { final IntentResult result = tryGetIntentResultFromBeam(intent); if (result != null) return result; } { final IntentResult result = tryGetIntentResultFromView(intent); if (result != null) return result; } return null; } /** did we get here from VIEW intent? */ private IntentResult tryGetIntentResultFromView(Intent intent) { if (!U.equals(intent.getAction(), "yuku.alkitab.action.VIEW")) return null; final boolean selectVerse = intent.getBooleanExtra("selectVerse", false); final int selectVerseCount = intent.getIntExtra("selectVerseCount", 1); if (intent.hasExtra("ari")) { int ari = intent.getIntExtra("ari", 0); if (ari != 0) { final IntentResult res = new IntentResult(ari); res.selectVerse = selectVerse; res.selectVerseCount = selectVerseCount; return res; } else { new MaterialDialog.Builder(this) .content("Invalid ari: " + ari) .positiveText(R.string.ok) .show(); return null; } } else if (intent.hasExtra("lid")) { int lid = intent.getIntExtra("lid", 0); int ari = LidToAri.lidToAri(lid); if (ari != 0) { jumpToAri(ari); history.add(ari); final IntentResult res = new IntentResult(ari); res.selectVerse = selectVerse; res.selectVerseCount = selectVerseCount; return res; } else { new MaterialDialog.Builder(this) .content("Invalid lid: " + lid) .positiveText(R.string.ok) .show(); return null; } } else { return null; } } private void initNfcIfAvailable() { nfcAdapter = NfcAdapter.getDefaultAdapter(getApplicationContext()); if (nfcAdapter != null) { nfcAdapter.setNdefPushMessageCallback(event -> { JSONObject obj = new JSONObject(); try { obj.put("ari", Ari.encode(IsiActivity.this.activeBook.bookId, IsiActivity.this.chapter_1, lsSplit0.getVerseBasedOnScroll())); } catch (JSONException e) { // won't happen } byte[] payload = obj.toString().getBytes(); NdefRecord record = new NdefRecord(NdefRecord.TNF_MIME_MEDIA, "application/vnd.yuku.alkitab.nfc.beam".getBytes(), new byte[0], payload); return new NdefMessage(new NdefRecord[] { record, NdefRecord.createApplicationRecord(getPackageName()), }); }, this); } } @Override protected void onPause() { super.onPause(); disableNfcForegroundDispatchIfAvailable(); } private void disableNfcForegroundDispatchIfAvailable() { if (nfcAdapter != null) nfcAdapter.disableForegroundDispatch(this); } @Override protected void onResume() { super.onResume(); enableNfcForegroundDispatchIfAvailable(); } private void enableNfcForegroundDispatchIfAvailable() { if (nfcAdapter != null) { PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, IsiActivity.class).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0); IntentFilter ndef = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED); try { ndef.addDataType("application/vnd.yuku.alkitab.nfc.beam"); } catch (IntentFilter.MalformedMimeTypeException e) { throw new RuntimeException("fail mime type", e); } IntentFilter[] intentFiltersArray = new IntentFilter[] {ndef, }; nfcAdapter.enableForegroundDispatch(this, pendingIntent, intentFiltersArray, null); } } private IntentResult tryGetIntentResultFromBeam(Intent intent) { String action = intent.getAction(); if (!U.equals(action, NfcAdapter.ACTION_NDEF_DISCOVERED)) return null; Parcelable[] rawMsgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES); // only one message sent during the beam if (rawMsgs == null || rawMsgs.length <= 0) return null; NdefMessage msg = (NdefMessage) rawMsgs[0]; // record 0 contains the MIME type, record 1 is the AAR, if present NdefRecord[] records = msg.getRecords(); if (records.length <= 0) return null; String json = new String(records[0].getPayload()); try { JSONObject obj = new JSONObject(json); final int ari = obj.optInt("ari", -1); if (ari == -1) return null; return new IntentResult(ari); } catch (JSONException e) { Log.e(TAG, "Malformed json from nfc", e); return null; } } MVersion getVersionFromVersionId(String versionId) { if (versionId == null || MVersionInternal.getVersionInternalId().equals(versionId)) { return null; // internal is made the same as null } // let's look at yes versions for (MVersionDb mvDb: S.getDb().listAllVersions()) { if (mvDb.getVersionId().equals(versionId)) { if (mvDb.hasDataFile()) { return mvDb; } else { return null; // this is the one that should have been chosen, but the data file is not available, so let's fallback. } } } return null; // not known } boolean loadVersion(final MVersion mv, boolean display) { try { final Version version = mv.getVersion(); if (version == null) { throw new RuntimeException(); // caught below } if (this.activeBook != null) { // we already have some other version loaded, so make the new version open the same book int bookId = this.activeBook.bookId; Book book = version.getBook(bookId); if (book != null) { // we load the new book succesfully this.activeBook = book; } else { // too bad, this book was not found, get any book this.activeBook = version.getFirstBook(); } } S.activeVersion = version; S.activeVersionId = mv.getVersionId(); bVersion.setText(S.getVersionInitials(version)); splitHandleButton.setLabel1("\u25b2 " + getSplitHandleVersionName(mv, version)); if (display) { display(chapter_1, lsSplit0.getVerseBasedOnScroll(), false); } App.getLbm().sendBroadcast(new Intent(ACTION_ACTIVE_VERSION_CHANGED)); return true; } catch (Throwable e) { // so we don't crash on the beginning of the app Log.e(TAG, "Error opening main version", e); new MaterialDialog.Builder(IsiActivity.this) .content(getString(R.string.version_error_opening, mv.longName)) .positiveText(R.string.ok) .show(); return false; } } boolean loadSplitVersion(final MVersion mv) { try { final Version version = mv.getVersion(); if (version == null) { throw new RuntimeException(); // caught below } activeSplitVersion = version; activeSplitVersionId = mv.getVersionId(); splitHandleButton.setLabel2(getSplitHandleVersionName(mv, version) + " \u25bc"); configureTextAppearancePanelForSplitVersion(); return true; } catch (Throwable e) { // so we don't crash on the beginning of the app Log.e(TAG, "Error opening split version", e); new MaterialDialog.Builder(IsiActivity.this) .content(getString(R.string.version_error_opening, mv.longName)) .positiveText(R.string.ok) .show(); return false; } } private void configureTextAppearancePanelForSplitVersion() { if (textAppearancePanel != null) { if (activeSplitVersion == null) { textAppearancePanel.clearSplitVersion(); } else { textAppearancePanel.setSplitVersion(activeSplitVersionId, activeSplitVersion.getLongName()); } } } String getSplitHandleVersionName(MVersion mv, Version version) { String shortName = version.getShortName(); if (shortName != null) { return shortName; } else { // try to get it from the model if (mv.shortName != null) { return mv.shortName; } return version.getLongName(); // this will not be null } } boolean press(int keyCode) { if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) { bLeft_click(); return true; } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) { bRight_click(); return true; } VersesView.PressResult pressResult = lsSplit0.press(keyCode); switch (pressResult.kind) { case left: bLeft_click(); return true; case right: bRight_click(); return true; case consumed: if (activeSplitVersion != null) { lsSplit1.scrollToVerse(pressResult.targetVerse_1); } return true; default: return false; } } /** * Jump to a given verse reference in string format. * @return ari of the parsed reference */ int jumpTo(String reference) { if (reference.trim().length() == 0) { return 0; } Log.d(TAG, "going to jump to " + reference); Jumper jumper = new Jumper(reference); if (! jumper.getParseSucceeded()) { new MaterialDialog.Builder(this) .content(R.string.alamat_tidak_sah_alamat, reference) .positiveText(R.string.ok) .show(); return 0; } int bookId = jumper.getBookId(S.activeVersion.getConsecutiveBooks()); Book selected; if (bookId != -1) { Book book = S.activeVersion.getBook(bookId); if (book != null) { selected = book; } else { // not avail, just fallback selected = this.activeBook; } } else { selected = this.activeBook; } // set book this.activeBook = selected; int chapter = jumper.getChapter(); int verse = jumper.getVerse(); int ari_cv; if (chapter == -1 && verse == -1) { ari_cv = display(1, 1); } else { ari_cv = display(chapter, verse); } return Ari.encode(selected.bookId, ari_cv); } /** * Jump to a given ari */ void jumpToAri(final int ari) { if (ari == 0) return; final int bookId = Ari.toBook(ari); final Book book = S.activeVersion.getBook(bookId); if (book == null) { Log.w(TAG, "bookId=" + bookId + " not found for ari=" + ari); return; } this.activeBook = book; final int ari_cv = display(Ari.toChapter(ari), Ari.toVerse(ari)); // call attention to the verse only if the displayed verse is equal to the requested verse if (ari == Ari.encode(this.activeBook.bookId, ari_cv)) { callAttentionForVerseToBothSplits(Ari.toVerse(ari)); } } private CharSequence referenceFromSelectedVerses(IntArrayList selectedVerses, Book book) { if (selectedVerses.size() == 0) { // should not be possible. So we don't do anything. return book.reference(this.chapter_1); } else if (selectedVerses.size() == 1) { return book.reference(this.chapter_1, selectedVerses.get(0)); } else { return book.reference(this.chapter_1, selectedVerses); } } /** * Construct text for copying or sharing (in plain text). * @param isSplitVersion whether take the verse text from the main or from the split version. * @return [0] text for copy/share, [1] text to be submitted to the share url service */ String[] prepareTextForCopyShare(IntArrayList selectedVerses_1, CharSequence reference, boolean isSplitVersion) { final StringBuilder res0 = new StringBuilder(); final StringBuilder res1 = new StringBuilder(); res0.append(reference); if (Preferences.getBoolean(getString(R.string.pref_copyWithVersionName_key), getResources().getBoolean(R.bool.pref_copyWithVersionName_default))) { final Version version = isSplitVersion ? activeSplitVersion : S.activeVersion; final String versionShortName = version.getShortName(); if (versionShortName != null) { res0.append(" (").append(versionShortName).append(")"); } } if (Preferences.getBoolean(getString(R.string.pref_copyWithVerseNumbers_key), false) && selectedVerses_1.size() > 1) { res0.append('\n'); // append each selected verse with verse number prepended for (int i = 0, len = selectedVerses_1.size(); i < len; i++) { final int verse_1 = selectedVerses_1.get(i); final String verseText = isSplitVersion ? lsSplit1.getVerseText(verse_1) : lsSplit0.getVerseText(verse_1); if (verseText != null) { final String verseTextPlain = U.removeSpecialCodes(verseText); res0.append(verse_1); res1.append(verse_1); res0.append(' '); res1.append(' '); res0.append(verseTextPlain); res1.append(verseText); if (i != len - 1) { res0.append('\n'); res1.append('\n'); } } } } else { res0.append(" "); // append each selected verse without verse number prepended for (int i = 0; i < selectedVerses_1.size(); i++) { final int verse_1 = selectedVerses_1.get(i); final String verseText = isSplitVersion ? lsSplit1.getVerseText(verse_1) : lsSplit0.getVerseText(verse_1); if (verseText != null) { final String verseTextPlain = U.removeSpecialCodes(verseText); if (i != 0) { res0.append('\n'); res1.append('\n'); } res0.append(verseTextPlain); res1.append(verseText); } } } return Array(res0.toString(), res1.toString()); } private void applyPreferences() { // make sure S applied variables are set first S.calculateAppliedValuesBasedOnPreferences(); { // apply background color, and clear window background to prevent overdraw getWindow().setBackgroundDrawableResource(android.R.color.transparent); final int backgroundColor = S.applied.backgroundColor; root.setBackgroundColor(backgroundColor); lsSplit0.setCacheColorHint(backgroundColor); lsSplit1.setCacheColorHint(backgroundColor); // ensure scrollbar is visible on Material devices if (Build.VERSION.SDK_INT >= 21) { final Drawable thumb; if (ColorUtils.calculateLuminance(backgroundColor) > 0.5) { thumb = getResources().getDrawable(R.drawable.scrollbar_handle_material_for_light, null); } else { thumb = getResources().getDrawable(R.drawable.scrollbar_handle_material_for_dark, null); } ScrollbarSetter.setVerticalThumb(lsSplit0, thumb); ScrollbarSetter.setVerticalThumb(lsSplit1, thumb); } } // necessary lsSplit0.invalidateViews(); lsSplit1.invalidateViews(); SettingsActivity.setPaddingBasedOnPreferences(lsSplit0); SettingsActivity.setPaddingBasedOnPreferences(lsSplit1); } @Override protected void onStop() { super.onStop(); Preferences.hold(); try { Preferences.setInt(Prefkey.lastBookId, this.activeBook.bookId); Preferences.setInt(Prefkey.lastChapter, chapter_1); Preferences.setInt(Prefkey.lastVerse, lsSplit0.getVerseBasedOnScroll()); Preferences.setString(Prefkey.lastVersionId, S.activeVersionId); if (activeSplitVersion == null) { Preferences.remove(Prefkey.lastSplitVersionId); } else { Preferences.setString(Prefkey.lastSplitVersionId, activeSplitVersionId); Preferences.setString(Prefkey.lastSplitOrientation, splitHandleButton.getOrientation().name()); } } finally { Preferences.unhold(); } history.save(); } @Override protected void onStart() { super.onStart(); applyPreferences(); getWindow().getDecorView().setKeepScreenOn(Preferences.getBoolean(getString(R.string.pref_keepScreenOn_key), getResources().getBoolean(R.bool.pref_keepScreenOn_default))); if (needsRestart) { needsRestart = false; final Intent originalIntent = getIntent(); finish(); startActivity(originalIntent); } } @Override public void onBackPressed() { final boolean debug = Preferences.getBoolean("secret_debug_back_button", false); if (debug) Toast.makeText(this, "@@onBackPressed TAP=" + (textAppearancePanel != null) + " fullScreen=" + fullScreen, Toast.LENGTH_SHORT).show(); if (textAppearancePanel != null) { if (debug) Toast.makeText(this, "inside textAppearancePanel != null", Toast.LENGTH_SHORT).show(); textAppearancePanel.hide(); textAppearancePanel = null; } else if (fullScreen) { if (debug) Toast.makeText(this, "inside fullScreen == true", Toast.LENGTH_SHORT).show(); setFullScreen(false); leftDrawer.getHandle().setFullScreen(false); } else { if (debug) Toast.makeText(this, "will call super", Toast.LENGTH_SHORT).show(); super.onBackPressed(); } } void bGoto_click() { App.trackEvent("nav_goto_button_click"); final Runnable r = () -> startActivityForResult(GotoActivity.createIntent(this.activeBook.bookId, this.chapter_1, lsSplit0.getVerseBasedOnScroll()), REQCODE_goto); if (!Preferences.getBoolean(Prefkey.history_button_understood, false) && history.getSize() > 0) { new MaterialDialog.Builder(this) .content(R.string.goto_button_history_tip) .positiveText(R.string.ok) .onPositive((dialog, which) -> { Preferences.setBoolean(Prefkey.history_button_understood, true); r.run(); }) .show(); } else { r.run(); } } void bGoto_longClick() { App.trackEvent("nav_goto_button_long_click"); if (history.getSize() > 0) { MaterialDialogAdapterHelper.show(new MaterialDialog.Builder(this), new HistoryAdapter()); Preferences.setBoolean(Prefkey.history_button_understood, true); } else { Snackbar.make(root, R.string.recentverses_not_available, Snackbar.LENGTH_SHORT).show(); } } static class HistoryEntryHolder extends RecyclerView.ViewHolder { final TextView text1; public HistoryEntryHolder(final View itemView) { super(itemView); text1 = V.get(itemView, android.R.id.text1); } } class HistoryAdapter extends MaterialDialogAdapterHelper.Adapter { private final java.text.DateFormat timeFormat = DateFormat.getTimeFormat(App.context); private final java.text.DateFormat mediumDateFormat = DateFormat.getMediumDateFormat(App.context); final String thisCreatorId = U.getInstallationId(); int defaultTextColor; @Override public RecyclerView.ViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) { final View view = getLayoutInflater().inflate(android.R.layout.simple_list_item_1, parent, false); final TextView textView = (TextView) view; defaultTextColor = textView.getCurrentTextColor(); return new HistoryEntryHolder(view); } @Override public void onBindViewHolder(final RecyclerView.ViewHolder _holder_, final int position) { final HistoryEntryHolder holder = (HistoryEntryHolder) _holder_; { int ari = history.getAri(position); SpannableStringBuilder sb = new SpannableStringBuilder(); sb.append(S.activeVersion.reference(ari)); sb.append(" "); int sb_len = sb.length(); sb.append(formatTimestamp(history.getTimestamp(position))); sb.setSpan(new ForegroundColorSpan(0xffaaaaaa), sb_len, sb.length(), 0); sb.setSpan(new RelativeSizeSpan(0.7f), sb_len, sb.length(), 0); holder.text1.setText(sb); if (thisCreatorId.equals(history.getCreatorId(position))) { holder.text1.setTextColor(defaultTextColor); } else { holder.text1.setTextColor(ResourcesCompat.getColor(getResources(), R.color.escape, getTheme())); } } holder.itemView.setOnClickListener(v -> { dismissDialog(); final int which = holder.getAdapterPosition(); final int ari = history.getAri(which); jumpToAri(ari); history.add(ari); }); } private CharSequence formatTimestamp(final long timestamp) { { long now = System.currentTimeMillis(); long delta = now - timestamp; if (delta <= 200000) { return getString(R.string.recentverses_just_now); } else if (delta <= 3600000) { return getString(R.string.recentverses_min_plural_ago, Math.round(delta / 60000.0)); } } { Calendar now = GregorianCalendar.getInstance(); Calendar that = GregorianCalendar.getInstance(); that.setTimeInMillis(timestamp); if (now.get(Calendar.YEAR) == that.get(Calendar.YEAR)) { if (now.get(Calendar.DAY_OF_YEAR) == that.get(Calendar.DAY_OF_YEAR)) { return getString(R.string.recentverses_today_time, timeFormat.format(that.getTime())); } else if (now.get(Calendar.DAY_OF_YEAR) == that.get(Calendar.DAY_OF_YEAR) + 1) { return getString(R.string.recentverses_yesterday_time, timeFormat.format(that.getTime())); } } return mediumDateFormat.format(that.getTime()); } } @Override public int getItemCount() { return history.getSize(); } } public void buildMenu(Menu menu) { menu.clear(); getMenuInflater().inflate(R.menu.activity_isi, menu); } @Override public boolean onCreateOptionsMenu(Menu menu) { buildMenu(menu); return true; } @Override public boolean onPrepareOptionsMenu(Menu menu) { if (menu != null) { buildMenu(menu); } return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: leftDrawer.toggleDrawer(); return true; case R.id.menuSearch: App.trackEvent("nav_search_click"); menuSearch_click(); return true; } return super.onOptionsItemSelected(item); } @TargetApi(19) void setFullScreen(boolean yes) { if (fullScreen == yes) return; // no change final View decorView = getWindow().getDecorView(); if (yes) { getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); getSupportActionBar().hide(); if (Build.VERSION.SDK_INT >= 19) { decorView.setSystemUiVisibility( View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE ); } } else { getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); getSupportActionBar().show(); if (Build.VERSION.SDK_INT >= 19) { decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE); } } fullScreen = yes; updateToolbarLocation(); } void updateToolbarLocation() { // 3 kinds of possible layout: // - fullscreen // - not fullscreen, toolbar at bottom // - not fullscreen, toolbar at top // root contains exactly 2 children: toolbar and splitRoot. This is checked in DEBUG in onCreate. // Need to move toolbar and splitRoot in order to accomplish this. if (!fullScreen) { root.removeView(toolbar); root.removeView(splitRoot); if (Preferences.getBoolean(R.string.pref_bottomToolbarOnText_key, R.bool.pref_bottomToolbarOnText_default)) { root.addView(splitRoot); root.addView(toolbar); } else { root.addView(toolbar); root.addView(splitRoot); } } } @Override public void onWindowFocusChanged(final boolean hasFocus) { super.onWindowFocusChanged(hasFocus); if (hasFocus && fullScreen) { if (Build.VERSION.SDK_INT >= 19) { final View decorView = getWindow().getDecorView(); decorView.setSystemUiVisibility( View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE ); } } } void setShowTextAppearancePanel(boolean yes) { if (yes) { if (textAppearancePanel == null) { // not showing yet textAppearancePanel = new TextAppearancePanel(this, overlayContainer, new TextAppearancePanel.Listener() { @Override public void onValueChanged() { applyPreferences(); } @Override public void onCloseButtonClick() { textAppearancePanel.hide(); textAppearancePanel = null; } }, REQCODE_textAppearanceGetFonts, REQCODE_textAppearanceCustomColors); configureTextAppearancePanelForSplitVersion(); textAppearancePanel.show(); } } else { if (textAppearancePanel != null) { textAppearancePanel.hide(); textAppearancePanel = null; } } } void setNightMode(boolean yes) { final boolean previousValue = Preferences.getBoolean(Prefkey.is_night_mode, false); if (previousValue == yes) return; Preferences.setBoolean(Prefkey.is_night_mode, yes); applyPreferences(); applyActionBarAndStatusBarColors(); if (textAppearancePanel != null) { textAppearancePanel.displayValues(); } App.getLbm().sendBroadcast(new Intent(ACTION_NIGHT_MODE_CHANGED)); } void openVersionsDialog() { S.openVersionsDialog(this, false, S.activeVersionId, mv -> loadVersion(mv, true)); } void openSplitVersionsDialog() { S.openVersionsDialog(this, true, activeSplitVersionId, mv -> { if (mv == null) { // closing split version disableSplitVersion(); } else { boolean ok = loadSplitVersion(mv); if (ok) { openSplitDisplay(); displaySplitFollowingMaster(); } else { disableSplitVersion(); } } }); } void disableSplitVersion() { activeSplitVersion = null; activeSplitVersionId = null; closeSplitDisplay(); configureTextAppearancePanelForSplitVersion(); } void openSplitDisplay() { if (splitHandleButton.getVisibility() == View.VISIBLE) { return; // it's already split, no need to do anything } configureSplitSizes(); bVersion.setVisibility(View.GONE); if (actionMode != null) actionMode.invalidate(); leftDrawer.getHandle().setSplitVersion(true); } void configureSplitSizes() { splitHandleButton.setVisibility(View.VISIBLE); float prop = Preferences.getFloat(Prefkey.lastSplitProp, Float.MIN_VALUE); if (prop == Float.MIN_VALUE || prop < 0.f || prop > 1.f) { prop = 0.5f; // guard against invalid values } final int splitHandleThickness = getResources().getDimensionPixelSize(R.dimen.split_handle_thickness); if (splitHandleButton.getOrientation() == LabeledSplitHandleButton.Orientation.vertical) { splitRoot.setOrientation(LinearLayout.VERTICAL); final int totalHeight = splitRoot.getHeight(); final int masterHeight = (int) ((totalHeight - splitHandleThickness) * prop); { // divide the screen space final ViewGroup.LayoutParams lp = lsSplit0.getLayoutParams(); lp.width = ViewGroup.LayoutParams.MATCH_PARENT; lp.height = masterHeight; lsSplit0.setLayoutParams(lp); } // no need to set height, because it has been set to match_parent, so it takes the remaining space. lsSplit1.setVisibility(View.VISIBLE); { final ViewGroup.LayoutParams lp = splitHandleButton.getLayoutParams(); lp.width = ViewGroup.LayoutParams.MATCH_PARENT; lp.height = splitHandleThickness; splitHandleButton.setLayoutParams(lp); } } else { splitRoot.setOrientation(LinearLayout.HORIZONTAL); final int totalWidth = splitRoot.getWidth(); final int masterWidth = (int) ((totalWidth - splitHandleThickness) * prop); { // divide the screen space final ViewGroup.LayoutParams lp = lsSplit0.getLayoutParams(); lp.width = masterWidth; lp.height = ViewGroup.LayoutParams.MATCH_PARENT; lsSplit0.setLayoutParams(lp); } // no need to set width, because it has been set to match_parent, so it takes the remaining space. lsSplit1.setVisibility(View.VISIBLE); { final ViewGroup.LayoutParams lp = splitHandleButton.getLayoutParams(); lp.width = splitHandleThickness; lp.height = ViewGroup.LayoutParams.MATCH_PARENT; splitHandleButton.setLayoutParams(lp); } } } void closeSplitDisplay() { if (splitHandleButton.getVisibility() == View.GONE) { return; // it's already not split, no need to do anything } splitHandleButton.setVisibility(View.GONE); lsSplit1.setVisibility(View.GONE); { final ViewGroup.LayoutParams lp = lsSplit0.getLayoutParams(); lp.width = ViewGroup.LayoutParams.MATCH_PARENT; lp.height = ViewGroup.LayoutParams.MATCH_PARENT; lsSplit0.setLayoutParams(lp); } bVersion.setVisibility(View.VISIBLE); if (actionMode != null) actionMode.invalidate(); leftDrawer.getHandle().setSplitVersion(false); } private void menuSearch_click() { startActivity(SearchActivity.createIntent(this.activeBook.bookId)); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == REQCODE_goto && resultCode == RESULT_OK) { GotoActivity.Result result = GotoActivity.obtainResult(data); if (result != null) { final int ari_cv; if (result.bookId == -1) { // stay on the same book ari_cv = display(result.chapter_1, result.verse_1); // call attention to the verse only if the displayed verse is equal to the requested verse if (Ari.encode(0, result.chapter_1, result.verse_1) == ari_cv) { callAttentionForVerseToBothSplits(result.verse_1); } } else { // change book final Book book = S.activeVersion.getBook(result.bookId); if (book != null) { this.activeBook = book; } else { // no book, just chapter and verse. result.bookId = this.activeBook.bookId; } ari_cv = display(result.chapter_1, result.verse_1); // select the verse only if the displayed verse is equal to the requested verse if (Ari.encode(result.bookId, result.chapter_1, result.verse_1) == Ari.encode(this.activeBook.bookId, ari_cv)) { callAttentionForVerseToBothSplits(result.verse_1); } } if (result.verse_1 == 0 && Ari.toVerse(ari_cv) == 1) { // verse 0 requested, but display method causes it to show verse_1 1. // However we want to store verse_1 0 on the history. history.add(Ari.encode(this.activeBook.bookId, Ari.toChapter(ari_cv), 0)); } else { history.add(Ari.encode(this.activeBook.bookId, ari_cv)); } } } else if (requestCode == REQCODE_share && resultCode == RESULT_OK) { ShareActivity.Result result = ShareActivity.obtainResult(data); if (result != null && result.chosenIntent != null) { Intent chosenIntent = result.chosenIntent; final String packageName = chosenIntent.getComponent().getPackageName(); if (U.equals(packageName, "com.facebook.katana")) { String verseUrl = chosenIntent.getStringExtra(EXTRA_verseUrl); if (verseUrl != null) { chosenIntent.putExtra(Intent.EXTRA_TEXT, verseUrl); // change text to url } } else if (U.equals(packageName, "com.whatsapp")) { chosenIntent.removeExtra(Intent.EXTRA_SUBJECT); } startActivity(chosenIntent); } } else if (requestCode == REQCODE_textAppearanceGetFonts) { if (textAppearancePanel != null) textAppearancePanel.onActivityResult(requestCode, resultCode, data); } else if (requestCode == REQCODE_textAppearanceCustomColors) { if (textAppearancePanel != null) textAppearancePanel.onActivityResult(requestCode, resultCode, data); } else if (requestCode == REQCODE_edit_note_1 && resultCode == RESULT_OK) { reloadBothAttributeMaps(); } else if (requestCode == REQCODE_edit_note_2 && resultCode == RESULT_OK) { lsSplit0.uncheckAllVerses(true); reloadBothAttributeMaps(); } super.onActivityResult(requestCode, resultCode, data); } /** * Display specified chapter and verse of the active book. By default all checked verses will be unchecked. * @return Ari that contains only chapter and verse. Book always set to 0. */ int display(int chapter_1, int verse_1) { return display(chapter_1, verse_1, true); } /** * Display specified chapter and verse of the active book. * @param uncheckAllVerses whether we want to always make all verses unchecked after this operation. * @return Ari that contains only chapter and verse. Book always set to 0. */ int display(int chapter_1, int verse_1, boolean uncheckAllVerses) { int current_chapter_1 = this.chapter_1; if (chapter_1 < 1) chapter_1 = 1; if (chapter_1 > this.activeBook.chapter_count) chapter_1 = this.activeBook.chapter_count; if (verse_1 < 1) verse_1 = 1; if (verse_1 > this.activeBook.verse_counts[chapter_1 - 1]) verse_1 = this.activeBook.verse_counts[chapter_1 - 1]; { // main this.uncheckVersesWhenActionModeDestroyed = false; try { boolean ok = loadChapterToVersesView(lsSplit0, S.activeVersion, S.activeVersionId, this.activeBook, chapter_1, current_chapter_1, uncheckAllVerses); if (!ok) return 0; } finally { this.uncheckVersesWhenActionModeDestroyed = true; } // tell activity this.chapter_1 = chapter_1; lsSplit0.scrollToVerse(verse_1); } displaySplitFollowingMaster(verse_1); // set goto button text final String reference = this.activeBook.reference(chapter_1); bGoto.setText(reference.replace(' ', '\u00a0')); if (fullScreen) { if (fullScreenToast == null) { fullScreenToast = Toast.makeText(this, reference, Toast.LENGTH_SHORT); fullScreenToast.setGravity(Gravity.CENTER_HORIZONTAL | Gravity.TOP, 0, 0); } else { fullScreenToast.setText(reference); } fullScreenToast.show(); } if (dictionaryMode) { finishDictionaryMode(); } return Ari.encode(0, chapter_1, verse_1); } void displaySplitFollowingMaster() { displaySplitFollowingMaster(lsSplit0.getVerseBasedOnScroll()); } private void displaySplitFollowingMaster(int verse_1) { if (activeSplitVersion != null) { // split1 final Book splitBook = activeSplitVersion.getBook(this.activeBook.bookId); if (splitBook == null) { tSplitEmpty.setText(getString(R.string.split_version_cant_display_verse, this.activeBook.reference(this.chapter_1), activeSplitVersion.getLongName())); tSplitEmpty.setTextColor(S.applied.fontColor); lsSplit1.setDataEmpty(); } else { this.uncheckVersesWhenActionModeDestroyed = false; try { loadChapterToVersesView(lsSplit1, activeSplitVersion, activeSplitVersionId, splitBook, this.chapter_1, this.chapter_1, true); } finally { this.uncheckVersesWhenActionModeDestroyed = true; } lsSplit1.scrollToVerse(verse_1); } } } static boolean loadChapterToVersesView(VersesView versesView, Version version, String versionId, Book book, int chapter_1, int current_chapter_1, boolean uncheckAllVerses) { final SingleChapterVerses verses = version.loadChapterText(book, chapter_1); if (verses == null) { return false; } //# max is set to 30 (one chapter has max of 30 blocks. Already almost impossible) int max = 30; int[] pericope_aris = new int[max]; PericopeBlock[] pericope_blocks = new PericopeBlock[max]; int nblock = version.loadPericope(book.bookId, chapter_1, pericope_aris, pericope_blocks, max); boolean retainSelectedVerses = (!uncheckAllVerses && chapter_1 == current_chapter_1); versesView.setDataWithRetainSelectedVerses(retainSelectedVerses, Ari.encode(book.bookId, chapter_1, 0), pericope_aris, pericope_blocks, nblock, verses, version, versionId); return true; } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (press(keyCode)) return true; return super.onKeyDown(keyCode, event); } @Override public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { if (press(keyCode)) return true; return super.onKeyMultiple(keyCode, repeatCount, event); } @Override public boolean onKeyUp(int keyCode, KeyEvent event) { final String volumeButtonsForNavigation = Preferences.getString(R.string.pref_volumeButtonNavigation_key, R.string.pref_volumeButtonNavigation_default); if (! U.equals(volumeButtonsForNavigation, "default")) { // consume here if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) return true; if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) return true; } return super.onKeyUp(keyCode, event); } @Override protected LeftDrawer getLeftDrawer() { return leftDrawer; } void bLeft_click() { App.trackEvent("nav_left_click"); final Book currentBook = this.activeBook; if (chapter_1 == 1) { // we are in the beginning of the book, so go to prev book int tryBookId = currentBook.bookId - 1; while (tryBookId >= 0) { Book newBook = S.activeVersion.getBook(tryBookId); if (newBook != null) { this.activeBook = newBook; int newChapter_1 = newBook.chapter_count; // to the last chapter display(newChapter_1, 1); break; } tryBookId--; } // whileelse: now is already Genesis 1. No need to do anything } else { int newChapter = chapter_1 - 1; display(newChapter, 1); } } void bRight_click() { App.trackEvent("nav_right_click"); final Book currentBook = this.activeBook; if (chapter_1 >= currentBook.chapter_count) { final int maxBookId = S.activeVersion.getMaxBookIdPlusOne(); int tryBookId = currentBook.bookId + 1; while (tryBookId < maxBookId) { final Book newBook = S.activeVersion.getBook(tryBookId); if (newBook != null) { this.activeBook = newBook; display(1, 1); break; } tryBookId++; } // whileelse: now is already Revelation (or the last book) at the last chapter. No need to do anything } else { int newChapter = chapter_1 + 1; display(newChapter, 1); } } void bVersion_click() { App.trackEvent("nav_version_click"); openVersionsDialog(); } @Override public boolean onSearchRequested() { menuSearch_click(); return true; } @Override public void onVerseSelected(XrefDialog dialog, int arif_source, int ari_target) { final int ari_source = arif_source >>> 8; dialog.dismiss(); jumpToAri(ari_target); // add both xref source and target, so user can go back to source easily history.add(ari_source); history.add(ari_target); } class AttributeListener implements VersesView.AttributeListener { void openBookmarkDialog(final long _id) { final TypeBookmarkDialog dialog = TypeBookmarkDialog.EditExisting(IsiActivity.this, _id); dialog.setListener(() -> { lsSplit0.reloadAttributeMap(); if (activeSplitVersion != null) { lsSplit1.reloadAttributeMap(); } }); dialog.show(); } @Override public void onBookmarkAttributeClick(final Version version, final String versionId, final int ari) { final List<Marker> markers = S.getDb().listMarkersForAriKind(ari, Marker.Kind.bookmark); if (markers.size() == 1) { openBookmarkDialog(markers.get(0)._id); } else { MaterialDialogAdapterHelper.show(new MaterialDialog.Builder(IsiActivity.this).title(R.string.edit_bookmark), new MultipleMarkerSelectAdapter(version, versionId, markers, Marker.Kind.bookmark)); } } void openNoteDialog(final long _id) { startActivityForResult(NoteActivity.createEditExistingIntent(_id), REQCODE_edit_note_1); } @Override public void onNoteAttributeClick(final Version version, final String versionId, final int ari) { final List<Marker> markers = S.getDb().listMarkersForAriKind(ari, Marker.Kind.note); if (markers.size() == 1) { openNoteDialog(markers.get(0)._id); } else { MaterialDialogAdapterHelper.show(new MaterialDialog.Builder(IsiActivity.this).title(R.string.edit_note), new MultipleMarkerSelectAdapter(version, versionId, markers, Marker.Kind.note)); } } class MarkerHolder extends RecyclerView.ViewHolder { final TextView lDate; final TextView lCaption; final TextView lSnippet; final FlowLayout panelLabels; public MarkerHolder(final View itemView) { super(itemView); lDate = V.get(itemView, R.id.lDate); lCaption = V.get(itemView, R.id.lCaption); lSnippet = V.get(itemView, R.id.lSnippet); panelLabels = V.get(itemView, R.id.panelLabels); } } class MultipleMarkerSelectAdapter extends MaterialDialogAdapterHelper.Adapter { final Version version; final float textSizeMult; final List<Marker> markers; final Marker.Kind kind; public MultipleMarkerSelectAdapter(final Version version, final String versionId, final List<Marker> markers, final Marker.Kind kind) { this.version = version; this.textSizeMult = S.getDb().getPerVersionSettings(versionId).fontSizeMultiplier; this.markers = markers; this.kind = kind; } @Override public RecyclerView.ViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) { return new MarkerHolder(getLayoutInflater().inflate(R.layout.item_marker, parent, false)); } @Override public void onBindViewHolder(final RecyclerView.ViewHolder _holder_, final int position) { final MarkerHolder holder = (MarkerHolder) _holder_; { final Marker marker = markers.get(position); { final Date addTime = marker.createTime; final Date modifyTime = marker.modifyTime; if (addTime.equals(modifyTime)) { holder.lDate.setText(Sqlitil.toLocaleDateMedium(addTime)); } else { holder.lDate.setText(getString(R.string.create_edited_modified_time, Sqlitil.toLocaleDateMedium(addTime), Sqlitil.toLocaleDateMedium(modifyTime))); } Appearances.applyMarkerDateTextAppearance(holder.lDate, textSizeMult); } final int ari = marker.ari; final String reference = version.reference(ari); final String caption = marker.caption; if (kind == Marker.Kind.bookmark) { holder.lCaption.setText(caption); Appearances.applyMarkerTitleTextAppearance(holder.lCaption, textSizeMult); holder.lSnippet.setVisibility(View.GONE); final List<Label> labels = S.getDb().listLabelsByMarker(marker); if (labels.size() != 0) { holder.panelLabels.setVisibility(View.VISIBLE); holder.panelLabels.removeAllViews(); for (Label label : labels) { holder.panelLabels.addView(MarkerListActivity.getLabelView(getLayoutInflater(), holder.panelLabels, label)); } } else { holder.panelLabels.setVisibility(View.GONE); } } else if (kind == Marker.Kind.note) { holder.lCaption.setText(reference); Appearances.applyMarkerTitleTextAppearance(holder.lCaption, textSizeMult); holder.lSnippet.setText(caption); Appearances.applyTextAppearance(holder.lSnippet, textSizeMult); } holder.itemView.setBackgroundColor(S.applied.backgroundColor); } holder.itemView.setOnClickListener(v -> { dismissDialog(); final int which = holder.getAdapterPosition(); final Marker marker = markers.get(which); if (kind == Marker.Kind.bookmark) { openBookmarkDialog(marker._id); } else if (kind == Marker.Kind.note) { openNoteDialog(marker._id); } }); } @Override public int getItemCount() { return markers.size(); } } @Override public void onProgressMarkAttributeClick(final Version version, final String versionId, final int preset_id) { final ProgressMark progressMark = S.getDb().getProgressMarkByPresetId(preset_id); ProgressMarkRenameDialog.show(IsiActivity.this, progressMark, new ProgressMarkRenameDialog.Listener() { @Override public void onOked() { lsSplit0.uncheckAllVerses(true); } @Override public void onDeleted() { lsSplit0.uncheckAllVerses(true); } }); } @Override public void onHasMapsAttributeClick(final Version version, final String versionId, final int ari) { String locale = null; if (this == lsSplit0.getAttributeListener()) { locale = S.activeVersion.getLocale(); } else if (this == lsSplit1.getAttributeListener()) { locale = activeSplitVersion.getLocale(); } try { final Intent intent = new Intent("palki.maps.action.SHOW_MAPS_DIALOG"); intent.putExtra("ari", ari); if (locale != null) { intent.putExtra("locale", locale); } startActivity(intent); } catch (ActivityNotFoundException e) { new MaterialDialog.Builder(IsiActivity.this) .content(R.string.maps_could_not_open) .positiveText(R.string.ok) .show(); } } } class VerseInlineLinkSpanFactory implements VerseInlineLinkSpan.Factory { private final Object source; VerseInlineLinkSpanFactory(final Object source) { this.source = source; } @Override public VerseInlineLinkSpan create(final VerseInlineLinkSpan.Type type, final int arif) { return new VerseInlineLinkSpan(type, arif, source) { @Override public void onClick(final Type type, final int arif, final Object source) { if (type == Type.xref) { final XrefDialog dialog = XrefDialog.newInstance(arif); // TODO setSourceVersion here is not restored when dialog is restored if (source == lsSplit0) { // use activeVersion dialog.setSourceVersion(S.activeVersion, S.activeVersionId); } else if (source == lsSplit1) { // use activeSplitVersion dialog.setSourceVersion(activeSplitVersion, activeSplitVersionId); } FragmentManager fm = getSupportFragmentManager(); dialog.show(fm, XrefDialog.class.getSimpleName()); } else if (type == Type.footnote) { FootnoteEntry fe = null; if (source == lsSplit0) { // use activeVersion fe = S.activeVersion.getFootnoteEntry(arif); } else if (source == lsSplit1) { // use activeSplitVersion fe = activeSplitVersion.getFootnoteEntry(arif); } if (fe != null) { final SpannableStringBuilder footnoteText = new SpannableStringBuilder(); VerseRenderer.appendSuperscriptNumber(footnoteText, arif & 0xff); footnoteText.append(" "); new MaterialDialog.Builder(IsiActivity.this) .content(FormattedTextRenderer.render(fe.content, footnoteText)) .positiveText(R.string.ok) .show(); } else { new MaterialDialog.Builder(IsiActivity.this) .content(String.format(Locale.US, "Error: footnote arif 0x%08x couldn't be loaded", arif)) .positiveText(R.string.ok) .show(); } } else { new MaterialDialog.Builder(IsiActivity.this) .content("Error: Unknown inline link type: " + type) .positiveText("OK") .show(); } } }; } } VersesView.SelectedVersesListener lsSplit0_selectedVerses = new VersesView.DefaultSelectedVersesListener() { @Override public void onSomeVersesSelected(VersesView v) { if (activeSplitVersion != null) { // synchronize the selection with the split view IntArrayList selectedVerses = v.getSelectedVerses_1(); lsSplit1.checkVerses(selectedVerses, false); } if (actionMode == null) { actionMode = startSupportActionMode(actionMode_callback); } if (actionMode != null) { actionMode.invalidate(); } } @Override public void onNoVersesSelected(VersesView v) { if (activeSplitVersion != null) { // synchronize the selection with the split view lsSplit1.uncheckAllVerses(false); } if (actionMode != null) { actionMode.finish(); actionMode = null; } } }; VersesView.SelectedVersesListener lsSplit1_selectedVerses = new VersesView.DefaultSelectedVersesListener() { @Override public void onSomeVersesSelected(VersesView v) { // synchronize the selection with the main view IntArrayList selectedVerses = v.getSelectedVerses_1(); lsSplit0.checkVerses(selectedVerses, true); } @Override public void onNoVersesSelected(VersesView v) { lsSplit0.uncheckAllVerses(true); } }; VersesView.OnVerseScrollListener lsSplit0_verseScroll = new VersesView.OnVerseScrollListener() { @Override public void onVerseScroll(VersesView v, boolean isPericope, int verse_1, float prop) { if (!isPericope && activeSplitVersion != null) { lsSplit1.scrollToVerse(verse_1, prop); } } @Override public void onScrollToTop(VersesView v) { if (activeSplitVersion != null) { lsSplit1.scrollToTop(); } } }; VersesView.OnVerseScrollListener lsSplit1_verseScroll = new VersesView.OnVerseScrollListener() { @Override public void onVerseScroll(VersesView v, boolean isPericope, int verse_1, float prop) { if (!isPericope) { lsSplit0.scrollToVerse(verse_1, prop); } } @Override public void onScrollToTop(VersesView v) { lsSplit0.scrollToTop(); } }; ActionMode.Callback actionMode_callback = new ActionMode.Callback() { private static final int MENU_GROUP_EXTENSIONS = Menu.FIRST + 1; private static final int MENU_EXTENSIONS_FIRST_ID = 0x1000; List<ExtensionManager.Info> extensions; @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { getMenuInflater().inflate(R.menu.context_isi, menu); Log.d(TAG, "@@onCreateActionMode"); /* The following "esvsbasal" thing is a personal thing by yuku that doesn't matter to anyone else. * Please ignore it and leave it intact. */ if (hasEsvsbAsal == null) { try { getPackageManager().getApplicationInfo("yuku.esvsbasal", 0); hasEsvsbAsal = true; } catch (PackageManager.NameNotFoundException e) { hasEsvsbAsal = false; } } if (hasEsvsbAsal) { MenuItem esvsb = menu.findItem(R.id.menuEsvsb); if (esvsb != null) esvsb.setVisible(true); } // show book name and chapter final String reference = activeBook.reference(chapter_1); mode.setTitle(reference); return true; } @Override public boolean onPrepareActionMode(ActionMode mode, Menu menu) { final MenuItem menuAddBookmark = menu.findItem(R.id.menuAddBookmark); final MenuItem menuAddNote = menu.findItem(R.id.menuAddNote); final MenuItem menuCompare = menu.findItem(R.id.menuCompare); final IntArrayList selected = lsSplit0.getSelectedVerses_1(); final boolean single = selected.size() == 1; boolean contiguous = true; if (!single) { int next = selected.get(0) + 1; for (int i = 1, len = selected.size(); i < len; i++) { final int cur = selected.get(i); if (next != cur) { contiguous = false; break; } next = cur + 1; } } menuAddBookmark.setVisible(contiguous); menuAddNote.setVisible(contiguous); menuCompare.setVisible(single); // just "copy" or ("copy primary" "copy secondary" "copy both") // same with "share". final MenuItem menuCopy = menu.findItem(R.id.menuCopy); final MenuItem menuCopySplit0 = menu.findItem(R.id.menuCopySplit0); final MenuItem menuCopySplit1 = menu.findItem(R.id.menuCopySplit1); final MenuItem menuCopyBothSplits = menu.findItem(R.id.menuCopyBothSplits); final MenuItem menuShare = menu.findItem(R.id.menuShare); final MenuItem menuShareSplit0 = menu.findItem(R.id.menuShareSplit0); final MenuItem menuShareSplit1 = menu.findItem(R.id.menuShareSplit1); final MenuItem menuShareBothSplits = menu.findItem(R.id.menuShareBothSplits); final boolean split = activeSplitVersion != null; menuCopy.setVisible(!split); menuCopySplit0.setVisible(split); menuCopySplit1.setVisible(split); menuCopyBothSplits.setVisible(split); menuShare.setVisible(!split); menuShareSplit0.setVisible(split); menuShareSplit1.setVisible(split); menuShareBothSplits.setVisible(split); // show selected verses if (single) { mode.setSubtitle(R.string.verse_select_one_verse_selected); } else { mode.setSubtitle(getString(R.string.verse_select_multiple_verse_selected, selected.size())); } final MenuItem menuGuide = menu.findItem(R.id.menuGuide); final MenuItem menuCommentary = menu.findItem(R.id.menuCommentary); final MenuItem menuDictionary = menu.findItem(R.id.menuDictionary); // force-show these items on sw600dp, otherwise never show final int showAsAction = getResources().getConfiguration().screenWidthDp >= 600 ? MenuItem.SHOW_AS_ACTION_ALWAYS : MenuItem.SHOW_AS_ACTION_NEVER; menuGuide.setShowAsActionFlags(showAsAction); menuCommentary.setShowAsActionFlags(showAsAction); menuDictionary.setShowAsActionFlags(showAsAction); // set visibility according to appconfig final AppConfig c = AppConfig.get(); menuGuide.setVisible(c.menuGuide); menuCommentary.setVisible(c.menuCommentary); // do not show dictionary item if not needed because of auto-lookup from menuDictionary.setVisible(c.menuDictionary && !Preferences.getBoolean(getString(R.string.pref_autoDictionaryAnalyze_key), getResources().getBoolean(R.bool.pref_autoDictionaryAnalyze_default)) ); { // extensions extensions = ExtensionManager.getExtensions(); menu.removeGroup(MENU_GROUP_EXTENSIONS); for (int i = 0; i < extensions.size(); i++) { final ExtensionManager.Info extension = extensions.get(i); if (single || (/* not single */ extension.supportsMultipleVerses)) { menu.add(MENU_GROUP_EXTENSIONS, MENU_EXTENSIONS_FIRST_ID + i, 0, extension.label); } } } return true; } @Override public boolean onActionItemClicked(ActionMode mode, MenuItem item) { final IntArrayList selected = lsSplit0.getSelectedVerses_1(); if (selected.size() == 0) return true; final CharSequence reference = referenceFromSelectedVerses(selected, activeBook); final int itemId = item.getItemId(); switch (itemId) { case R.id.menuCopy: case R.id.menuCopySplit0: case R.id.menuCopySplit1: case R.id.menuCopyBothSplits: { // copy, can be multiple verses final String[] t; if (itemId == R.id.menuCopy || itemId == R.id.menuCopySplit0 || itemId == R.id.menuCopyBothSplits) { t = prepareTextForCopyShare(selected, reference, false); } else { // menuCopySplit1 t = prepareTextForCopyShare(selected, reference, true); } if (itemId == R.id.menuCopyBothSplits && activeSplitVersion != null) { // put guard on activeSplitVersion appendSplitTextForCopyShare(t); } final String textToCopy = t[0]; final String textToSubmit = t[1]; ShareUrl.make(IsiActivity.this, !Preferences.getBoolean(getString(R.string.pref_copyWithShareUrl_key), getResources().getBoolean(R.bool.pref_copyWithShareUrl_default)), textToSubmit, Ari.encode(activeBook.bookId, chapter_1, 0), selected, reference.toString(), S.activeVersion, MVersionDb.presetNameFromVersionId(S.activeVersionId), new ShareUrl.Callback() { @Override public void onSuccess(final String shareUrl) { U.copyToClipboard(textToCopy + "\n\n" + shareUrl); } @Override public void onUserCancel() { U.copyToClipboard(textToCopy); } @Override public void onError(final Exception e) { U.copyToClipboard(textToCopy); } @Override public void onFinally() { lsSplit0.uncheckAllVerses(true); Snackbar.make(root, getString(R.string.alamat_sudah_disalin, reference), Snackbar.LENGTH_SHORT).show(); mode.finish(); } }); } return true; case R.id.menuShare: case R.id.menuShareSplit0: case R.id.menuShareSplit1: case R.id.menuShareBothSplits: { // share, can be multiple verses final String[] t; if (itemId == R.id.menuShare || itemId == R.id.menuShareSplit0 || itemId == R.id.menuShareBothSplits) { t = prepareTextForCopyShare(selected, reference, false); } else { // menuShareSplit1 t = prepareTextForCopyShare(selected, reference, true); } if (itemId == R.id.menuShareBothSplits && activeSplitVersion != null) { // put guard on activeSplitVersion appendSplitTextForCopyShare(t); } final String textToShare = t[0]; final String textToSubmit = t[1]; final Intent intent = ShareCompat.IntentBuilder.from(IsiActivity.this) .setType("text/plain") .setSubject(reference.toString()) .getIntent(); ShareUrl.make(IsiActivity.this, !Preferences.getBoolean(getString(R.string.pref_copyWithShareUrl_key), getResources().getBoolean(R.bool.pref_copyWithShareUrl_default)), textToSubmit, Ari.encode(activeBook.bookId, chapter_1, 0), selected, reference.toString(), S.activeVersion, MVersionDb.presetNameFromVersionId(S.activeVersionId), new ShareUrl.Callback() { @Override public void onSuccess(final String shareUrl) { intent.putExtra(Intent.EXTRA_TEXT, textToShare + "\n\n" + shareUrl); intent.putExtra(EXTRA_verseUrl, shareUrl); } @Override public void onUserCancel() { intent.putExtra(Intent.EXTRA_TEXT, textToShare); } @Override public void onError(final Exception e) { intent.putExtra(Intent.EXTRA_TEXT, textToShare); } @Override public void onFinally() { startActivityForResult(ShareActivity.createIntent(intent, getString(R.string.bagikan_alamat, reference)), REQCODE_share); lsSplit0.uncheckAllVerses(true); mode.finish(); } }); } return true; case R.id.menuCompare: { final int ari = Ari.encode(IsiActivity.this.activeBook.bookId, IsiActivity.this.chapter_1, selected.get(0)); final VersesDialog dialog = VersesDialog.newCompareInstance(ari); dialog.show(getSupportFragmentManager(), "compare_dialog"); dialog.setListener(new VersesDialog.VersesDialogListener() { @Override public void onComparedVerseSelected(final VersesDialog dialog, final int ari, final MVersion mversion) { loadVersion(mversion, true); dialog.dismiss(); } }); } return true; case R.id.menuAddBookmark: { // contract: this menu only appears when contiguous verses are selected if (selected.get(selected.size() - 1) - selected.get(0) != selected.size() - 1) { throw new RuntimeException("Non contiguous verses when adding bookmark: " + selected); } final int ari = Ari.encode(IsiActivity.this.activeBook.bookId, IsiActivity.this.chapter_1, selected.get(0)); final int verseCount = selected.size(); // always create a new bookmark TypeBookmarkDialog dialog = TypeBookmarkDialog.NewBookmark(IsiActivity.this, ari, verseCount); dialog.setListener(() -> { lsSplit0.uncheckAllVerses(true); reloadBothAttributeMaps(); }); dialog.show(); mode.finish(); } return true; case R.id.menuAddNote: { // contract: this menu only appears when contiguous verses are selected if (selected.get(selected.size() - 1) - selected.get(0) != selected.size() - 1) { throw new RuntimeException("Non contiguous verses when adding note: " + selected); } final int ari = Ari.encode(IsiActivity.this.activeBook.bookId, IsiActivity.this.chapter_1, selected.get(0)); final int verseCount = selected.size(); // always create a new note startActivityForResult(NoteActivity.createNewNoteIntent(S.activeVersion.referenceWithVerseCount(ari, verseCount), ari, verseCount), REQCODE_edit_note_2); mode.finish(); } return true; case R.id.menuAddHighlight: { final int ariBc = Ari.encode(IsiActivity.this.activeBook.bookId, IsiActivity.this.chapter_1, 0); int colorRgb = S.getDb().getHighlightColorRgb(ariBc, selected); final TypeHighlightDialog.Listener listener = colorRgb1 -> { lsSplit0.uncheckAllVerses(true); reloadBothAttributeMaps(); }; if (selected.size() == 1) { final VerseRenderer.FormattedTextResult ftr = new VerseRenderer.FormattedTextResult(); final int ari = Ari.encodeWithBc(ariBc, selected.get(0)); final String rawVerseText = S.activeVersion.loadVerseText(ari); final Highlights.Info info = S.getDb().getHighlightColorRgb(ari); assert rawVerseText != null; VerseRenderer.render(null, null, ari, rawVerseText, "" + Ari.toVerse(ari), null, false, null, ftr); new TypeHighlightDialog(IsiActivity.this, ari, listener, colorRgb, info, reference, ftr.result); } else { new TypeHighlightDialog(IsiActivity.this, ariBc, selected, listener, colorRgb, reference); } mode.finish(); } return true; case R.id.menuEsvsb: { final int ari = Ari.encode(IsiActivity.this.activeBook.bookId, IsiActivity.this.chapter_1, selected.get(0)); try { Intent intent = new Intent("yuku.esvsbasal.action.GOTO"); intent.putExtra("ari", ari); startActivity(intent); } catch (Exception e) { Log.e(TAG, "ESVSB starting", e); } } return true; case R.id.menuGuide: { final int ari = Ari.encode(IsiActivity.this.activeBook.bookId, IsiActivity.this.chapter_1, 0); try { getPackageManager().getPackageInfo("org.sabda.pedia", 0); final Intent intent = new Intent("org.sabda.pedia.action.VIEW"); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.putExtra("ari", ari); startActivity(intent); } catch (PackageManager.NameNotFoundException e) { OtherAppIntegration.openMarket(IsiActivity.this, "org.sabda.pedia"); } } return true; case R.id.menuCommentary: { final int ari = Ari.encode(IsiActivity.this.activeBook.bookId, IsiActivity.this.chapter_1, selected.get(0)); try { getPackageManager().getPackageInfo("org.sabda.tafsiran", 0); final Intent intent = new Intent("org.sabda.tafsiran.action.VIEW"); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.putExtra("ari", ari); startActivity(intent); } catch (PackageManager.NameNotFoundException e) { OtherAppIntegration.openMarket(IsiActivity.this, "org.sabda.tafsiran"); } } return true; case R.id.menuDictionary: { final int ariBc = Ari.encode(IsiActivity.this.activeBook.bookId, IsiActivity.this.chapter_1, 0); final SparseBooleanArray aris = new SparseBooleanArray(); for (int i = 0, len = selected.size(); i < len; i++) { final int verse_1 = selected.get(i); final int ari = Ari.encodeWithBc(ariBc, verse_1); aris.put(ari, true); } startDictionaryMode(aris); } return true; default: if (itemId >= MENU_EXTENSIONS_FIRST_ID && itemId < MENU_EXTENSIONS_FIRST_ID + extensions.size()) { final ExtensionManager.Info extension = extensions.get(itemId - MENU_EXTENSIONS_FIRST_ID); final Intent intent = new Intent(ExtensionManager.ACTION_SHOW_VERSE_INFO); intent.setComponent(new ComponentName(extension.activityInfo.packageName, extension.activityInfo.name)); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // prepare extra "aris" final int[] aris = new int[selected.size()]; final int ariBc = Ari.encode(IsiActivity.this.activeBook.bookId, IsiActivity.this.chapter_1, 0); for (int i = 0, len = selected.size(); i < len; i++) { final int verse_1 = selected.get(i); final int ari = Ari.encodeWithBc(ariBc, verse_1); aris[i] = ari; } intent.putExtra("aris", aris); if (extension.includeVerseText) { // prepare extra "verseTexts" final String[] verseTexts = new String[selected.size()]; for (int i = 0, len = selected.size(); i < len; i++) { final int verse_1 = selected.get(i); final String verseText = lsSplit0.getVerseText(verse_1); if (extension.includeVerseTextFormatting) { verseTexts[i] = verseText; } else { verseTexts[i] = U.removeSpecialCodes(verseText); } } intent.putExtra("verseTexts", verseTexts); } try { startActivity(intent); } catch (ActivityNotFoundException e) { new MaterialDialog.Builder(IsiActivity.this) .content("Error ANFE starting extension\n\n" + extension.activityInfo.packageName + "/" + extension.activityInfo.name) .positiveText(R.string.ok) .show(); } return true; } } return false; } /** * @param t [0] is text to copy, [1] is text to submit */ void appendSplitTextForCopyShare(final String[] t) { final Book splitBook = activeSplitVersion.getBook(activeBook.bookId); if (splitBook != null) { IntArrayList selectedSplit = lsSplit1.getSelectedVerses_1(); CharSequence referenceSplit = referenceFromSelectedVerses(selectedSplit, splitBook); final String[] a = prepareTextForCopyShare(selectedSplit, referenceSplit, true); t[0] += "\n\n" + a[0]; t[1] += "\n\n" + a[1]; } } @Override public void onDestroyActionMode(ActionMode mode) { actionMode = null; // FIXME even with this guard, verses are still unchecked when switching version while both Fullscreen and Split is active. // This guard only fixes unchecking of verses when in fullscreen mode. if (uncheckVersesWhenActionModeDestroyed) { lsSplit0.uncheckAllVerses(true); } } }; void reloadBothAttributeMaps() { lsSplit0.reloadAttributeMap(); if (activeSplitVersion != null) { lsSplit1.reloadAttributeMap(); } } final SplitHandleButton.SplitHandleButtonListener splitHandleButton_listener = new SplitHandleButton.SplitHandleButtonListener() { int first; int handle; int root; float prop; // proportion from top or left @Override public void onHandleDragStart() { splitRoot.setOnefingerEnabled(false); if (splitHandleButton.getOrientation() == SplitHandleButton.Orientation.vertical) { first = lsSplit0.getHeight(); handle = splitHandleButton.getHeight(); root = splitRoot.getHeight(); } else { first = lsSplit0.getWidth(); handle = splitHandleButton.getWidth(); root = splitRoot.getWidth(); } prop = Float.MIN_VALUE; // guard against glitches } @Override public void onHandleDragMoveX(final float dxSinceLast, final float dxSinceStart) { final int newW = (int) (first + dxSinceStart); final int maxW = root - handle; final ViewGroup.LayoutParams lp = lsSplit0.getLayoutParams(); lp.width = newW < 0? 0: newW > maxW? maxW: newW; lp.height = ViewGroup.LayoutParams.MATCH_PARENT; lsSplit0.setLayoutParams(lp); prop = (float) lp.width / maxW; } @Override public void onHandleDragMoveY(float dySinceLast, float dySinceStart) { final int newH = (int) (first + dySinceStart); final int maxH = root - handle; final ViewGroup.LayoutParams lp = lsSplit0.getLayoutParams(); lp.width = ViewGroup.LayoutParams.MATCH_PARENT; lp.height = newH < 0? 0: newH > maxH? maxH: newH; lsSplit0.setLayoutParams(lp); prop = (float) lp.height / maxH; } @Override public void onHandleDragStop() { splitRoot.setOnefingerEnabled(true); if (prop != Float.MIN_VALUE) { Preferences.setFloat(Prefkey.lastSplitProp, prop); } } }; LabeledSplitHandleButton.ButtonPressListener splitHandleButton_labelPressed = which -> { switch (which) { case rotate: closeSplitDisplay(); openSplitDisplay(); break; case start: openVersionsDialog(); break; case end: openSplitVersionsDialog(); break; } }; void startDictionaryMode(final SparseBooleanArray aris) { if (!OtherAppIntegration.hasIntegratedDictionaryApp()) { OtherAppIntegration.askToInstallDictionary(this); return; } dictionaryMode = true; lsSplit0.setDictionaryModeAris(aris); lsSplit1.setDictionaryModeAris(aris); } void finishDictionaryMode() { dictionaryMode = false; lsSplit0.setDictionaryModeAris(null); lsSplit1.setDictionaryModeAris(null); } @Override public void bMarkers_click() { startActivity(MarkersActivity.createIntent()); } @Override public void bDisplay_click() { App.trackEvent("left_drawer_display_click"); setShowTextAppearancePanel(textAppearancePanel == null); } @Override public void cFullScreen_checkedChange(final boolean isChecked) { App.trackEvent("left_drawer_full_screen_click"); setFullScreen(isChecked); } @Override public void cNightMode_checkedChange(final boolean isChecked) { App.trackEvent("left_drawer_night_mode_click"); setNightMode(isChecked); } @Override public void cSplitVersion_checkedChange(final SwitchCompat cSplitVersion, final boolean isChecked) { App.trackEvent("left_drawer_split_click"); if (isChecked) { cSplitVersion.setChecked(false); // do it later, at the version chooser dialog openSplitVersionsDialog(); } else { disableSplitVersion(); } } @Override public void bProgressMarkList_click() { App.trackEvent("left_drawer_progress_mark_list_click"); if (S.getDb().countAllProgressMarks() > 0) { final ProgressMarkListDialog dialog = new ProgressMarkListDialog(); dialog.show(getSupportFragmentManager(), "dialog_progress_mark_list"); leftDrawer.closeDrawer(); } else { new MaterialDialog.Builder(this) .content(R.string.pm_activate_tutorial) .positiveText(R.string.ok) .show(); } } @Override public void bProgress_click(final int preset_id) { gotoProgressMark(preset_id); } @Override public void bCurrentReadingClose_click() { App.trackEvent("left_drawer_current_reading_close_click"); CurrentReading.clear(); } @Override public void bCurrentReadingReference_click() { App.trackEvent("left_drawer_current_reading_verse_reference_click"); final int[] aris = CurrentReading.get(); if (aris == null) { return; } final int ari_start = aris[0]; jumpToAri(ari_start); history.add(ari_start); leftDrawer.closeDrawer(); } private void gotoProgressMark(final int preset_id) { final ProgressMark progressMark = S.getDb().getProgressMarkByPresetId(preset_id); if (progressMark == null) { return; } final int ari = progressMark.ari; if (ari != 0) { App.trackEvent("left_drawer_progress_mark_pin_click_succeed"); jumpToAri(ari); history.add(ari); } else { App.trackEvent("left_drawer_progress_mark_pin_click_failed"); new MaterialDialog.Builder(this) .content(R.string.pm_activate_tutorial) .positiveText(R.string.ok) .show(); } } @Override public void onProgressMarkSelected(final int preset_id) { gotoProgressMark(preset_id); } }