package com.quran.labs.androidquran.ui;
import android.annotation.TargetApi;
import android.app.ProgressDialog;
import android.app.SearchManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v4.util.Pair;
import android.support.v4.view.MenuItemCompat;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.ViewPager;
import android.support.v4.view.ViewPager.OnPageChangeListener;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.SearchView;
import android.support.v7.widget.Toolbar;
import android.text.TextUtils;
import android.util.SparseBooleanArray;
import android.view.HapticFeedbackConstants;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.Toast;
import com.quran.labs.androidquran.HelpActivity;
import com.quran.labs.androidquran.QuranApplication;
import com.quran.labs.androidquran.QuranPreferenceActivity;
import com.quran.labs.androidquran.R;
import com.quran.labs.androidquran.SearchActivity;
import com.quran.labs.androidquran.common.LocalTranslation;
import com.quran.labs.androidquran.common.QariItem;
import com.quran.labs.androidquran.component.activity.PagerActivityComponent;
import com.quran.labs.androidquran.data.AyahInfoDatabaseProvider;
import com.quran.labs.androidquran.data.Constants;
import com.quran.labs.androidquran.data.QuranDataProvider;
import com.quran.labs.androidquran.data.QuranInfo;
import com.quran.labs.androidquran.data.SuraAyah;
import com.quran.labs.androidquran.database.TranslationsDBAdapter;
import com.quran.labs.androidquran.model.bookmark.BookmarkModel;
import com.quran.labs.androidquran.model.translation.ArabicDatabaseUtils;
import com.quran.labs.androidquran.module.activity.PagerActivityModule;
import com.quran.labs.androidquran.presenter.bookmark.RecentPagePresenter;
import com.quran.labs.androidquran.service.AudioService;
import com.quran.labs.androidquran.service.QuranDownloadService;
import com.quran.labs.androidquran.service.util.AudioRequest;
import com.quran.labs.androidquran.service.util.DefaultDownloadReceiver;
import com.quran.labs.androidquran.service.util.DownloadAudioRequest;
import com.quran.labs.androidquran.service.util.QuranDownloadNotifier;
import com.quran.labs.androidquran.service.util.ServiceIntentHelper;
import com.quran.labs.androidquran.service.util.StreamingAudioRequest;
import com.quran.labs.androidquran.ui.fragment.AddTagDialog;
import com.quran.labs.androidquran.ui.fragment.AyahActionFragment;
import com.quran.labs.androidquran.ui.fragment.JumpFragment;
import com.quran.labs.androidquran.ui.fragment.TabletFragment;
import com.quran.labs.androidquran.ui.fragment.TagBookmarkDialog;
import com.quran.labs.androidquran.ui.fragment.TranslationFragment;
import com.quran.labs.androidquran.ui.helpers.AyahSelectedListener;
import com.quran.labs.androidquran.ui.helpers.AyahTracker;
import com.quran.labs.androidquran.ui.helpers.HighlightType;
import com.quran.labs.androidquran.ui.helpers.QuranDisplayHelper;
import com.quran.labs.androidquran.ui.helpers.QuranPage;
import com.quran.labs.androidquran.ui.helpers.QuranPageAdapter;
import com.quran.labs.androidquran.ui.helpers.QuranPageWorker;
import com.quran.labs.androidquran.ui.helpers.SlidingPagerAdapter;
import com.quran.labs.androidquran.ui.util.TranslationsSpinnerAdapter;
import com.quran.labs.androidquran.util.AudioUtils;
import com.quran.labs.androidquran.util.QuranAppUtils;
import com.quran.labs.androidquran.util.QuranFileUtils;
import com.quran.labs.androidquran.util.QuranScreenInfo;
import com.quran.labs.androidquran.util.QuranSettings;
import com.quran.labs.androidquran.util.QuranUtils;
import com.quran.labs.androidquran.util.ShareUtil;
import com.quran.labs.androidquran.widgets.AudioStatusBar;
import com.quran.labs.androidquran.widgets.AyahToolBar;
import com.quran.labs.androidquran.widgets.IconPageIndicator;
import com.quran.labs.androidquran.widgets.QuranSpinner;
import com.quran.labs.androidquran.widgets.SlidingUpPanelLayout;
import java.io.File;
import java.lang.ref.WeakReference;
import java.util.List;
import java.util.Set;
import javax.inject.Inject;
import io.reactivex.Observable;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.observers.DisposableObserver;
import io.reactivex.observers.DisposableSingleObserver;
import io.reactivex.schedulers.Schedulers;
import timber.log.Timber;
import static com.quran.labs.androidquran.data.Constants.PAGES_LAST;
import static com.quran.labs.androidquran.data.Constants.PAGES_LAST_DUAL;
import static com.quran.labs.androidquran.ui.helpers.SlidingPagerAdapter.AUDIO_PAGE;
import static com.quran.labs.androidquran.ui.helpers.SlidingPagerAdapter.PAGES;
import static com.quran.labs.androidquran.ui.helpers.SlidingPagerAdapter.TAG_PAGE;
import static com.quran.labs.androidquran.ui.helpers.SlidingPagerAdapter.TRANSLATION_PAGE;
import static com.quran.labs.androidquran.widgets.AyahToolBar.AyahToolBarPosition;
public class PagerActivity extends QuranActionBarActivity implements
AudioStatusBar.AudioBarListener,
DefaultDownloadReceiver.DownloadListener,
TagBookmarkDialog.OnBookmarkTagsUpdateListener,
AyahSelectedListener {
private static final String AUDIO_DOWNLOAD_KEY = "AUDIO_DOWNLOAD_KEY";
private static final String LAST_AUDIO_DL_REQUEST = "LAST_AUDIO_DL_REQUEST";
private static final String LAST_READ_PAGE = "LAST_READ_PAGE";
private static final String LAST_READING_MODE_IS_TRANSLATION =
"LAST_READING_MODE_IS_TRANSLATION";
private static final String LAST_ACTIONBAR_STATE = "LAST_ACTIONBAR_STATE";
private static final String LAST_AUDIO_REQUEST = "LAST_AUDIO_REQUEST";
private static final String LAST_START_POINT = "LAST_START_POINT";
private static final String LAST_ENDING_POINT = "LAST_ENDING_POINT";
public static final String EXTRA_JUMP_TO_TRANSLATION = "jumpToTranslation";
public static final String EXTRA_HIGHLIGHT_SURA = "highlightSura";
public static final String EXTRA_HIGHLIGHT_AYAH = "highlightAyah";
public static final String LAST_WAS_DUAL_PAGES = "wasDualPages";
private static final long DEFAULT_HIDE_AFTER_TIME = 2000;
private long lastPopupTime = 0;
private boolean isActionBarHidden = true;
private AudioStatusBar audioStatusBar = null;
private ViewPager viewPager = null;
private QuranPageAdapter pagerAdapter = null;
private boolean shouldReconnect = false;
private SparseBooleanArray bookmarksCache = null;
private DownloadAudioRequest lastAudioDownloadRequest = null;
private boolean showingTranslation = false;
private int highlightedSura = -1;
private int highlightedAyah = -1;
private int ayahToolBarTotalHeight;
private boolean shouldOverridePlaying = false;
private DefaultDownloadReceiver downloadReceiver;
private boolean needsPermissionToDownloadOver3g = true;
private AlertDialog promptDialog = null;
private AyahToolBar ayahToolBar;
private AyahToolBarPosition ayahToolBarPos;
private AudioRequest lastAudioRequest;
private boolean isDualPages = false;
private boolean isLandscape;
private boolean isImmersiveInPortrait;
private Integer lastPlayingSura;
private Integer lastPlayingAyah;
private View toolBarArea;
private boolean promptedForExtraDownload;
private QuranSpinner translationsSpinner;
private ProgressDialog progressDialog;
private ViewGroup.MarginLayoutParams audioBarParams;
private boolean isInMultiWindowMode;
private String[] translationItems;
private List<LocalTranslation> translations;
private TranslationsSpinnerAdapter translationsSpinnerAdapter;
public static final int MSG_HIDE_ACTIONBAR = 1;
// AYAH ACTION PANEL STUFF
// Max height of sliding panel (% of screen)
private static final float PANEL_MAX_HEIGHT = 0.6f;
private SlidingUpPanelLayout slidingPanel;
private ViewPager slidingPager;
private SlidingPagerAdapter slidingPagerAdapter;
private boolean isInAyahMode;
private SuraAyah start;
private SuraAyah end;
private PagerActivityComponent pagerActivityComponent;
@Inject QuranPageWorker quranPageWorker;
@Inject BookmarkModel bookmarkModel;
@Inject RecentPagePresenter recentPagePresenter;
@Inject AyahInfoDatabaseProvider ayahInfoDatabaseProvider;
@Inject QuranSettings quranSettings;
@Inject QuranScreenInfo quranScreenInfo;
@Inject ArabicDatabaseUtils arabicDatabaseUtils;
@Inject TranslationsDBAdapter translationsDBAdapter;
private CompositeDisposable compositeDisposable;
private final PagerHandler handler = new PagerHandler(this);
private static class PagerHandler extends Handler {
private final WeakReference<PagerActivity> activity;
PagerHandler(PagerActivity activity) {
this.activity = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
PagerActivity activity = this.activity.get();
if (activity != null) {
if (msg.what == MSG_HIDE_ACTIONBAR) {
activity.toggleActionBarVisibility(false);
} else {
super.handleMessage(msg);
}
}
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
QuranApplication quranApp = (QuranApplication) getApplication();
quranApp.refreshLocale(this, false);
super.onCreate(savedInstanceState);
// field injection
getPagerActivityComponent().inject(this);
bookmarksCache = new SparseBooleanArray();
boolean shouldAdjustPageNumber = false;
isDualPages = QuranUtils.isDualPages(this, quranScreenInfo);
// remove the window background to avoid overdraw. note that, per Romain's blog, this is
// acceptable (as long as we don't set the background color to null in the theme, since
// that is used to generate preview windows).
getWindow().setBackgroundDrawable(null);
int page = -1;
isActionBarHidden = true;
if (savedInstanceState != null) {
Timber.d("non-null saved instance state!");
DownloadAudioRequest lastAudioRequest =
savedInstanceState.getParcelable(LAST_AUDIO_DL_REQUEST);
if (lastAudioRequest != null) {
Timber.d("restoring request from saved instance!");
lastAudioDownloadRequest = lastAudioRequest;
}
page = savedInstanceState.getInt(LAST_READ_PAGE, -1);
if (page != -1) {
page = PAGES_LAST - page;
}
showingTranslation = savedInstanceState
.getBoolean(LAST_READING_MODE_IS_TRANSLATION, false);
if (savedInstanceState.containsKey(LAST_ACTIONBAR_STATE)) {
isActionBarHidden = !savedInstanceState
.getBoolean(LAST_ACTIONBAR_STATE);
}
boolean lastWasDualPages = savedInstanceState.getBoolean(LAST_WAS_DUAL_PAGES, isDualPages);
shouldAdjustPageNumber = (lastWasDualPages != isDualPages);
start = savedInstanceState.getParcelable(LAST_START_POINT);
end = savedInstanceState.getParcelable(LAST_ENDING_POINT);
this.lastAudioRequest = savedInstanceState.getParcelable(LAST_AUDIO_REQUEST);
} else {
Intent intent = getIntent();
Bundle extras = intent.getExtras();
if (extras != null) {
page = PAGES_LAST - extras.getInt("page", Constants.PAGES_FIRST);
showingTranslation = extras.getBoolean(EXTRA_JUMP_TO_TRANSLATION, showingTranslation);
highlightedSura = extras.getInt(EXTRA_HIGHLIGHT_SURA, -1);
highlightedAyah = extras.getInt(EXTRA_HIGHLIGHT_AYAH, -1);
}
}
compositeDisposable = new CompositeDisposable();
// subscribe to changes in bookmarks
compositeDisposable.add(
bookmarkModel.bookmarksObservable()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(ignore -> {
onBookmarksChanged();
}));
final Resources resources = getResources();
isImmersiveInPortrait = resources.getBoolean(R.bool.immersive_in_portrait);
isLandscape = resources.getConfiguration().orientation ==
Configuration.ORIENTATION_LANDSCAPE;
ayahToolBarTotalHeight = resources
.getDimensionPixelSize(R.dimen.toolbar_total_height);
setContentView(R.layout.quran_page_activity_slider);
audioStatusBar = (AudioStatusBar) findViewById(R.id.audio_area);
audioStatusBar.setAudioBarListener(this);
audioBarParams = (ViewGroup.MarginLayoutParams) audioStatusBar.getLayoutParams();
toolBarArea = findViewById(R.id.toolbar_area);
translationsSpinner = (QuranSpinner) findViewById(R.id.spinner);
// this is the colored view behind the status bar on kitkat and above
final View statusBarBackground = findViewById(R.id.status_bg);
statusBarBackground.getLayoutParams().height = getStatusBarHeight();
final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
if (quranSettings.isArabicNames() || QuranUtils.isRtl()) {
// remove when we remove LTR from quran_page_activity's root
ViewCompat.setLayoutDirection(toolbar, ViewCompat.LAYOUT_DIRECTION_RTL);
}
setSupportActionBar(toolbar);
final ActionBar ab = getSupportActionBar();
if (ab != null) {
ab.setDisplayShowHomeEnabled(true);
ab.setDisplayHomeAsUpEnabled(true);
}
initAyahActionPanel();
if (showingTranslation && translationItems != null) {
updateActionBarSpinner();
} else {
updateActionBarTitle(PAGES_LAST - page);
}
lastPopupTime = System.currentTimeMillis();
pagerAdapter = new QuranPageAdapter(
getSupportFragmentManager(), isDualPages, showingTranslation);
ayahToolBar = (AyahToolBar) findViewById(R.id.ayah_toolbar);
viewPager = (ViewPager) findViewById(R.id.quran_pager);
viewPager.setAdapter(pagerAdapter);
ayahToolBar.setOnItemSelectedListener(new AyahMenuItemSelectionHandler());
viewPager.addOnPageChangeListener(new OnPageChangeListener() {
@Override
public void onPageScrollStateChanged(int state) {
}
@Override
public void onPageScrolled(int position, float positionOffset,
int positionOffsetPixels) {
if (ayahToolBar.isShowing() && ayahToolBarPos != null) {
int barPos = QuranInfo.getPosFromPage(start.getPage(), isDualPages);
if (position == barPos) {
// Swiping to next ViewPager page (i.e. prev quran page)
ayahToolBarPos.xScroll = 0 - positionOffsetPixels;
} else if (position == barPos - 1) {
// Swiping to prev ViewPager page (i.e. next quran page)
ayahToolBarPos.xScroll = viewPager.getWidth() - positionOffsetPixels;
} else {
// Totally off screen, should hide toolbar
ayahToolBar.setVisibility(View.GONE);
return;
}
ayahToolBar.updatePosition(ayahToolBarPos);
// If the toolbar is not showing, show it
if (ayahToolBar.getVisibility() != View.VISIBLE) {
ayahToolBar.setVisibility(View.VISIBLE);
}
}
}
@Override
public void onPageSelected(int position) {
Timber.d("onPageSelected(): %d", position);
final int page = QuranInfo.getPageFromPos(position, isDualPages);
if (quranSettings.shouldDisplayMarkerPopup()) {
lastPopupTime = QuranDisplayHelper.displayMarkerPopup(
PagerActivity.this, page, lastPopupTime);
if (isDualPages) {
lastPopupTime = QuranDisplayHelper.displayMarkerPopup(
PagerActivity.this, page - 1, lastPopupTime);
}
}
if (!showingTranslation) {
updateActionBarTitle(page);
} else {
refreshActionBarSpinner();
}
if (bookmarksCache.indexOfKey(page) < 0) {
if (isDualPages) {
if (bookmarksCache.indexOfKey(page - 1) < 0) {
checkIfPageIsBookmarked(page - 1, page);
}
} else {
// we don't have the key
checkIfPageIsBookmarked(page);
}
}
// If we're more than 1 page away from ayah selection end ayah mode
if (isInAyahMode) {
int ayahPos = QuranInfo.getPosFromPage(start.getPage(), isDualPages);
if (Math.abs(ayahPos - position) > 1) {
endAyahMode();
}
}
}
});
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
setUiVisibilityListener();
audioStatusBar.setVisibility(View.VISIBLE);
}
toggleActionBarVisibility(true);
if (shouldAdjustPageNumber) {
// when going from two page per screen to one or vice versa, we adjust the page number,
// such that the first page is always selected.
int curPage = PAGES_LAST - page;
if (isDualPages) {
if (curPage % 2 != 0) {
curPage++;
}
curPage = PAGES_LAST_DUAL - (curPage / 2);
} else {
if (curPage % 2 == 0) {
curPage--;
}
curPage = PAGES_LAST - curPage;
}
page = curPage;
} else if (isDualPages) {
page = page / 2;
}
viewPager.setCurrentItem(page);
// just got created, need to reconnect to service
shouldReconnect = true;
// enforce orientation lock
if (quranSettings.isLockOrientation()) {
int current = getResources().getConfiguration().orientation;
if (quranSettings.isLandscapeOrientation()) {
if (current == Configuration.ORIENTATION_PORTRAIT) {
setRequestedOrientation(
ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
return;
}
} else if (current == Configuration.ORIENTATION_LANDSCAPE) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
return;
}
}
LocalBroadcastManager.getInstance(this).registerReceiver(
audioReceiver,
new IntentFilter(AudioService.AudioUpdateIntent.INTENT_NAME));
downloadReceiver = new DefaultDownloadReceiver(this,
QuranDownloadService.DOWNLOAD_TYPE_AUDIO);
String action = QuranDownloadNotifier.ProgressIntent.INTENT_NAME;
LocalBroadcastManager.getInstance(this).registerReceiver(
downloadReceiver,
new IntentFilter(action));
downloadReceiver.setListener(this);
}
public Observable<Integer> getViewPagerObservable() {
return Observable.create(e -> {
final OnPageChangeListener pageChangedListener =
new ViewPager.SimpleOnPageChangeListener() {
@Override
public void onPageSelected(int position) {
e.onNext(QuranInfo.getPageFromPos(position, isDualPages));
}
};
viewPager.addOnPageChangeListener(pageChangedListener);
e.onNext(getCurrentPage());
e.setCancellable(() -> viewPager.removeOnPageChangeListener(pageChangedListener));
});
}
private int getStatusBarHeight() {
// thanks to https://github.com/jgilfelt/SystemBarTint for this
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
final Resources resources = getResources();
final int resId = resources.getIdentifier(
"status_bar_height", "dimen", "android");
if (resId > 0) {
return resources.getDimensionPixelSize(resId);
}
}
return 0;
}
private void initAyahActionPanel() {
slidingPanel = (SlidingUpPanelLayout) findViewById(R.id.sliding_panel);
final ViewGroup slidingLayout =
(ViewGroup) slidingPanel.findViewById(R.id.sliding_layout);
slidingPager = (ViewPager) slidingPanel
.findViewById(R.id.sliding_layout_pager);
final IconPageIndicator slidingPageIndicator =
(IconPageIndicator) slidingPanel
.findViewById(R.id.sliding_pager_indicator);
// Find close button and set listener
final View closeButton = slidingPanel
.findViewById(R.id.sliding_menu_close);
closeButton.setOnClickListener(v -> endAyahMode());
// Create and set fragment pager adapter
slidingPagerAdapter = new SlidingPagerAdapter(getSupportFragmentManager(),
Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 &&
(quranSettings.isArabicNames() || QuranUtils.isRtl()));
slidingPager.setAdapter(slidingPagerAdapter);
// Attach the view pager to the action bar
slidingPageIndicator.setViewPager(slidingPager);
// Set sliding layout parameters
int displayHeight = getResources().getDisplayMetrics().heightPixels;
slidingLayout.getLayoutParams().height =
(int) (displayHeight * PANEL_MAX_HEIGHT);
slidingPanel.setEnableDragViewTouchEvents(true);
slidingPanel.setPanelSlideListener(new SlidingPanelListener());
slidingLayout.setVisibility(View.GONE);
// When clicking any menu items, expand the panel
slidingPageIndicator.setOnClickListener(v -> {
if (!slidingPanel.isExpanded()) {
slidingPanel.expandPane();
}
});
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
handler.sendEmptyMessageDelayed(MSG_HIDE_ACTIONBAR, DEFAULT_HIDE_AFTER_TIME);
} else {
handler.removeMessages(MSG_HIDE_ACTIONBAR);
}
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
private void setUiVisibility(boolean isVisible) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT &&
(isLandscape || isImmersiveInPortrait)){
setUiVisibilityKitKat(isVisible);
if (isInMultiWindowMode) {
animateToolBar(isVisible);
}
return;
}
int flags = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
if (!isVisible) {
flags |= View.SYSTEM_UI_FLAG_LOW_PROFILE | View.SYSTEM_UI_FLAG_FULLSCREEN;
}
viewPager.setSystemUiVisibility(flags);
if (isInMultiWindowMode) {
animateToolBar(isVisible);
}
}
@TargetApi(Build.VERSION_CODES.KITKAT)
private void setUiVisibilityKitKat(boolean isVisible) {
int flags = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
if (!isVisible) {
flags |= View.SYSTEM_UI_FLAG_LOW_PROFILE
| View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_IMMERSIVE;
}
viewPager.setSystemUiVisibility(flags);
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
private void setUiVisibilityListener(){
viewPager.setOnSystemUiVisibilityChangeListener(
flags -> {
boolean visible = (flags & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0;
animateToolBar(visible);
});
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
private void clearUiVisibilityListener(){
viewPager.setOnSystemUiVisibilityChangeListener(null);
}
private void animateToolBar(boolean visible) {
isActionBarHidden = !visible;
if (visible) {
audioStatusBar.updateSelectedItem();
}
// animate toolbar
toolBarArea.animate()
.translationY(visible ? 0 : -toolBarArea.getHeight())
.setDuration(250)
.start();
/* the bottom margin on the audio bar is not part of its height, and so we have to
* take it into account when animating the audio bar off the screen. */
final int bottomMargin = audioBarParams.bottomMargin;
// and audio bar
audioStatusBar.animate()
.translationY(visible ? 0 : audioStatusBar.getHeight() + bottomMargin)
.setDuration(250)
.start();
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
boolean navigate = audioStatusBar.getCurrentMode() !=
AudioStatusBar.PLAYING_MODE
&& PreferenceManager.getDefaultSharedPreferences(this).
getBoolean(Constants.PREF_USE_VOLUME_KEY_NAV, false);
if (navigate && keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
viewPager.setCurrentItem(viewPager.getCurrentItem() - 1);
return true;
} else if (navigate && keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
viewPager.setCurrentItem(viewPager.getCurrentItem() + 1);
return true;
}
return super.onKeyDown(keyCode, event);
}
@Override
public boolean onKeyUp(int keyCode, @NonNull KeyEvent event) {
return ((keyCode == KeyEvent.KEYCODE_VOLUME_DOWN ||
keyCode == KeyEvent.KEYCODE_VOLUME_UP) &&
audioStatusBar.getCurrentMode() !=
AudioStatusBar.PLAYING_MODE &&
PreferenceManager.getDefaultSharedPreferences(this)
.getBoolean(Constants.PREF_USE_VOLUME_KEY_NAV, false))
|| super.onKeyUp(keyCode, event);
}
@Override
public void onResume() {
super.onResume();
recentPagePresenter.bind(this);
isInMultiWindowMode = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && isInMultiWindowMode();
// read the list of translations
requestTranslationsList();
if (shouldReconnect) {
startService(AudioUtils.getAudioIntent(this, AudioService.ACTION_CONNECT));
shouldReconnect = false;
}
if (highlightedSura > 0 && highlightedAyah > 0) {
handler.postDelayed(() ->
highlightAyah(highlightedSura, highlightedAyah, false, HighlightType.SELECTION), 750);
}
}
@NonNull
public PagerActivityComponent getPagerActivityComponent() {
// a fragment may call this before Activity's onCreate, so cache and reuse.
if (pagerActivityComponent == null) {
pagerActivityComponent = ((QuranApplication) getApplication())
.getApplicationComponent()
.pagerActivityComponentBuilder()
.withPagerActivityModule(new PagerActivityModule(this))
.build();
}
return pagerActivityComponent;
}
public void showGetRequiredFilesDialog() {
if (promptDialog != null) {
return;
}
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage(R.string.download_extra_data)
.setPositiveButton(R.string.downloadPrompt_ok,
(dialog, option) -> {
downloadRequiredFiles();
dialog.dismiss();
promptDialog = null;
})
.setNegativeButton(R.string.downloadPrompt_no,
(dialog, option) -> {
dialog.dismiss();
promptDialog = null;
});
promptDialog = builder.create();
promptDialog.show();
}
private void downloadRequiredFiles() {
int downloadType = QuranDownloadService.DOWNLOAD_TYPE_AUDIO;
if (audioStatusBar.getCurrentMode() == AudioStatusBar.STOPPED_MODE) {
// if we're stopped, use audio download bar as our progress bar
audioStatusBar.switchMode(AudioStatusBar.DOWNLOADING_MODE);
if (isActionBarHidden) {
toggleActionBar();
}
} else {
// if audio is playing, let's not disrupt it - do this using a
// different type so the broadcast receiver ignores it.
downloadType = QuranDownloadService.DOWNLOAD_TYPE_ARABIC_SEARCH_DB;
}
boolean haveDownload = false;
if (!QuranFileUtils.haveAyaPositionFile(this)) {
String url = QuranFileUtils.getAyaPositionFileUrl();
if (QuranUtils.isDualPages(this, quranScreenInfo)) {
url = QuranFileUtils.getAyaPositionFileUrl(
quranScreenInfo.getTabletWidthParam());
}
String destination = QuranFileUtils.getQuranAyahDatabaseDirectory(this);
// start the download
String notificationTitle = getString(R.string.highlighting_database);
Intent intent = ServiceIntentHelper.getDownloadIntent(this, url,
destination, notificationTitle, AUDIO_DOWNLOAD_KEY,
downloadType);
startService(intent);
haveDownload = true;
}
if (!QuranFileUtils.hasArabicSearchDatabase(this)) {
String url = QuranFileUtils.getArabicSearchDatabaseUrl();
// show "downloading required files" unless we already showed that for
// highlighting database, in which case show "downloading search data"
String notificationTitle = getString(R.string.highlighting_database);
if (haveDownload) {
notificationTitle = getString(R.string.search_data);
}
Intent intent = ServiceIntentHelper.getDownloadIntent(this, url,
QuranFileUtils.getQuranDatabaseDirectory(this), notificationTitle,
AUDIO_DOWNLOAD_KEY, downloadType);
intent.putExtra(QuranDownloadService.EXTRA_OUTPUT_FILE_NAME,
QuranDataProvider.QURAN_ARABIC_DATABASE);
startService(intent);
}
if (downloadType != QuranDownloadService.DOWNLOAD_TYPE_AUDIO) {
// if audio is playing, just show a status notification
Toast.makeText(this, R.string.downloading_title,
Toast.LENGTH_SHORT).show();
}
}
@Override
public void onNewIntent(Intent intent) {
if (intent == null) {
return;
}
recentPagePresenter.onJump();
Bundle extras = intent.getExtras();
if (extras != null) {
int page = PAGES_LAST - extras.getInt("page", Constants.PAGES_FIRST);
updateActionBarTitle(PAGES_LAST - page);
boolean currentValue = showingTranslation;
showingTranslation = extras.getBoolean(EXTRA_JUMP_TO_TRANSLATION, showingTranslation);
highlightedSura = extras.getInt(EXTRA_HIGHLIGHT_SURA, -1);
highlightedAyah = extras.getInt(EXTRA_HIGHLIGHT_AYAH, -1);
if (showingTranslation != currentValue) {
if (showingTranslation) {
pagerAdapter.setTranslationMode();
} else {
pagerAdapter.setQuranMode();
}
supportInvalidateOptionsMenu();
}
if (highlightedAyah > 0 && highlightedSura > 0) {
// this will jump to the right page automagically
highlightAyah(highlightedSura, highlightedAyah, true, HighlightType.SELECTION);
} else {
if (isDualPages) {
page = page / 2;
}
viewPager.setCurrentItem(page);
}
setIntent(intent);
}
}
public void jumpTo(int page) {
Intent i = new Intent(this, PagerActivity.class);
i.putExtra("page", page);
onNewIntent(i);
}
public void jumpToAndHighlight(int page, int sura, int ayah) {
Intent i = new Intent(this, PagerActivity.class);
i.putExtra("page", page);
i.putExtra(EXTRA_HIGHLIGHT_SURA, sura);
i.putExtra(EXTRA_HIGHLIGHT_AYAH, ayah);
onNewIntent(i);
}
@Override
public void onPause() {
if (promptDialog != null) {
promptDialog.dismiss();
promptDialog = null;
}
recentPagePresenter.unbind(this);
quranSettings.setWasShowingTranslation(pagerAdapter.getIsShowingTranslation());
super.onPause();
}
@Override
protected void onDestroy() {
Timber.d("onDestroy()");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
clearUiVisibilityListener();
}
// remove broadcast receivers
LocalBroadcastManager.getInstance(this).unregisterReceiver(audioReceiver);
if (downloadReceiver != null) {
downloadReceiver.setListener(null);
LocalBroadcastManager.getInstance(this)
.unregisterReceiver(downloadReceiver);
downloadReceiver = null;
}
compositeDisposable.dispose();
handler.removeCallbacksAndMessages(null);
dismissProgressDialog();
super.onDestroy();
}
@Override
public void onSaveInstanceState(Bundle state) {
if (lastAudioDownloadRequest != null) {
state.putParcelable(LAST_AUDIO_DL_REQUEST, lastAudioDownloadRequest);
}
int lastPage = QuranInfo.getPageFromPos(viewPager.getCurrentItem(), isDualPages);
state.putInt(LAST_READ_PAGE, lastPage);
state.putBoolean(LAST_READING_MODE_IS_TRANSLATION, showingTranslation);
state.putBoolean(LAST_ACTIONBAR_STATE, isActionBarHidden);
state.putBoolean(LAST_WAS_DUAL_PAGES, isDualPages);
if (start != null && end != null) {
state.putParcelable(LAST_START_POINT, start);
state.putParcelable(LAST_ENDING_POINT, end);
}
if (lastAudioRequest != null) {
state.putParcelable(LAST_AUDIO_REQUEST, lastAudioRequest);
}
super.onSaveInstanceState(state);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.quran_menu, menu);
final MenuItem item = menu.findItem(R.id.search);
final SearchView searchView = (SearchView) MenuItemCompat.getActionView(item);
final SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
searchView.setQueryHint(getString(R.string.search_hint));
searchView.setSearchableInfo(searchManager.getSearchableInfo(
new ComponentName(this, SearchActivity.class)));
return true;
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
MenuItem item = menu.findItem(R.id.favorite_item);
if (item != null) {
int page = QuranInfo.getPageFromPos(viewPager.getCurrentItem(), isDualPages);
boolean bookmarked = false;
if (bookmarksCache.indexOfKey(page) >= 0) {
bookmarked = bookmarksCache.get(page);
}
if (!bookmarked && isDualPages &&
bookmarksCache.indexOfKey(page - 1) >= 0) {
bookmarked = bookmarksCache.get(page - 1);
}
item.setIcon(bookmarked ? R.drawable.ic_favorite : R.drawable.ic_not_favorite);
}
MenuItem quran = menu.findItem(R.id.goto_quran);
MenuItem translation = menu.findItem(R.id.goto_translation);
if (quran != null && translation != null) {
if (!showingTranslation) {
quran.setVisible(false);
translation.setVisible(true);
} else {
quran.setVisible(true);
translation.setVisible(false);
}
}
MenuItem nightMode = menu.findItem(R.id.night_mode);
if (nightMode != null) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
final boolean isNightMode = prefs.getBoolean(Constants.PREF_NIGHT_MODE, false);
nightMode.setChecked(isNightMode);
nightMode.setIcon(isNightMode ? R.drawable.ic_night_mode : R.drawable.ic_day_mode);
}
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
final int itemId = item.getItemId();
if (itemId == R.id.favorite_item) {
int page = getCurrentPage();
toggleBookmark(null, null, page);
return true;
} else if (itemId == R.id.goto_quran) {
switchToQuran();
return true;
} else if (itemId == R.id.goto_translation) {
switchToTranslation();
return true;
} else if (itemId == R.id.night_mode) {
SharedPreferences prefs = PreferenceManager
.getDefaultSharedPreferences(this);
SharedPreferences.Editor prefsEditor = prefs.edit();
final boolean isNightMode = !item.isChecked();
prefsEditor.putBoolean(Constants.PREF_NIGHT_MODE, isNightMode).apply();
item.setIcon(isNightMode ?
R.drawable.ic_night_mode : R.drawable.ic_day_mode);
item.setChecked(isNightMode);
refreshQuranPages();
return true;
} else if (itemId == R.id.settings) {
Intent i = new Intent(this, QuranPreferenceActivity.class);
startActivity(i);
return true;
} else if (itemId == R.id.help) {
Intent i = new Intent(this, HelpActivity.class);
startActivity(i);
return true;
} else if (itemId == android.R.id.home) {
finish();
return true;
} else if (itemId == R.id.jump) {
FragmentManager fm = getSupportFragmentManager();
JumpFragment jumpDialog = new JumpFragment();
jumpDialog.show(fm, JumpFragment.TAG);
return true;
}
return super.onOptionsItemSelected(item);
}
private void refreshQuranPages() {
int pos = viewPager.getCurrentItem();
int start = (pos == 0) ? pos : pos - 1;
int end = (pos == pagerAdapter.getCount() - 1) ? pos : pos + 1;
for (int i = start; i <= end; i++) {
Fragment f = pagerAdapter.getFragmentIfExists(i);
if (f instanceof QuranPage) {
((QuranPage) f).updateView();
}
}
}
@Override
public boolean onSearchRequested() {
return super.onSearchRequested();
}
private void switchToQuran() {
pagerAdapter.setQuranMode();
showingTranslation = false;
int page = getCurrentPage();
supportInvalidateOptionsMenu();
updateActionBarTitle(page);
if (highlightedSura > 0 && highlightedAyah > 0) {
highlightAyah(highlightedSura, highlightedAyah, false, HighlightType.SELECTION);
}
}
private void switchToTranslation() {
if (isInAyahMode) {
endAyahMode();
}
if (translations.size() == 0) {
startTranslationManager();
} else {
pagerAdapter.setTranslationMode();
showingTranslation = true;
supportInvalidateOptionsMenu();
updateActionBarSpinner();
if (highlightedSura > 0 && highlightedAyah > 0) {
highlightAyah(highlightedSura, highlightedAyah, false, HighlightType.SELECTION);
}
}
if (!QuranFileUtils.hasArabicSearchDatabase(this) && !promptedForExtraDownload) {
promptedForExtraDownload = true;
showGetRequiredFilesDialog();
}
}
public void startTranslationManager() {
Intent i = new Intent(this, TranslationManagerActivity.class);
startActivity(i);
}
private TranslationsSpinnerAdapter.OnSelectionChangedListener translationItemChangedListener =
selectedItems -> {
quranSettings.setActiveTranslations(selectedItems);
int pos = viewPager.getCurrentItem() - 1;
for (int count = 0; count < 3; count++) {
if (pos + count < 0) {
continue;
}
Fragment f = pagerAdapter.getFragmentIfExists(pos + count);
if (f instanceof TranslationFragment) {
((TranslationFragment) f).refresh();
} else if (f instanceof TabletFragment) {
((TabletFragment) f).refresh();
}
}
};
public List<LocalTranslation> getTranslations() {
return translations;
}
public String[] getTranslationNames() {
return translationItems;
}
@Override
public void onAddTagSelected() {
FragmentManager fm = getSupportFragmentManager();
AddTagDialog dialog = new AddTagDialog();
dialog.show(fm, AddTagDialog.TAG);
}
private void onBookmarksChanged() {
if (isInAyahMode) {
compositeDisposable.add(
bookmarkModel.getIsBookmarkedObservable(start.sura, start.ayah, start.getPage())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new DisposableSingleObserver<Boolean>() {
@Override
public void onSuccess(Boolean isBookmarked) {
updateAyahBookmark(start, isBookmarked, true);
}
@Override
public void onError(Throwable e) {
}
}));
}
}
private void updateActionBarTitle(int page) {
String sura = QuranInfo.getSuraNameFromPage(this, page, true);
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
translationsSpinner.setVisibility(View.GONE);
actionBar.setDisplayShowTitleEnabled(true);
actionBar.setTitle(sura);
String desc = QuranInfo.getPageSubtitle(this, page);
actionBar.setSubtitle(desc);
}
}
private void refreshActionBarSpinner() {
if (translationsSpinnerAdapter != null) {
translationsSpinnerAdapter.notifyDataSetChanged();
} else {
updateActionBarSpinner();
}
}
private int getCurrentPage() {
return QuranInfo.getPageFromPos(viewPager.getCurrentItem(), isDualPages);
}
private void updateActionBarSpinner() {
if (translationItems == null || translationItems.length == 0) {
int page = getCurrentPage();
updateActionBarTitle(page);
return;
}
if (translationsSpinnerAdapter == null) {
translationsSpinnerAdapter = new TranslationsSpinnerAdapter(this,
R.layout.translation_ab_spinner_item, translationItems, translations,
quranSettings.getActiveTranslations(),
translationItemChangedListener) {
@NonNull
@Override
public View getView(int position, View convertView, @NonNull ViewGroup parent) {
int type = super.getItemViewType(position);
convertView = super.getView(position, convertView, parent);
if (type == 0) {
SpinnerHolder holder = (SpinnerHolder) convertView.getTag();
int page = getCurrentPage();
String sura = QuranInfo.getSuraNameFromPage(PagerActivity.this, page, true);
holder.title.setText(sura);
String desc = QuranInfo.getPageSubtitle(PagerActivity.this, page);
holder.subtitle.setText(desc);
holder.subtitle.setVisibility(View.VISIBLE);
}
return convertView;
}
};
translationsSpinner.setAdapter(translationsSpinnerAdapter);
}
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayShowTitleEnabled(false);
translationsSpinner.setVisibility(View.VISIBLE);
}
}
private BroadcastReceiver audioReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent != null) {
int state = intent.getIntExtra(
AudioService.AudioUpdateIntent.STATUS, -1);
int sura = intent.getIntExtra(
AudioService.AudioUpdateIntent.SURA, -1);
int ayah = intent.getIntExtra(
AudioService.AudioUpdateIntent.AYAH, -1);
int repeatCount = intent.getIntExtra(
AudioService.AudioUpdateIntent.REPEAT_COUNT, -200);
AudioRequest request = intent.getParcelableExtra(AudioService.AudioUpdateIntent.REQUEST);
if (request != null) {
lastAudioRequest = request;
}
if (state == AudioService.AudioUpdateIntent.PLAYING) {
audioStatusBar.switchMode(AudioStatusBar.PLAYING_MODE);
highlightAyah(sura, ayah, HighlightType.AUDIO);
if (repeatCount >= -1) {
audioStatusBar.setRepeatCount(repeatCount);
}
} else if (state == AudioService.AudioUpdateIntent.PAUSED) {
audioStatusBar.switchMode(AudioStatusBar.PAUSED_MODE);
highlightAyah(sura, ayah, HighlightType.AUDIO);
} else if (state == AudioService.AudioUpdateIntent.STOPPED) {
audioStatusBar.switchMode(AudioStatusBar.STOPPED_MODE);
unHighlightAyahs(HighlightType.AUDIO);
lastAudioRequest = null;
AudioRequest qi = intent.getParcelableExtra(AudioService.EXTRA_PLAY_INFO);
if (qi != null) {
// this means we stopped due to missing audio
}
}
}
}
};
@Override
public void updateDownloadProgress(int progress,
long downloadedSize, long totalSize) {
audioStatusBar.switchMode(
AudioStatusBar.DOWNLOADING_MODE);
audioStatusBar.setProgress(progress);
}
@Override
public void updateProcessingProgress(int progress,
int processFiles, int totalFiles) {
audioStatusBar.setProgressText(getString(R.string.extracting_title), false);
audioStatusBar.setProgress(-1);
}
@Override
public void handleDownloadTemporaryError(int errorId) {
audioStatusBar.setProgressText(getString(errorId), false);
}
@Override
public void handleDownloadSuccess() {
refreshQuranPages();
playAudioRequest(lastAudioDownloadRequest);
}
@Override
public void handleDownloadFailure(int errId) {
String s = getString(errId);
audioStatusBar.setProgressText(s, true);
}
public void toggleActionBarVisibility(boolean visible) {
if (visible == isActionBarHidden) {
toggleActionBar();
}
}
public void toggleActionBar() {
if (isActionBarHidden) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
setUiVisibility(true);
} else {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
toolBarArea.setVisibility(View.VISIBLE);
audioStatusBar.updateSelectedItem();
audioStatusBar.setVisibility(View.VISIBLE);
}
isActionBarHidden = false;
} else {
handler.removeMessages(MSG_HIDE_ACTIONBAR);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
setUiVisibility(false);
} else {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
toolBarArea.setVisibility(View.GONE);
audioStatusBar.setVisibility(View.GONE);
}
isActionBarHidden = true;
}
}
public QuranPageWorker getQuranPageWorker() {
return quranPageWorker;
}
public void highlightAyah(int sura, int ayah, HighlightType type) {
if (type == HighlightType.AUDIO) {
lastPlayingSura = sura;
lastPlayingAyah = ayah;
}
highlightAyah(sura, ayah, true, type);
}
private void highlightAyah(int sura, int ayah,
boolean force, HighlightType type) {
Timber.d("highlightAyah() - %s:%s", sura, ayah);
int page = QuranInfo.getPageFromSuraAyah(sura, ayah);
if (page < Constants.PAGES_FIRST ||
PAGES_LAST < page) {
return;
}
int position = QuranInfo.getPosFromPage(page, isDualPages);
if (position != viewPager.getCurrentItem() && force) {
unHighlightAyahs(type);
viewPager.setCurrentItem(position);
}
Fragment f = pagerAdapter.getFragmentIfExists(position);
if (f instanceof QuranPage && f.isAdded()) {
((QuranPage) f).getAyahTracker().highlightAyah(sura, ayah, type, true);
}
}
private void unHighlightAyah(int sura, int ayah, HighlightType type) {
int position = viewPager.getCurrentItem();
Fragment f = pagerAdapter.getFragmentIfExists(position);
if (f instanceof QuranPage && f.isVisible()) {
((QuranPage) f).getAyahTracker().unHighlightAyah(sura, ayah, type);
}
}
private void unHighlightAyahs(HighlightType type) {
if (type == HighlightType.AUDIO) {
lastPlayingSura = null;
lastPlayingAyah = null;
}
int position = viewPager.getCurrentItem();
Fragment f = pagerAdapter.getFragmentIfExists(position);
if (f instanceof QuranPage && f.isVisible()) {
((QuranPage) f).getAyahTracker().unHighlightAyahs(type);
}
}
private void requestTranslationsList() {
compositeDisposable.add(
Single.fromCallable(() ->
translationsDBAdapter.getTranslations())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new DisposableSingleObserver<List<LocalTranslation>>() {
@Override
public void onSuccess(List<LocalTranslation> translationList) {
int items = translationList.size();
String[] titles = new String[items];
for (int i = 0; i < items; i++) {
LocalTranslation item = translationList.get(i);
if (!TextUtils.isEmpty(item.translatorForeign)) {
titles[i] = item.translatorForeign;
} else if (!TextUtils.isEmpty(item.translator)) {
titles[i] = item.translator;
} else {
titles[i] = item.name;
}
}
Set<String> activeTranslations = quranSettings.getActiveTranslations();
if (translationsSpinnerAdapter != null) {
translationsSpinnerAdapter.updateItems(titles, translationList, activeTranslations);
}
translationItems = titles;
translations = translationList;
if (showingTranslation) {
// Since translation items have changed, need to
updateActionBarSpinner();
}
}
@Override
public void onError(Throwable e) {
}
}));
}
private void toggleBookmark(final Integer sura, final Integer ayah, final int page) {
compositeDisposable.add(bookmarkModel.toggleBookmarkObservable(sura, ayah, page)
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new DisposableSingleObserver<Boolean>() {
@Override
public void onSuccess(Boolean isBookmarked) {
if (sura == null || ayah == null) {
// page bookmark
bookmarksCache.put(page, isBookmarked);
supportInvalidateOptionsMenu();
} else {
// ayah bookmark
SuraAyah suraAyah = new SuraAyah(sura, ayah);
updateAyahBookmark(suraAyah, isBookmarked, true);
}
}
@Override
public void onError(Throwable e) {
}
}));
}
private void checkIfPageIsBookmarked(Integer... pages) {
compositeDisposable.add(bookmarkModel.getIsBookmarkedObservable(pages)
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new DisposableObserver<Pair<Integer, Boolean>>() {
@Override
public void onNext(Pair<Integer, Boolean> result) {
bookmarksCache.put(result.first, result.second);
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
supportInvalidateOptionsMenu();
}
}));
}
// region Audio playback
@Override
public void onPlayPressed() {
if (audioStatusBar.getCurrentMode() == AudioStatusBar.PAUSED_MODE) {
// if we are "paused," just un-pause.
play(null);
return;
}
int position = viewPager.getCurrentItem();
int page = PAGES_LAST - position;
if (isDualPages) {
page = ((PAGES_LAST_DUAL - position) * 2) - 1;
}
int startSura = QuranInfo.safelyGetSuraOnPage(page);
int startAyah = QuranInfo.PAGE_AYAH_START[page - 1];
playFromAyah(page, startSura, startAyah, false);
}
private void playFromAyah(int page, int startSura,
int startAyah, boolean force) {
final SuraAyah start = new SuraAyah(startSura, startAyah);
playFromAyah(start, null, page, 0, 0, false, force);
}
public void playFromAyah(SuraAyah start, SuraAyah end,
int page, int verseRepeat, int rangeRepeat,
boolean enforceRange, boolean force) {
if (force) {
shouldOverridePlaying = true;
}
QariItem item = audioStatusBar.getAudioInfo();
lastAudioDownloadRequest = getAudioDownloadRequest(start, end, page, item,
verseRepeat, rangeRepeat, enforceRange);
if (quranSettings.shouldStream() && lastAudioDownloadRequest != null &&
!AudioUtils.haveAllFiles(lastAudioDownloadRequest)) {
playStreaming(start, end, page, item, verseRepeat, rangeRepeat, enforceRange);
} else {
playAudioRequest(lastAudioDownloadRequest);
}
}
private void playStreaming(SuraAyah ayah, SuraAyah end,
int page, QariItem item, int verseRepeat,
int rangeRepeat, boolean enforceRange) {
String qariUrl = AudioUtils.getQariUrl(item);
String dbFile = AudioUtils.getQariDatabasePathIfGapless(this, item);
if (!TextUtils.isEmpty(dbFile)) {
// gapless audio is "download only"
lastAudioDownloadRequest = getAudioDownloadRequest(ayah, end, page, item,
verseRepeat, rangeRepeat, enforceRange);
playAudioRequest(lastAudioDownloadRequest);
return;
}
final SuraAyah ending;
if (end != null) {
ending = end;
} else {
// this won't be enforced unless the user sets a range
// repeat, but we set it to a sane default anyway.
ending = AudioUtils.getLastAyahToPlay(ayah, page,
quranSettings.getPreferredDownloadAmount(), isDualPages);
}
AudioRequest request = new StreamingAudioRequest(qariUrl, ayah);
request.setPlayBounds(ayah, ending);
request.setEnforceBounds(enforceRange);
request.setRangeRepeatCount(rangeRepeat);
request.setVerseRepeatCount(verseRepeat);
play(request);
audioStatusBar.switchMode(AudioStatusBar.PLAYING_MODE);
audioStatusBar.setRepeatCount(verseRepeat);
}
@Nullable
private DownloadAudioRequest getAudioDownloadRequest(SuraAyah ayah, SuraAyah ending,
int page, @NonNull QariItem item, int verseRepeat,
int rangeRepeat, boolean enforceBounds) {
final SuraAyah endAyah;
if (ending != null) {
endAyah = ending;
} else {
endAyah = AudioUtils.getLastAyahToPlay(ayah, page,
quranSettings.getPreferredDownloadAmount(), isDualPages);
}
String baseUri = AudioUtils.getLocalQariUrl(this, item);
if (endAyah == null || baseUri == null) {
return null;
}
String dbFile = AudioUtils.getQariDatabasePathIfGapless(this, item);
String fileUrl;
if (TextUtils.isEmpty(dbFile)) {
fileUrl = baseUri + File.separator + "%d" + File.separator +
"%d" + AudioUtils.AUDIO_EXTENSION;
} else {
fileUrl = baseUri + File.separator + "%03d" +
AudioUtils.AUDIO_EXTENSION;
}
DownloadAudioRequest request = new DownloadAudioRequest(fileUrl, ayah, item, baseUri);
request.setGaplessDatabaseFilePath(dbFile);
request.setPlayBounds(ayah, endAyah);
request.setEnforceBounds(enforceBounds);
request.setRangeRepeatCount(rangeRepeat);
request.setVerseRepeatCount(verseRepeat);
return request;
}
private void playAudioRequest(@Nullable DownloadAudioRequest request) {
if (request == null) {
audioStatusBar.switchMode(AudioStatusBar.STOPPED_MODE);
return;
}
boolean needsPermission = needsPermissionToDownloadOver3g;
if (needsPermission) {
if (QuranUtils.isOnWifiNetwork(this)) {
Timber.d("on wifi, don't need permission for download...");
needsPermission = false;
}
}
Timber.d("seeing if we can play audio request...");
if (!QuranFileUtils.haveAyaPositionFile(this)) {
if (needsPermission) {
audioStatusBar.switchMode(AudioStatusBar.PROMPT_DOWNLOAD_MODE);
return;
}
if (isActionBarHidden) {
toggleActionBar();
}
audioStatusBar.switchMode(AudioStatusBar.DOWNLOADING_MODE);
String url = QuranFileUtils.getAyaPositionFileUrl();
String destination = QuranFileUtils.getQuranDatabaseDirectory(this);
// start the download
String notificationTitle = getString(R.string.highlighting_database);
Intent intent = ServiceIntentHelper.getDownloadIntent(this, url,
destination, notificationTitle, AUDIO_DOWNLOAD_KEY,
QuranDownloadService.DOWNLOAD_TYPE_AUDIO);
startService(intent);
} else if (AudioUtils.shouldDownloadGaplessDatabase(request)) {
Timber.d("need to download gapless database...");
if (needsPermission) {
audioStatusBar.switchMode(AudioStatusBar.PROMPT_DOWNLOAD_MODE);
return;
}
if (isActionBarHidden) {
toggleActionBar();
}
audioStatusBar.switchMode(AudioStatusBar.DOWNLOADING_MODE);
String url = AudioUtils.getGaplessDatabaseUrl(request);
String destination = request.getLocalPath();
// start the download
String notificationTitle = getString(R.string.timing_database);
Intent intent = ServiceIntentHelper.getDownloadIntent(this, url,
destination, notificationTitle, AUDIO_DOWNLOAD_KEY,
QuranDownloadService.DOWNLOAD_TYPE_AUDIO);
startService(intent);
} else if (AudioUtils.haveAllFiles(request)) {
if (!AudioUtils.shouldDownloadBasmallah(request)) {
Timber.d("have all files, playing!");
play(request);
lastAudioDownloadRequest = null;
} else {
Timber.d("should download basmalla...");
if (needsPermission) {
audioStatusBar.switchMode(AudioStatusBar.PROMPT_DOWNLOAD_MODE);
return;
}
SuraAyah firstAyah = new SuraAyah(1, 1);
String qariUrl = AudioUtils.getQariUrl(request.getQariItem());
audioStatusBar.switchMode(AudioStatusBar.DOWNLOADING_MODE);
if (isActionBarHidden) {
toggleActionBar();
}
String notificationTitle = QuranInfo.getNotificationTitle(
this, firstAyah, firstAyah, request.isGapless());
Intent intent = ServiceIntentHelper.getDownloadIntent(this, qariUrl,
request.getLocalPath(), notificationTitle,
AUDIO_DOWNLOAD_KEY,
QuranDownloadService.DOWNLOAD_TYPE_AUDIO);
intent.putExtra(QuranDownloadService.EXTRA_START_VERSE, firstAyah);
intent.putExtra(QuranDownloadService.EXTRA_END_VERSE, firstAyah);
startService(intent);
}
} else {
if (needsPermission) {
audioStatusBar.switchMode(AudioStatusBar.PROMPT_DOWNLOAD_MODE);
return;
}
if (isActionBarHidden) {
toggleActionBar();
}
audioStatusBar.switchMode(AudioStatusBar.DOWNLOADING_MODE);
String notificationTitle = QuranInfo.getNotificationTitle(this,
request.getMinAyah(), request.getMaxAyah(), request.isGapless());
String qariUrl = AudioUtils.getQariUrl(request.getQariItem());
Timber.d("need to start download: %s", qariUrl);
// start service
Intent intent = ServiceIntentHelper.getDownloadIntent(this, qariUrl,
request.getLocalPath(), notificationTitle, AUDIO_DOWNLOAD_KEY,
QuranDownloadService.DOWNLOAD_TYPE_AUDIO);
intent.putExtra(QuranDownloadService.EXTRA_START_VERSE,
request.getMinAyah());
intent.putExtra(QuranDownloadService.EXTRA_END_VERSE,
request.getMaxAyah());
intent.putExtra(QuranDownloadService.EXTRA_IS_GAPLESS,
request.isGapless());
startService(intent);
}
}
private void play(AudioRequest request) {
needsPermissionToDownloadOver3g = true;
Intent i = new Intent(this, AudioService.class);
i.setAction(AudioService.ACTION_PLAYBACK);
if (request != null) {
i.putExtra(AudioService.EXTRA_PLAY_INFO, request);
lastAudioRequest = request;
audioStatusBar.setRepeatCount(request.getVerseRepeatCount());
}
if (shouldOverridePlaying) {
// force the current audio to stop and start playing new request
i.putExtra(AudioService.EXTRA_STOP_IF_PLAYING, true);
shouldOverridePlaying = false;
}
// just a playback request, so tell audio service to just continue
// playing (and don't store new audio data) if it was already playing
else {
i.putExtra(AudioService.EXTRA_IGNORE_IF_PLAYING, true);
}
startService(i);
}
@Override
public void onPausePressed() {
startService(AudioUtils.getAudioIntent(
this, AudioService.ACTION_PAUSE));
audioStatusBar.switchMode(AudioStatusBar.PAUSED_MODE);
}
@Override
public void onNextPressed() {
startService(AudioUtils.getAudioIntent(this,
AudioService.ACTION_SKIP));
}
@Override
public void onPreviousPressed() {
startService(AudioUtils.getAudioIntent(this,
AudioService.ACTION_REWIND));
}
@Override
public void onAudioSettingsPressed() {
if (lastPlayingSura != null) {
start = new SuraAyah(lastPlayingSura, lastPlayingAyah);
end = start;
}
if (start == null) {
final int[] bounds = QuranInfo.getPageBounds(getCurrentPage());
start = new SuraAyah(bounds[0], bounds[1]);
end = start;
}
showSlider(AUDIO_PAGE);
}
public boolean updatePlayOptions(int rangeRepeat,
int verseRepeat, boolean enforceRange) {
if (lastAudioRequest != null) {
Intent i = new Intent(this, AudioService.class);
i.setAction(AudioService.ACTION_UPDATE_REPEAT);
i.putExtra(AudioService.EXTRA_VERSE_REPEAT_COUNT, verseRepeat);
i.putExtra(AudioService.EXTRA_RANGE_REPEAT_COUNT, rangeRepeat);
i.putExtra(AudioService.EXTRA_RANGE_RESTRICT, enforceRange);
startService(i);
lastAudioRequest.setVerseRepeatCount(verseRepeat);
lastAudioRequest.setRangeRepeatCount(rangeRepeat);
lastAudioRequest.setEnforceBounds(enforceRange);
audioStatusBar.setRepeatCount(verseRepeat);
return true;
} else {
return false;
}
}
@Override
public void setRepeatCount(int repeatCount) {
if (lastAudioRequest != null) {
Intent i = new Intent(this, AudioService.class);
i.setAction(AudioService.ACTION_UPDATE_REPEAT);
i.putExtra(AudioService.EXTRA_VERSE_REPEAT_COUNT, repeatCount);
startService(i);
lastAudioRequest.setVerseRepeatCount(repeatCount);
}
}
@Override
public void onStopPressed() {
startService(AudioUtils.getAudioIntent(this, AudioService.ACTION_STOP));
audioStatusBar.switchMode(AudioStatusBar.STOPPED_MODE);
unHighlightAyahs(HighlightType.AUDIO);
lastAudioRequest = null;
}
@Override
public void onCancelPressed(boolean cancelDownload) {
if (cancelDownload) {
needsPermissionToDownloadOver3g = true;
int resId = R.string.canceling;
audioStatusBar.setProgressText(getString(resId), true);
Intent i = new Intent(this, QuranDownloadService.class);
i.setAction(QuranDownloadService.ACTION_CANCEL_DOWNLOADS);
startService(i);
} else {
audioStatusBar.switchMode(AudioStatusBar.STOPPED_MODE);
}
}
@Override
public void onAcceptPressed() {
if (lastAudioDownloadRequest != null) {
needsPermissionToDownloadOver3g = false;
playAudioRequest(lastAudioDownloadRequest);
}
}
//endregion
@Override
public void onBackPressed() {
if (isInAyahMode) {
endAyahMode();
} else if (showingTranslation) {
switchToQuran();
} else {
super.onBackPressed();
}
}
// region Ayah selection
@Override
public boolean isListeningForAyahSelection(EventType eventType) {
return eventType == EventType.LONG_PRESS ||
eventType == EventType.SINGLE_TAP && isInAyahMode;
}
@Override
public boolean onAyahSelected(EventType eventType, SuraAyah suraAyah, AyahTracker tracker) {
switch (eventType) {
case SINGLE_TAP:
if (isInAyahMode) {
updateAyahStartSelection(suraAyah, tracker);
return true;
}
return false;
case LONG_PRESS:
if (isInAyahMode) {
updateAyahEndSelection(suraAyah);
} else {
startAyahMode(suraAyah, tracker);
}
viewPager.performHapticFeedback(
HapticFeedbackConstants.LONG_PRESS);
return true;
default:
return false;
}
}
@Override
public boolean onClick(EventType eventType) {
switch (eventType) {
case SINGLE_TAP:
if (!isInAyahMode) {
toggleActionBar();
return true;
}
return false;
case DOUBLE_TAP:
if (isInAyahMode) {
endAyahMode();
return true;
}
return false;
default:
return false;
}
}
public SuraAyah getSelectionStart() {
return start;
}
public SuraAyah getSelectionEnd() {
return end;
}
public AudioRequest getLastAudioRequest() {
return lastAudioRequest;
}
private void startAyahMode(SuraAyah suraAyah, AyahTracker tracker) {
if (!isInAyahMode) {
start = end = suraAyah;
updateToolbarPosition(suraAyah, tracker);
ayahToolBar.showMenu();
showAyahModeHighlights(suraAyah, tracker);
isInAyahMode = true;
}
}
public void endAyahMode() {
ayahToolBar.hideMenu();
slidingPanel.collapsePane();
clearAyahModeHighlights();
isInAyahMode = false;
}
public void nextAyah() {
if (end != null) {
final int ayat = QuranInfo.getNumAyahs(end.sura);
final SuraAyah s;
if (end.ayah + 1 <= ayat) {
s = new SuraAyah(end.sura, end.ayah + 1);
} else if (end.sura < 114) {
s = new SuraAyah(end.sura + 1, 1);
} else {
return;
}
selectAyah(s);
}
}
public void previousAyah() {
if (end != null) {
final SuraAyah s;
if (end.ayah > 1) {
s = new SuraAyah(end.sura, end.ayah - 1);
} else if (end.sura > 1) {
s = new SuraAyah(end.sura - 1, QuranInfo.getNumAyahs(end.sura - 1));
} else {
return;
}
selectAyah(s);
}
}
private void selectAyah(SuraAyah s) {
final int page = s.getPage();
final int position = QuranInfo.getPosFromPage(page, isDualPages);
Fragment f = pagerAdapter.getFragmentIfExists(position);
if (f instanceof QuranPage && f.isVisible()) {
if (position != viewPager.getCurrentItem()) {
viewPager.setCurrentItem(position);
}
updateAyahStartSelection(s, ((QuranPage) f).getAyahTracker());
}
}
private void updateAyahStartSelection(SuraAyah suraAyah, AyahTracker tracker) {
if (isInAyahMode) {
clearAyahModeHighlights();
start = end = suraAyah;
if (ayahToolBar.isShowing()) {
ayahToolBar.resetMenu();
updateToolbarPosition(suraAyah, tracker);
}
if (slidingPanel.isPaneVisible()) {
refreshPages();
}
showAyahModeHighlights(suraAyah, tracker);
}
}
private void updateAyahEndSelection(SuraAyah suraAyah) {
if (isInAyahMode) {
clearAyahModeHighlights();
if (suraAyah.after(start)) {
end = suraAyah;
} else {
end = start;
start = suraAyah;
}
if (slidingPanel.isPaneVisible()) {
refreshPages();
}
showAyahModeRangeHighlights();
}
}
//endregion
private void updateToolbarPosition(final SuraAyah start, AyahTracker tracker) {
compositeDisposable.add(bookmarkModel
.getIsBookmarkedObservable(start.sura, start.ayah, start.getPage())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new DisposableSingleObserver<Boolean>() {
@Override
public void onSuccess(Boolean isBookmarked) {
updateAyahBookmark(start, isBookmarked, false);
}
@Override
public void onError(Throwable e) {
}
}));
ayahToolBarPos = tracker.getToolBarPosition(start.sura, start.ayah,
ayahToolBar.getToolBarWidth(), ayahToolBarTotalHeight);
if (ayahToolBarPos != null) {
ayahToolBar.updatePosition(ayahToolBarPos);
if (ayahToolBar.getVisibility() != View.VISIBLE) {
ayahToolBar.setVisibility(View.VISIBLE);
}
}
}
// Used to sync toolbar with page's SV (landscape non-tablet mode)
public void onQuranPageScroll(int scrollY) {
if (ayahToolBarPos != null) {
ayahToolBarPos.yScroll = 0 - scrollY;
if (isInAyahMode) {
ayahToolBar.updatePosition(ayahToolBarPos);
}
}
}
private void refreshPages() {
for (int page : PAGES) {
final int mappedTagPage = slidingPagerAdapter.getPagePosition(TAG_PAGE);
if (page == mappedTagPage) {
Fragment fragment = slidingPagerAdapter.getFragmentIfExists(mappedTagPage);
if (fragment instanceof TagBookmarkDialog && start != null) {
((TagBookmarkDialog) fragment).updateAyah(start);
}
} else {
AyahActionFragment f = (AyahActionFragment) slidingPagerAdapter
.getFragmentIfExists(page);
if (f != null) {
f.updateAyahSelection(start, end);
}
}
}
}
private void showAyahModeRangeHighlights() {
// Determine the start and end of the selection
int minPage = Math.min(start.getPage(), end.getPage());
int maxPage = Math.max(start.getPage(), end.getPage());
SuraAyah start = SuraAyah.min(this.start, end);
SuraAyah end = SuraAyah.max(this.start, this.end);
// Iterate from beginning to end
for (int i = minPage; i <= maxPage; i++) {
QuranPage fragment = pagerAdapter.getFragmentIfExistsForPage(i);
if (fragment != null) {
Set<String> ayahKeys = QuranInfo.getAyahKeysOnPage(i, start, end);
fragment.getAyahTracker().highlightAyat(i, ayahKeys, HighlightType.SELECTION);
}
}
}
private void showAyahModeHighlights(SuraAyah suraAyah, AyahTracker tracker) {
tracker.highlightAyah(
suraAyah.sura, suraAyah.ayah, HighlightType.SELECTION, false);
}
private void clearAyahModeHighlights() {
if (isInAyahMode) {
for (int i = start.getPage(); i <= end.getPage(); i++) {
QuranPage fragment = pagerAdapter.getFragmentIfExistsForPage(i);
if (fragment != null) {
fragment.getAyahTracker().unHighlightAyahs(HighlightType.SELECTION);
}
}
}
}
private class AyahMenuItemSelectionHandler implements MenuItem.OnMenuItemClickListener {
@Override
public boolean onMenuItemClick(MenuItem item) {
int sliderPage = -1;
if (start == null || end == null) {
return false;
}
switch (item.getItemId()) {
case R.id.cab_bookmark_ayah:
toggleBookmark(start.sura, start.ayah, start.getPage());
break;
case R.id.cab_tag_ayah:
sliderPage = slidingPagerAdapter.getPagePosition(TAG_PAGE);
break;
case R.id.cab_translate_ayah:
sliderPage = slidingPagerAdapter.getPagePosition(TRANSLATION_PAGE);
break;
case R.id.cab_play_from_here:
sliderPage = slidingPagerAdapter.getPagePosition(AUDIO_PAGE);
break;
case R.id.cab_share_ayah_link:
shareAyahLink(start, end);
break;
case R.id.cab_share_ayah_text:
shareAyah(start, end, false);
break;
case R.id.cab_copy_ayah:
shareAyah(start, end, true);
break;
default:
return false;
}
if (sliderPage < 0) {
endAyahMode();
} else {
showSlider(sliderPage);
}
return true;
}
}
private void shareAyah(SuraAyah start, SuraAyah end, final boolean isCopy) {
if (start == null || end == null) {
return;
}
compositeDisposable.add(
arabicDatabaseUtils
.getVerses(start, end)
.filter(quranAyahs -> quranAyahs.size() > 0)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(quranAyahs -> {
if (isCopy) {
ShareUtil.copyVerses(PagerActivity.this, quranAyahs);
} else {
ShareUtil.shareVerses(PagerActivity.this, quranAyahs);
}
}));
}
private void shareAyahLink(SuraAyah start, SuraAyah end) {
showProgressDialog();
compositeDisposable.add(
QuranAppUtils.getQuranAppUrlObservable(getString(R.string.quranapp_key), start, end)
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new DisposableSingleObserver<String>() {
@Override
public void onSuccess(String url) {
ShareUtil.shareViaIntent(PagerActivity.this, url, R.string.share_ayah);
dismissProgressDialog();
}
@Override
public void onError(Throwable e) {
dismissProgressDialog();
}
})
);
}
private void showProgressDialog() {
if (progressDialog == null) {
progressDialog = new ProgressDialog(this);
progressDialog.setIndeterminate(true);
progressDialog.setMessage(getString(R.string.index_loading));
progressDialog.show();
}
}
private void dismissProgressDialog() {
if (progressDialog != null && progressDialog.isShowing()) {
progressDialog.dismiss();
}
progressDialog = null;
}
private void showSlider(int sliderPage) {
ayahToolBar.hideMenu();
slidingPager.setCurrentItem(sliderPage);
slidingPanel.showPane();
// TODO there's got to be a better way than this hack
// The issue is that smoothScrollTo returns if mCanSlide is false
// and it's false when the panel is GONE and showPane only calls
// requestLayout, and only in onLayout does mCanSlide become true.
// So by posting this later it gives time for onLayout to run.
// Another issue is that the fragments haven't been created yet
// (on first run), so calling refreshPages() before then won't work.
handler.post(() -> {
slidingPanel.expandPane();
refreshPages();
});
}
private void updateAyahBookmark(
SuraAyah suraAyah, boolean bookmarked, boolean refreshHighlight) {
// Refresh toolbar icon
if (isInAyahMode && start.equals(suraAyah)) {
ayahToolBar.setBookmarked(bookmarked);
}
// Refresh highlight
if (refreshHighlight && quranSettings.shouldHighlightBookmarks()) {
if (bookmarked) {
highlightAyah(suraAyah.sura, suraAyah.ayah, HighlightType.BOOKMARK);
} else {
unHighlightAyah(suraAyah.sura, suraAyah.ayah, HighlightType.BOOKMARK);
}
}
}
private class SlidingPanelListener implements SlidingUpPanelLayout.PanelSlideListener {
@Override
public void onPanelSlide(View panel, float slideOffset) {
}
@Override
public void onPanelCollapsed(View panel) {
if (isInAyahMode) {
endAyahMode();
}
slidingPanel.hidePane();
}
@Override
public void onPanelExpanded(View panel) {
}
@Override
public void onPanelAnchored(View panel) {
}
}
}