diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 41afab5810..5b01dd7579 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,9 +1,9 @@ + package="com.quran.labs.androidquran" android:versionName="1.4.0" + android:versionCode="6"> + android:debuggable="false"> @@ -14,8 +14,7 @@ - - + diff --git a/res/layout-land/quran_page.xml b/res/layout-land/quran_page.xml deleted file mode 100644 index c1d18da340..0000000000 --- a/res/layout-land/quran_page.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/res/layout-land/quran_page_layout.xml b/res/layout-land/quran_page_layout.xml new file mode 100644 index 0000000000..8e9c9ecb68 --- /dev/null +++ b/res/layout-land/quran_page_layout.xml @@ -0,0 +1,30 @@ + + + + + + + diff --git a/res/layout/quran_exp.xml b/res/layout/quran_exp.xml index e47ad97ba7..2bdac9543c 100644 --- a/res/layout/quran_exp.xml +++ b/res/layout/quran_exp.xml @@ -1,15 +1,31 @@ + + + + + + - - - - diff --git a/res/layout/quran_page_layout.xml b/res/layout/quran_page_layout.xml new file mode 100644 index 0000000000..176cb7a81c --- /dev/null +++ b/res/layout/quran_page_layout.xml @@ -0,0 +1,23 @@ + + + + + diff --git a/res/layout/quran_translation.xml b/res/layout/quran_translation.xml index 11d1820c9a..003991b4f4 100644 --- a/res/layout/quran_translation.xml +++ b/res/layout/quran_translation.xml @@ -1,9 +1,20 @@ - - - \ No newline at end of file + + + + + \ No newline at end of file diff --git a/res/values/attr.xml b/res/values/attr.xml new file mode 100644 index 0000000000..5b04ef3df2 --- /dev/null +++ b/res/values/attr.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml index 7b4b17eb9f..090042ff91 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -4,6 +4,7 @@ Need to download translation! Quran Android Page not found. + Page Loading... Download Required Files? For optimal performance, some files must be downloaded to the sd card. @@ -41,16 +42,17 @@ Feedback quran.android@gmail.com Quran Android is a free, open source Quran - application for Android. The images used are from the quran.com - project, and the data used in the translations and Arabic comes from - tanzil.net. graphics by @somaiagabr. + application for Android. The images used are from the quran.com + project, and the data used in the translations and Arabic comes from + tanzil.net. + \n\nCredits: + \n @wnafee - page flip animation (based on android-page-curl project) + \n @hams_rrr - arabic support for non-arabic phones + \n @neo_4583 - arabic reshaper project + \n @somaiagabr - graphics \n\nNew features in this release: - \n- Enhanced Navigation. - \n- Arabic support (if your device does not support Arabic). - \n- New translations added: French, German, Urdu, Spanish, Indonesian, - Turkish and Malay Translations. - \n- Al Tafsir Al Mouyassar. (Added with translations) - \n- Search translations. (Soon we will introduce Arabic search) + \n- Awesome page flip animation (thanks @wnafee). + \n- Minor bugfixes. \n\nUpcoming features include: \n- Recitation. \nAnd more.. @@ -68,9 +70,9 @@ \n- Adjust translation text size to your preference via Settings. \n- Initial search implementation allows you to search the active translation via the search button. - \n- On tapping the center of the screen and you will find the page bookmarker - on the top left corner. - tap on the bookmarker to toggle the page bookmark state. + \n- On tapping the center of the screen and you will find the page bookmark + option in the top left corner. + tap on the bookmark button to toggle the page bookmark state. \n- You can add as many bookmarks as you\'d like. \n- The application remembers the last page you were reading and displays it when you restart the app. \n- Don\'t forget to check out the settings. diff --git a/src/com/quran/labs/androidquran/ExpViewActivity.java b/src/com/quran/labs/androidquran/ExpViewActivity.java deleted file mode 100644 index 628fb0d13c..0000000000 --- a/src/com/quran/labs/androidquran/ExpViewActivity.java +++ /dev/null @@ -1,213 +0,0 @@ -package com.quran.labs.androidquran; - -import java.lang.ref.SoftReference; -import java.util.HashMap; -import java.util.Map; - -import android.app.ProgressDialog; -import android.content.Context; -import android.graphics.Bitmap; -import android.os.AsyncTask; -import android.os.Bundle; -import android.os.AsyncTask.Status; -import android.util.Log; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.ScrollView; -import android.widget.TextView; -import android.widget.Toast; - -import com.quran.labs.androidquran.common.GestureQuranActivity; -import com.quran.labs.androidquran.common.QuranGalleryAdapter; -import com.quran.labs.androidquran.data.ApplicationConstants; -import com.quran.labs.androidquran.util.QuranSettings; -import com.quran.labs.androidquran.util.QuranUtils; -import com.quran.labs.androidquran.widgets.GalleryFriendlyScrollView; - -public class ExpViewActivity extends GestureQuranActivity { - - private DownloadBitmapTask currentTask; - private ProgressDialog progressDialog; - -// private void adjustLockView() { -// if (QuranSettings.getInstance().isLockOrientation()) { -// btnLockOrientation.setImageResource(R.drawable.lock); -// } else { -// btnLockOrientation.setImageResource(R.drawable.unlock); -// } -// } - - @Override - protected void onCreate(Bundle savedInstanceState) { - Object [] saved = (Object []) getLastNonConfigurationInstance(); - if (saved != null) { - Log.d("exp_v", "Adapter retrieved.."); - currentTask = (DownloadBitmapTask) saved[0]; - galleryAdapter = (QuranGalleryImageAdapter) saved[1]; - } - super.onCreate(savedInstanceState); - progressDialog = new ProgressDialog(this); - } - - @Override - public Object onRetainNonConfigurationInstance() { - Object [] o = {currentTask, galleryAdapter}; - return o; - } - - @Override - protected void onStart() { - super.onStart(); - // Always initialize Quran Screen on start so as to be able to retrieve images - // Error cause: Gallery Adapter was unable to retrieve images from SDCard as QuranScreenInfo - // was cleared after long sleep.. - initializeQuranScreen(); - } - - public class QuranGalleryImageAdapter extends QuranGalleryAdapter { - private Map> cache = - new HashMap>(); - - public QuranGalleryImageAdapter(Context context) { - super(context); - } - - @Override - public void emptyCache() { - super.emptyCache(); - cache.clear(); - } - - public View getView(int position, View convertView, ViewGroup parent) { - PageHolder holder; - if (convertView == null){ - convertView = mInflater.inflate(R.layout.quran_page, null); - holder = new PageHolder(); - holder.page = (ImageView)convertView.findViewById(R.id.pageImageView); - holder.scroll = (GalleryFriendlyScrollView)convertView.findViewById(R.id.pageScrollView); - holder.txtPageNotFound = (TextView)convertView.findViewById(R.id.txtPageNotFound); - convertView.setTag(holder); - } - else { - holder = (PageHolder)convertView.getTag(); - } - - Log.d("exp_v", "position: " + position); - int page = ApplicationConstants.PAGES_LAST - position; - Bitmap bitmap = getBitmap(page); - - if (bitmap == null) { - Log.d("QuranAndroid", "Page not found: " + page); - adjustView(holder, true); - if (currentTask == null || currentTask.getStatus() != Status.RUNNING) { - currentTask = new DownloadBitmapTask(position, page); - connect(); - } - } else { - holder.page.setImageBitmap(bitmap); - adjustView(holder, false); - QuranSettings.getInstance().setLastPage(page); - QuranSettings.save(prefs); - } - - updatePageInfo(position); - adjustBookmarkView(); - return convertView; - } - - private void adjustView(PageHolder holder, boolean pageNotFound) { - if (pageNotFound) { - holder.txtPageNotFound.setVisibility(View.VISIBLE); - holder.page.setVisibility(View.GONE); - } else { - holder.txtPageNotFound.setVisibility(View.GONE); - holder.page.setVisibility(View.VISIBLE); - } - } - - private Bitmap getBitmap(int page) { - Bitmap bitmap = null; - if (cache != null && cache.containsKey("page_" + page)){ - SoftReference bitmapRef = cache.get("page_" + page); - bitmap = bitmapRef.get(); - Log.d("exp_v", "reading image for page " + page + " from cache!"); - } else { - Log.d("exp_v", "loading image for page " + page + " from sdcard"); - } - - // Bitmap not found in cache.. - if (bitmap == null){ - String filename = getPageFileName(page); - bitmap = QuranUtils.getImageFromSD(filename); - // Add Bitmap to cache.. - if (bitmap != null) { - cache.put("page_" + page, new SoftReference(bitmap)); - Log.d("exp_v", "page " + page + " added to cache!"); - } else { - Log.d("exp_v", "page " + page + " not found on sdcard"); - } - } - - return bitmap; - } - } - - static class PageHolder { - TextView txtPageNotFound; - ImageView page; - ScrollView scroll; - } - - @Override - protected void initGalleryAdapter() { - if (galleryAdapter == null) { - Log.d("exp_v", "Adapter instantiated.."); - galleryAdapter = new QuranGalleryImageAdapter(this); - } - } - - @Override - protected void onConnectionSuccess() { - super.onConnectionSuccess(); - if (currentTask != null) - currentTask.execute(); - } - - private class DownloadBitmapTask extends AsyncTask { - private int page; - private boolean downloaded = false; - - public DownloadBitmapTask(int position, int page) { - this.page = page; - } - - protected void onPreExecute() { - if (progressDialog != null) { - progressDialog.setMessage("Downloading page (" + page + ").. Please wait.."); - progressDialog.show(); - } - } - - @Override - public void onPostExecute(Object result) { - if (downloaded) { - if (galleryAdapter != null) - galleryAdapter.notifyDataSetChanged(); - } else { - Toast.makeText(getApplicationContext(), "Error downloading page..", Toast.LENGTH_SHORT); - } - currentTask = null; - if (progressDialog != null) - progressDialog.hide(); - } - - @Override - protected Object doInBackground(Void... arg0) { - Bitmap b = QuranUtils.getImageFromWeb(getPageFileName(page)); - downloaded = b != null; - return null; - } - } - -} diff --git a/src/com/quran/labs/androidquran/QuranViewActivity.java b/src/com/quran/labs/androidquran/QuranViewActivity.java new file mode 100644 index 0000000000..10ae541791 --- /dev/null +++ b/src/com/quran/labs/androidquran/QuranViewActivity.java @@ -0,0 +1,92 @@ +package com.quran.labs.androidquran; + +import android.os.Bundle; +import android.util.Log; +import android.view.Display; +import android.view.Window; +import android.view.WindowManager; + +import com.quran.labs.androidquran.common.PageViewQuranActivity; +import com.quran.labs.androidquran.common.QuranPageFeeder; +import com.quran.labs.androidquran.util.BookmarksManager; +import com.quran.labs.androidquran.util.QuranSettings; + +public class QuranViewActivity extends PageViewQuranActivity { + private static final String TAG = "QuranViewActivity"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + Object [] saved = (Object []) getLastNonConfigurationInstance(); + if (saved != null) { + Log.d("exp_v", "Adapter retrieved.."); + quranPageFeeder = (QuranPageFeeder) saved[0]; + } + super.onCreate(savedInstanceState); + + requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); + requestWindowFeature(Window.FEATURE_NO_TITLE); + + // does requestWindowFeature, has to be before setContentView + adjustDisplaySettings(); + + setContentView(R.layout.quran_exp); + + WindowManager manager = getWindowManager(); + Display display = manager.getDefaultDisplay(); + width = display.getWidth(); + + initComponents(); + BookmarksManager.load(prefs); + + int page = loadPageState(savedInstanceState); + quranPageFeeder.jumpToPage(page); + //renderPage(ApplicationConstants.PAGES_LAST - page); + + toggleMode(); + } + + @Override + public Object onRetainNonConfigurationInstance() { + Object [] o = { quranPageFeeder }; + return o; + } + + @Override + protected void onStart() { + super.onStart(); + // Always initialize Quran Screen on start so as to be able to retrieve images + // Error cause: Gallery Adapter was unable to retrieve images from SDCard as QuranScreenInfo + // was cleared after long sleep.. + initializeQuranScreen(); + } + + protected void initQuranPageFeeder(){ + if (quranPageFeeder == null) { + Log.d(TAG, "Quran Feeder instantiated..."); + quranPageFeeder = new QuranPageFeeder(this, quranPageCurler, R.layout.quran_page_layout); + } else { + quranPageFeeder.setContext(this, quranPageCurler); + } + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + outState.putInt("lastPage", QuranSettings.getInstance().getLastPage()); + super.onSaveInstanceState(outState); + } + + @Override + protected void onResume() { + super.onResume(); + expLayout.setKeepScreenOn(QuranSettings.getInstance().isKeepScreenOn()); + Log.d("QuranAndroid", "Screen on"); + adjustActivityOrientation(); + } + + @Override + protected void onPause() { + super.onPause(); + expLayout.setKeepScreenOn(false); + Log.d("QuranAndroid","Screen off"); + } +} diff --git a/src/com/quran/labs/androidquran/TranslationActivity.java b/src/com/quran/labs/androidquran/TranslationActivity.java index 806e1b3821..ac0f52f9d8 100644 --- a/src/com/quran/labs/androidquran/TranslationActivity.java +++ b/src/com/quran/labs/androidquran/TranslationActivity.java @@ -1,45 +1,75 @@ package com.quran.labs.androidquran; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - import android.app.AlertDialog; -import android.content.Context; import android.content.DialogInterface; import android.content.Intent; -import android.database.Cursor; -import android.database.SQLException; import android.os.Bundle; -import android.text.Html; import android.util.Log; +import android.view.Display; import android.view.Menu; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ScrollView; -import android.widget.TextView; +import android.view.Window; +import android.view.WindowManager; -import com.quran.labs.androidquran.common.GestureQuranActivity; -import com.quran.labs.androidquran.common.QuranGalleryAdapter; +import com.quran.labs.androidquran.common.PageViewQuranActivity; import com.quran.labs.androidquran.common.TranslationItem; +import com.quran.labs.androidquran.common.TranslationPageFeeder; import com.quran.labs.androidquran.common.TranslationsDBAdapter; -import com.quran.labs.androidquran.data.ApplicationConstants; -import com.quran.labs.androidquran.data.DatabaseHandler; -import com.quran.labs.androidquran.data.QuranInfo; -import com.quran.labs.androidquran.util.ArabicStyle; +import com.quran.labs.androidquran.util.BookmarksManager; import com.quran.labs.androidquran.util.QuranSettings; -import com.quran.labs.androidquran.widgets.GalleryFriendlyScrollView; -public class TranslationActivity extends GestureQuranActivity { +public class TranslationActivity extends PageViewQuranActivity { + private static final String TAG = "TranslationViewActivity"; private TranslationsDBAdapter dba; @Override - protected void onCreate(Bundle savedInstanceState) { - dba = new TranslationsDBAdapter(this); + protected void onCreate(Bundle savedInstanceState) { + Object [] saved = (Object []) getLastNonConfigurationInstance(); + if (saved != null) { + Log.d("exp_v", "Adapter retrieved.."); + quranPageFeeder = (TranslationPageFeeder) saved[0]; + } super.onCreate(savedInstanceState); - //checkTranslationAvailability(); + + requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); + requestWindowFeature(Window.FEATURE_NO_TITLE); + + // does requestWindowFeature, has to be before setContentView + adjustDisplaySettings(); + + setContentView(R.layout.quran_exp); + + dba = new TranslationsDBAdapter(this); + checkTranslationAvailability(); + + WindowManager manager = getWindowManager(); + Display display = manager.getDefaultDisplay(); + width = display.getWidth(); + + initComponents(); + BookmarksManager.load(prefs); + + int page = loadPageState(savedInstanceState); + quranPageFeeder.jumpToPage(page); + //renderPage(ApplicationConstants.PAGES_LAST - page); + + toggleMode(); + } + + + @Override + public Object onRetainNonConfigurationInstance() { + Object [] o = { quranPageFeeder }; + return o; + } + + @Override + protected void onStart() { + super.onStart(); + // Always initialize Quran Screen on start so as to be able to retrieve images + // Error cause: Gallery Adapter was unable to retrieve images from SDCard as QuranScreenInfo + // was cleared after long sleep.. + initializeQuranScreen(); } @Override @@ -48,6 +78,39 @@ public boolean onPrepareOptionsMenu(Menu menu) { return super.onPrepareOptionsMenu(menu); } + @Override + protected void initQuranPageFeeder(){ + if (quranPageFeeder == null) { + Log.d(TAG, "Quran Feeder instantiated..."); + quranPageFeeder = new TranslationPageFeeder(this, quranPageCurler, + R.layout.quran_translation, dba); + } else { + quranPageFeeder.setContext(this, quranPageCurler); + } + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + outState.putInt("lastPage", QuranSettings.getInstance().getLastPage()); + super.onSaveInstanceState(outState); + } + + @Override + protected void onResume() { + super.onResume(); + checkTranslationAvailability(); + expLayout.setKeepScreenOn(QuranSettings.getInstance().isKeepScreenOn()); + Log.d("QuranAndroid", "Screen on"); + adjustActivityOrientation(); + } + + @Override + protected void onPause() { + super.onPause(); + expLayout.setKeepScreenOn(false); + Log.d("QuranAndroid","Screen off"); + } + private boolean checkTranslationAvailability() { Log.d("QuranAndroid", "checking translations"); TranslationItem[] translationLists; @@ -67,86 +130,6 @@ private boolean checkTranslationAvailability() { return true; } - @Override - protected void onResume() { - super.onResume(); - checkTranslationAvailability(); - if (galleryAdapter != null) - galleryAdapter.notifyDataSetChanged(); - } - - public String getTranslation(int page){ - if (dba.getActiveTranslation() == null) - return ""; - - Log.d("QuranAndroid", "get translation for page " + page); - String translation = ""; - if ((page > ApplicationConstants.PAGES_LAST) || (page < ApplicationConstants.PAGES_FIRST)) page = 1; - setTitle(QuranInfo.getPageTitle(page)); - - Integer[] bounds = QuranInfo.getPageBounds(page); - TranslationItem[] translationLists = {dba.getActiveTranslation()}; - - List> translations = new ArrayList>(); - for (TranslationItem tl : translationLists){ - Map currentTranslation = getVerses(tl.getFileName(), bounds); - if (currentTranslation != null){ - translations.add(currentTranslation); - } - } - - int numTranslations = translationLists.length; - - int i = bounds[0]; - for (; i <= bounds[2]; i++){ - int j = (i == bounds[0])? bounds[1] : 1; - for (;;){ - int numAdded = 0; - String key = i + ":" + j++; - for (int t = 0; t < numTranslations; t++){ - if (translations.get(t) == null) continue; - String text = translations.get(t).get(key); - if (text != null){ - numAdded++; - translation += "" + key + ": " + text + "
"; - } - } - if (numAdded == 0) break; - } - } - - return translation; - } - - public Map getVerses(String translation, Integer[] bounds){ - DatabaseHandler handler = null; - try { - Map ayahs = new HashMap(); - handler = new DatabaseHandler(translation); - for (int i = bounds[0]; i <= bounds[2]; i++){ - int max = (i == bounds[2])? bounds[3] : QuranInfo.getNumAyahs(i); - int min = (i == bounds[0])? bounds[1] : 1; - Cursor res = handler.getVerses(i, min, max); - if ((res == null) || (!res.moveToFirst())) continue; - do { - int sura = res.getInt(0); - int ayah = res.getInt(1); - String text = res.getString(2); - ayahs.put(sura + ":" + ayah, text); - } - while (res.moveToNext()); - res.close(); - } - handler.closeDatabase(); - return ayahs; - } - catch (SQLException ex){ - ex.printStackTrace(); - if (handler != null) handler.closeDatabase(); - return null; - } - } - public void promptForTranslationDownload(){ AlertDialog.Builder dialog = new AlertDialog.Builder(this); dialog.setMessage(R.string.downloadTranslationPrompt); @@ -172,66 +155,4 @@ public void onClick(DialogInterface dialog, int id) { alert.setTitle(R.string.downloadPrompt_title); alert.show(); } - - public class QuranGalleryTranslationAdapter extends QuranGalleryAdapter { - private Map cache = new HashMap(); - - public QuranGalleryTranslationAdapter(Context context) { - super(context); - } - - @Override - public void emptyCache() { - super.emptyCache(); - cache.clear(); - } - - public View getView(int position, View convertView, ViewGroup parent) { - PageHolder holder; - if (convertView == null){ - convertView = mInflater.inflate(R.layout.quran_translation, null); - holder = new PageHolder(); - holder.page = (TextView)convertView.findViewById(R.id.translationText); - holder.page.setTypeface(ArabicStyle.getTypeface()); - holder.scroll = (GalleryFriendlyScrollView)convertView.findViewById(R.id.pageScrollView); - convertView.setTag(holder); - } - else { - holder = (PageHolder)convertView.getTag(); - } - - int page = ApplicationConstants.PAGES_LAST - position; - String str = null; - if (cache.containsKey("page_" + page)){ - str = cache.get("page_" + page); - Log.d("exp_v", "reading translation for page " + page + " from cache!"); - } - - if (str == null){ - str = getTranslation(page); - if (str != null && !"".equals(str)) - cache.put("page_" + page, str); - } - - holder.page.setText(Html.fromHtml(ArabicStyle.reshape(str))); - holder.page.setTextSize(QuranSettings.getInstance().getTranslationTextSize()); - QuranSettings.getInstance().setLastPage(page); - QuranSettings.save(prefs); - - if (!inReadingMode) - updatePageInfo(position); - return convertView; - } - } - - static class PageHolder { - TextView page; - ScrollView scroll; - } - - @Override - protected void initGalleryAdapter() { - galleryAdapter = new QuranGalleryTranslationAdapter(this); - } - } diff --git a/src/com/quran/labs/androidquran/common/BaseQuranActivity.java b/src/com/quran/labs/androidquran/common/BaseQuranActivity.java index e74314d920..79a0d5e9ff 100644 --- a/src/com/quran/labs/androidquran/common/BaseQuranActivity.java +++ b/src/com/quran/labs/androidquran/common/BaseQuranActivity.java @@ -1,11 +1,13 @@ package com.quran.labs.androidquran.common; +import java.text.NumberFormat; + import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; +import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnCancelListener; -import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.net.ConnectivityManager; @@ -21,10 +23,10 @@ import com.quran.labs.androidquran.AboutUsActivity; import com.quran.labs.androidquran.BookmarksActivity; import com.quran.labs.androidquran.DownloadActivity; -import com.quran.labs.androidquran.ExpViewActivity; import com.quran.labs.androidquran.HelpActivity; import com.quran.labs.androidquran.QuranJumpDialog; import com.quran.labs.androidquran.QuranPreferenceActivity; +import com.quran.labs.androidquran.QuranViewActivity; import com.quran.labs.androidquran.R; import com.quran.labs.androidquran.TranslationActivity; import com.quran.labs.androidquran.data.ApplicationConstants; @@ -38,7 +40,6 @@ public abstract class BaseQuranActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { - // TODO Auto-generated method stub super.onCreate(savedInstanceState); prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); } @@ -120,7 +121,7 @@ public boolean onMenuItemSelected(int featureId, MenuItem item) { } public void jumpTo(int page) { - Intent i = new Intent(this, ExpViewActivity.class); + Intent i = new Intent(this, QuranViewActivity.class); i.putExtra("page", page); startActivityForResult(i, ApplicationConstants.QURAN_VIEW_CODE); } @@ -140,7 +141,6 @@ protected void connect() { } protected void onConnectionSuccess() { - } protected void onConnectionFailed() { @@ -162,6 +162,12 @@ public void onClick(DialogInterface dialog, int id) { alert.show(); } + public String getPageFileName(int p) { + NumberFormat nf = NumberFormat.getInstance(); + nf.setMinimumIntegerDigits(3); + return "page" + nf.format(p) + ".png"; + } + @Override protected void onResume() { super.onResume(); diff --git a/src/com/quran/labs/androidquran/common/GestureQuranActivity.java b/src/com/quran/labs/androidquran/common/GestureQuranActivity.java deleted file mode 100644 index 89a9d2382b..0000000000 --- a/src/com/quran/labs/androidquran/common/GestureQuranActivity.java +++ /dev/null @@ -1,337 +0,0 @@ -package com.quran.labs.androidquran.common; - -import java.text.NumberFormat; - -import android.content.pm.ActivityInfo; -import android.os.Bundle; -import android.util.Log; -import android.view.Display; -import android.view.GestureDetector; -import android.view.KeyEvent; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.view.Window; -import android.view.WindowManager; -import android.view.GestureDetector.SimpleOnGestureListener; -import android.view.View.OnClickListener; -import android.widget.ImageView; -import android.widget.SeekBar; -import android.widget.TextView; - -import com.quran.labs.androidquran.R; -import com.quran.labs.androidquran.data.ApplicationConstants; -import com.quran.labs.androidquran.data.QuranInfo; -import com.quran.labs.androidquran.util.ArabicStyle; -import com.quran.labs.androidquran.util.BookmarksManager; -import com.quran.labs.androidquran.util.QuranSettings; -import com.quran.labs.androidquran.widgets.QuranGallery; - -public abstract class GestureQuranActivity extends BaseQuranActivity implements INavigatorListener { - protected GestureDetector gestureDetector; - - protected static final int SWIPE_MIN_DISTANCE = 120; - protected static final int SWIPE_MAX_OFF_PATH = 250; - protected static final int SWIPE_THRESHOLD_VELOCITY = 200; - protected static final int BOOKMARK_SAFE_REGION = 15; - - protected ImageView btnBookmark = null; - protected boolean inReadingMode = false; - protected QuranGallery gallery = null; - protected SeekBar seekBar = null; - protected TextView titleText = null; - protected ViewGroup expLayout = null; - protected int width = 0; - protected QuranGalleryAdapter galleryAdapter = null; - - @Override - protected void onCreate(Bundle savedInstanceState){ - super.onCreate(savedInstanceState); - requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); - requestWindowFeature(Window.FEATURE_NO_TITLE); - - // does requestWindowFeature, has to be before setContentView - adjustDisplaySettings(); - - setContentView(R.layout.quran_exp); - - WindowManager manager = getWindowManager(); - Display display = manager.getDefaultDisplay(); - width = display.getWidth(); - - initComponents(); - - gestureDetector = new GestureDetector(new QuranGestureDetector()); - - BookmarksManager.load(prefs); - - int page = loadPageState(savedInstanceState); - renderPage(ApplicationConstants.PAGES_LAST - page); - - toggleMode(); - } - - @Override - public void onLowMemory() { - super.onLowMemory(); - galleryAdapter.emptyCache(); - } - - protected abstract void initGalleryAdapter(); - - protected void initComponents() { - initGalleryAdapter(); - expLayout = (ViewGroup) findViewById(R.id.expLayout); - - gallery = (QuranGallery) findViewById(R.id.gallery); - gallery.setAdapter(galleryAdapter); - gallery.setAnimationDuration(0); - gallery.setSpacing(25); - gallery.setNavigatorListener(this); - - titleText = (TextView) findViewById(R.id.pagetitle); - titleText.setTypeface(ArabicStyle.getTypeface()); - //toolbar = (View) findViewById(R.id.toolbar); -// btnLockOrientation = (ImageView) findViewById(R.id.btnLockOrientation); -// btnLockOrientation.setOnClickListener(new OnClickListener() { -// public void onClick(View v) { -// QuranSettings qs = QuranSettings.getInstance(); -// qs.setLockOrientation(!qs.isLockOrientation()); -// QuranSettings.save(prefs); -// adjustLockView(); -// } -// }); - - btnBookmark = (ImageView) findViewById(R.id.btnBookmark); - btnBookmark.setOnClickListener(new OnClickListener() { - public void onClick(View v) { - BookmarksManager.toggleBookmarkState(QuranSettings.getInstance().getLastPage(), prefs); - adjustBookmarkView(); - } - }); - - seekBar = (SeekBar) findViewById(R.id.suraSeek); - seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { - @Override - public void onProgressChanged(SeekBar seekBar, int progress, - boolean fromUser) { - if (fromUser) - titleText.setText(ArabicStyle.reshape(QuranInfo.getPageTitle(ApplicationConstants.PAGES_LAST - progress))); - } - - @Override - public void onStartTrackingTouch(SeekBar seekBar) { - } - - @Override - public void onStopTrackingTouch(SeekBar seekBar) { - if (seekBar.getProgress() != gallery.getSelectedItemPosition()) { - renderPage(seekBar.getProgress()); - } - } - }); - } - - protected void goToNextPage() { - int position = gallery.getSelectedItemPosition(); - if (position > 0) - renderPage(position - 1); - } - - protected void goToPreviousPage() { - int position = gallery.getSelectedItemPosition(); - if (position < ApplicationConstants.PAGES_LAST - 1) - renderPage(position + 1); - } - - protected void renderPage(int position){ - if (position < ApplicationConstants.PAGES_LAST) { - gallery.setSelection(position, true); - adjustBookmarkView(); - updatePageInfo(position); - } - } - - protected void adjustBookmarkView() { - if (BookmarksManager.getInstance().contains(QuranSettings.getInstance().getLastPage())) { - btnBookmark.setImageResource(R.drawable.bookmarks); - } else { - btnBookmark.setImageResource(R.drawable.remove_bookmark); - } - } - - @Override - public boolean onKeyDown(int keyCode, KeyEvent event) { - Log.d("QuranAndroid", "KeyDown"); - if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT){ - goToNextPage(); - } - else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT){ - goToPreviousPage(); - } - - return super.onKeyDown(keyCode, event); - } - - // thanks to codeshogun's blog post for this - // http://www.codeshogun.com/blog/2009/04/16/how-to-implement-swipe-action-in-android/ - public class QuranGestureDetector extends SimpleOnGestureListener { - @Override - public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, - float velocityY) { - return false; - } - - @Override - public boolean onDoubleTap(MotionEvent e){ - return handleDoubleTap(e); - } - - @Override - public boolean onSingleTapConfirmed(MotionEvent e){ - return handleSingleTap(e); - } - } - - public boolean handleDoubleTap(MotionEvent e) { - return false; - } - - @Override - public boolean onTouchEvent(MotionEvent event){ - return gestureDetector.onTouchEvent(event); - } - - // this function lets this activity handle the touch event before the ScrollView - @Override - public boolean dispatchTouchEvent(MotionEvent event){ - super.dispatchTouchEvent(event); - return gestureDetector.onTouchEvent(event); - } - - protected void adjustActivityOrientation() { - if (QuranSettings.getInstance().isLockOrientation()) { - if (QuranSettings.getInstance().isLandscapeOrientation()) - setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); - else - setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); - } else { - setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); - } - } - - protected void adjustDisplaySettings() { - if (QuranSettings.getInstance().isFullScreen()) { - requestWindowFeature(Window.FEATURE_NO_TITLE); - if (!QuranSettings.getInstance().isShowClock()) { - getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, - WindowManager.LayoutParams.FLAG_FULLSCREEN); - } - } - - adjustActivityOrientation(); - } - - protected String getPageFileName(int p) { - NumberFormat nf = NumberFormat.getInstance(); - nf.setMinimumIntegerDigits(3); - return "page" + nf.format(p) + ".png"; - } - - protected int loadPageState(Bundle savedInstanceState){ - int page = savedInstanceState != null ? savedInstanceState.getInt("page") : ApplicationConstants.PAGES_FIRST; - if (page == ApplicationConstants.PAGES_FIRST){ - Bundle extras = getIntent().getExtras(); - page = extras != null? extras.getInt("page") : QuranSettings.getInstance().getLastPage(); - } else if (page == ApplicationConstants.NO_PAGE_SAVED) { - page = ApplicationConstants.PAGES_FIRST; - } - - return page; - } - - public boolean handleSingleTap(MotionEvent e){ - Log.d("exp_v", "in handle single tap"); - int sliceWidth = (int)(0.2 * width); - - // Skip bookmark region - int bookmarkRegionY = btnBookmark.getTop() + btnBookmark.getHeight() + BOOKMARK_SAFE_REGION; - int bookmarkRegionX = btnBookmark.getLeft() + btnBookmark.getWidth() + BOOKMARK_SAFE_REGION; - if (e.getY() < bookmarkRegionY && e.getX() < bookmarkRegionX) - return true; - - if (e.getX() < sliceWidth) - goToNextPage(); - else if (e.getX() > (width - sliceWidth)) - goToPreviousPage(); - else toggleMode(); - - return true; - } - - @Override - protected void onResume() { - super.onResume(); - expLayout.setKeepScreenOn(QuranSettings.getInstance().isKeepScreenOn()); - Log.d("QuranAndroid", "Screen on"); - adjustActivityOrientation(); - //currentPage = QuranSettings.getInstance().getLastPage(); - //renderPage(ApplicationConstants.PAGES_LAST - currentPage); - } - - @Override - protected void onPause() { - super.onPause(); - expLayout.setKeepScreenOn(false); - Log.d("QuranAndroid","Screen off"); - } - - protected void updatePageInfo(int position){ - Log.d("QuranAndroid", "Update page info: " + position); - titleText.setText(ArabicStyle.reshape(QuranInfo.getPageTitle(ApplicationConstants.PAGES_LAST - position))); - seekBar.setProgress(position); - } - - private void updatePageInfo() { - updatePageInfo(gallery.getSelectedItemPosition()); - } - - protected void toggleMode(){ - Log.d("exp_v", "in toggle mode"); - if (inReadingMode){ - getWindow().addFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); - getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); - - //toolbar.setVisibility(View.VISIBLE); - seekBar.setVisibility(TextView.VISIBLE); - titleText.setVisibility(TextView.VISIBLE); - //btnLockOrientation.setVisibility(View.VISIBLE); - btnBookmark.setVisibility(View.VISIBLE); - //adjustLockView(); - adjustBookmarkView(); - updatePageInfo(); - } - else { - getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); - getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); - - //toolbar.setVisibility(View.INVISIBLE); - seekBar.setVisibility(TextView.INVISIBLE); - titleText.setVisibility(TextView.INVISIBLE); - //btnLockOrientation.setVisibility(View.INVISIBLE); - btnBookmark.setVisibility(View.INVISIBLE); - } - - inReadingMode = !inReadingMode; - } - - @Override - public void onLeftKey() { - goToNextPage(); - } - - @Override - public void onRightKey() { - goToPreviousPage(); - } -} diff --git a/src/com/quran/labs/androidquran/common/INavigatorListener.java b/src/com/quran/labs/androidquran/common/INavigatorListener.java deleted file mode 100644 index 88d6fd4928..0000000000 --- a/src/com/quran/labs/androidquran/common/INavigatorListener.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.quran.labs.androidquran.common; - -public interface INavigatorListener { - - public void onRightKey(); - public void onLeftKey(); - -} diff --git a/src/com/quran/labs/androidquran/common/PageViewQuranActivity.java b/src/com/quran/labs/androidquran/common/PageViewQuranActivity.java new file mode 100644 index 0000000000..a8d892bea1 --- /dev/null +++ b/src/com/quran/labs/androidquran/common/PageViewQuranActivity.java @@ -0,0 +1,221 @@ +package com.quran.labs.androidquran.common; + +import android.content.pm.ActivityInfo; +import android.os.Bundle; +import android.util.Log; +import android.view.KeyEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowManager; +import android.view.View.OnClickListener; +import android.widget.ImageView; +import android.widget.SeekBar; +import android.widget.TextView; + +import com.quran.labs.androidquran.R; +import com.quran.labs.androidquran.data.ApplicationConstants; +import com.quran.labs.androidquran.data.QuranInfo; +import com.quran.labs.androidquran.util.ArabicStyle; +import com.quran.labs.androidquran.util.BookmarksManager; +import com.quran.labs.androidquran.util.QuranSettings; +import com.quran.labs.androidquran.widgets.QuranPageCurlView; + +public abstract class PageViewQuranActivity extends BaseQuranActivity { + private static final String TAG = "BaseQuranActivity"; + + protected ImageView btnBookmark = null; + protected boolean inReadingMode = false; + protected SeekBar seekBar = null; + protected TextView titleText = null; + protected ViewGroup expLayout = null; + protected int width = 0; + protected QuranPageCurlView quranPageCurler = null; + protected QuranPageFeeder quranPageFeeder; + + protected abstract void initQuranPageFeeder(); + + protected void initComponents() { + expLayout = (ViewGroup) findViewById(R.id.expLayout); + + if (quranPageCurler == null){ + quranPageCurler = (QuranPageCurlView)findViewById(R.id.gallery); + quranPageCurler.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + toggleMode(); + } + }); + } + + initQuranPageFeeder(); + + titleText = (TextView) findViewById(R.id.pagetitle); + titleText.setTypeface(ArabicStyle.getTypeface()); + + btnBookmark = (ImageView) findViewById(R.id.btnBookmark); + btnBookmark.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + BookmarksManager.toggleBookmarkState( + QuranSettings.getInstance().getLastPage(), prefs); + adjustBookmarkView(); + } + }); + + seekBar = (SeekBar) findViewById(R.id.suraSeek); + seekBar.setOnSeekBarChangeListener( + new SeekBar.OnSeekBarChangeListener(){ + @Override + public void onProgressChanged(SeekBar seekBar, int progress, + boolean fromUser) { + if (fromUser) + titleText.setText(ArabicStyle.reshape( + QuranInfo.getPageTitle( + ApplicationConstants.PAGES_LAST - progress))); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + if (seekBar.getProgress() != + quranPageFeeder.getCurrentPagePosition()) { + quranPageFeeder.jumpToPage( + ApplicationConstants.PAGES_LAST - + seekBar.getProgress()); + } + } + }); + } + + protected void goToNextPage() { + quranPageFeeder.goToNextpage(); + } + + protected void goToPreviousPage() { + quranPageFeeder.goToPreviousPage(); + } + + protected void scrollPageDown() { + quranPageFeeder.ScrollDown(); + } + + protected void scrollPageUp() { + quranPageFeeder.ScrollUp(); + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + Log.d(TAG, "KeyDown"); + if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT){ + goToNextPage(); + } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT){ + goToPreviousPage(); + } else if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) { + scrollPageDown(); + } else if (keyCode == KeyEvent.KEYCODE_DPAD_UP){ + scrollPageUp(); + } + + return super.onKeyDown(keyCode, event); + } + + protected void adjustActivityOrientation() { + if (QuranSettings.getInstance().isLockOrientation()) { + // TODO - don't call setRequestedOrientation here unless we are + // in the wrong orientation... + if (QuranSettings.getInstance().isLandscapeOrientation()) + setRequestedOrientation( + ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); + else + setRequestedOrientation( + ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); + } + } + + protected void adjustBookmarkView() { + if (BookmarksManager.getInstance().contains( + QuranSettings.getInstance().getLastPage())) { + btnBookmark.setImageResource(R.drawable.bookmarks); + } else { + btnBookmark.setImageResource(R.drawable.remove_bookmark); + } + } + + protected void adjustDisplaySettings() { + if (QuranSettings.getInstance().isFullScreen()) { + requestWindowFeature(Window.FEATURE_NO_TITLE); + if (!QuranSettings.getInstance().isShowClock()) { + getWindow().setFlags( + WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN); + } + } + } + + protected int loadPageState(Bundle savedInstanceState){ + int page = savedInstanceState != null ? + savedInstanceState.getInt("lastPage") : + ApplicationConstants.NO_PAGE_SAVED; + + if (page == ApplicationConstants.NO_PAGE_SAVED){ + Bundle extras = getIntent().getExtras(); + page = extras != null? extras.getInt("page") : + QuranSettings.getInstance().getLastPage(); + + // If still no page saved + if (page == ApplicationConstants.NO_PAGE_SAVED) { + page = ApplicationConstants.PAGES_FIRST; + } + } + + Log.d(TAG, "page: "+ page+" fyi: "+ + QuranSettings.getInstance().getLastPage()); + return page; + } + + protected void toggleMode(){ + Log.d("exp_v", "in toggle mode"); + if (inReadingMode){ + getWindow().addFlags( + WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); + getWindow().clearFlags( + WindowManager.LayoutParams.FLAG_FULLSCREEN); + + seekBar.setVisibility(TextView.VISIBLE); + titleText.setVisibility(TextView.VISIBLE); + btnBookmark.setVisibility(View.VISIBLE); + adjustBookmarkView(); + updatePageInfo(); + } + else { + getWindow().addFlags( + WindowManager.LayoutParams.FLAG_FULLSCREEN); + getWindow().clearFlags( + WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); + + seekBar.setVisibility(TextView.INVISIBLE); + titleText.setVisibility(TextView.INVISIBLE); + btnBookmark.setVisibility(View.INVISIBLE); + } + + inReadingMode = !inReadingMode; + } + + + protected void updatePageInfo(int position){ + Log.d(TAG, "Update page info: " + position); + titleText.setText(ArabicStyle.reshape( + QuranInfo.getPageTitle(position))); + seekBar.setProgress(ApplicationConstants.PAGES_LAST - position); + } + + private void updatePageInfo() { + updatePageInfo(quranPageFeeder.getCurrentPagePosition()); + QuranSettings.getInstance().setLastPage( + quranPageFeeder.getCurrentPagePosition()); + } +} diff --git a/src/com/quran/labs/androidquran/common/QuranGalleryAdapter.java b/src/com/quran/labs/androidquran/common/QuranGalleryAdapter.java deleted file mode 100644 index 32fbf62964..0000000000 --- a/src/com/quran/labs/androidquran/common/QuranGalleryAdapter.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.quran.labs.androidquran.common; - -import com.quran.labs.androidquran.data.ApplicationConstants; - -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; - -public abstract class QuranGalleryAdapter extends BaseAdapter { - - protected Context context; - protected LayoutInflater mInflater; - - public QuranGalleryAdapter(Context context) { - this.context = context; - mInflater = LayoutInflater.from(this.context); - } - - public int getCount() { - return ApplicationConstants.PAGES_LAST; - } - - public Object getItem(int position) { - return ApplicationConstants.PAGES_LAST - 1 - position; - } - - public long getItemId(int position) { - return ApplicationConstants.PAGES_LAST - 1 - position; - } - - public abstract View getView(int position, View convertView, ViewGroup parent); - public void emptyCache() {}; -} diff --git a/src/com/quran/labs/androidquran/common/QuranPageFeeder.java b/src/com/quran/labs/androidquran/common/QuranPageFeeder.java new file mode 100644 index 0000000000..99a19a1124 --- /dev/null +++ b/src/com/quran/labs/androidquran/common/QuranPageFeeder.java @@ -0,0 +1,278 @@ +package com.quran.labs.androidquran.common; + +import java.lang.ref.SoftReference; +import java.util.HashMap; +import java.util.Map; + +import android.app.Activity; +import android.graphics.Bitmap; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.ImageView; +import android.widget.ScrollView; +import android.widget.TextView; + +import com.quran.labs.androidquran.R; +import com.quran.labs.androidquran.data.ApplicationConstants; +import com.quran.labs.androidquran.util.QuranSettings; +import com.quran.labs.androidquran.util.QuranUtils; +import com.quran.labs.androidquran.widgets.QuranPageCurlView; +import com.quran.labs.androidquran.widgets.QuranPageCurlView.OnPageFlipListener; + + +/** + * + * @author wnafee + * + * This class takes care of loading pages into the mushaf when needed. + * + * If the image is in the SD or cache then it retrieves it, otherwise, it downloads + * the page from the server. As the pages are being flipped, it keeps the page stack + * filled to allow for proper animation from the user's perspective + * + */ +public class QuranPageFeeder implements OnPageFlipListener { + + private static final String TAG = "QuranPageFeeder"; + + private Map> cache = + new HashMap>(); + + protected PageViewQuranActivity mContext; + protected QuranPageCurlView mQuranPage; + + protected LayoutInflater mInflater; + protected int mPageLayout; + protected int mCurrentPageNumber; + + public QuranPageFeeder(PageViewQuranActivity context, + QuranPageCurlView quranPage, int page_layout) { + mContext = context; + mInflater = LayoutInflater.from(context); + mPageLayout = page_layout; + mQuranPage = quranPage; + mQuranPage.setOnPageFlipListener(this); + mCurrentPageNumber = 0; + } + + public int getCurrentPagePosition() { + return mCurrentPageNumber; + } + + public void jumpToPage(int page){ + if (page <= ApplicationConstants.PAGES_FIRST) { + page = ApplicationConstants.PAGES_FIRST; + mQuranPage.addNextPage(null); + mQuranPage.addNextPage(createPage(page)); + mQuranPage.addNextPage(createPage(page+1)); + } else if (page >= ApplicationConstants.PAGES_LAST){ + page = ApplicationConstants.PAGES_LAST; + mQuranPage.addPreviousPage(null); + mQuranPage.addPreviousPage(createPage(page)); + mQuranPage.addPreviousPage(createPage(page-1)); + } else { + mQuranPage.addNextPage(createPage(page-1)); + mQuranPage.addNextPage(createPage(page)); + mQuranPage.addNextPage(createPage(page+1)); + } + + mCurrentPageNumber = page; + + mQuranPage.refresh(true); + } + + public void goToNextpage() { + mQuranPage.doPageFlip(OnPageFlipListener.NEXT_PAGE); + /* Keeping old code in case new solution is too buggy + if (mCurrentPageNumber+1 > ApplicationConstants.PAGES_LAST) + return; + loadNextPage(mQuranPage); + mQuranPage.refresh(true); + */ + } + + public void goToPreviousPage() { + mQuranPage.doPageFlip(OnPageFlipListener.PREVIOUS_PAGE); + /* Keeping old code in case new solution is too buggy + if (mCurrentPageNumber-1 < ApplicationConstants.PAGES_FIRST) + return; + loadPreviousPage(mQuranPage); + mQuranPage.refresh(true); + */ + } + + public void refreshCurrent() { + jumpToPage(mCurrentPageNumber); + } + + public void ScrollDown() { + mQuranPage.scrollPage(R.id.page_scroller, View.FOCUS_DOWN); + } + + public void ScrollUp() { + mQuranPage.scrollPage(R.id.page_scroller, View.FOCUS_UP); + } + + @Override + public void onPageFlipBegin(QuranPageCurlView pageView, int flipDirection){ + // Does nothing + } + + @Override + public void onPageFlipEnd(QuranPageCurlView pageView, int flipDirection) { + if (flipDirection == OnPageFlipListener.NEXT_PAGE){ + loadNextPage(pageView); + } else if (flipDirection == OnPageFlipListener.PREVIOUS_PAGE){ + loadPreviousPage(pageView); + } + } + + public int loadNextPage(QuranPageCurlView pageView) { + mCurrentPageNumber += 1; + if (mCurrentPageNumber < ApplicationConstants.PAGES_LAST){ + Log.d(TAG, "Adding Next Page: " + (mCurrentPageNumber+1)); + View v = createPage(mCurrentPageNumber+1); + pageView.addNextPage(v); + mContext.updatePageInfo(mCurrentPageNumber); + } else { + pageView.addNextPage((View)null); + // add empty page to prevent coming here again + } + return mCurrentPageNumber; + } + + public int loadPreviousPage(QuranPageCurlView pageView){ + mCurrentPageNumber -= 1; + if (mCurrentPageNumber > ApplicationConstants.PAGES_FIRST ) { + Log.d(TAG, "Adding Previous Page: " + (mCurrentPageNumber-1)); + View v = createPage(mCurrentPageNumber-1); + pageView.addPreviousPage(v); + mContext.updatePageInfo(mCurrentPageNumber); + } else { + pageView.addPreviousPage((View)null); + // add empty page to prevent coming here again + } + return mCurrentPageNumber; + } + + protected View createPage(int index) { + View v = mInflater.inflate(mPageLayout, null); + + ScrollView sv = (ScrollView)v.findViewById(R.id.page_scroller); + if (sv == null) + v.setTag(new Boolean(false)); + else + v.setTag(new Boolean(true)); + + updateViewForUser(v, true, false); + + // Get page on different thread + new PageRetriever(v, index).start(); + + return v; + } + + protected void updateViewForUser(View v, boolean loading, + boolean pageNotFound){ + TextView tv = (TextView)v.findViewById(R.id.txtPageNotFound); + ImageView iv = (ImageView)v.findViewById(R.id.page_image); + if ((loading) || (pageNotFound)){ + tv.setText(loading? R.string.pageLoading : R.string.pageNotFound); + tv.setVisibility(View.VISIBLE); + iv.setVisibility(View.GONE); + } + else { + tv.setVisibility(View.GONE); + iv.setVisibility(View.VISIBLE); + } + } + + private Bitmap getBitmap(int page) { + Bitmap bitmap = null; + if (cache != null && cache.containsKey("page_" + page)){ + SoftReference bitmapRef = cache.get("page_" + page); + bitmap = bitmapRef.get(); + Log.d(TAG, "reading image for page " + page + " from cache!"); + } else { + Log.d(TAG, "loading image for page " + page + " from sdcard"); + } + + // Bitmap not found in cache.. + if (bitmap == null){ + String filename = mContext.getPageFileName(page); + bitmap = QuranUtils.getImageFromSD(filename); + // Add Bitmap to cache.. + if (bitmap != null) { + cache.put("page_" + page, new SoftReference(bitmap)); + Log.d(TAG, "page " + page + " added to cache!"); + } else { + Log.d(TAG, "page " + page + " not found on sdcard"); + bitmap = QuranUtils.getImageFromWeb(filename); + if (bitmap != null) + cache.put("page_" + page, new SoftReference(bitmap)); + else Log.d(TAG, "page " + page + " could not be fetched from the web"); + } + } + + return bitmap; + } + + public void setContext(PageViewQuranActivity context, + QuranPageCurlView quranPage) { + mContext = context; + mInflater = LayoutInflater.from(context); + mQuranPage = quranPage; + mQuranPage.setOnPageFlipListener(this); + } + + private class PageRetriever extends Thread { + View v; + int index; + + public PageRetriever(View v, int index){ + this.index = index; + this.v = v; + } + + @Override + public void run() { + Bitmap bitmap = getBitmap(index); + ((Activity)mContext).runOnUiThread(new PageDisplayer(bitmap, v, index)); + + //clear for GC + v = null; + } + } + + class PageDisplayer implements Runnable { + private Bitmap bitmap; + private View v; + private int index; + + public PageDisplayer(Bitmap bitmap, View v, int index){ + this.bitmap = bitmap; + this.v = v; + this.index = index; + } + + public void run(){ + ImageView iv = (ImageView)v.findViewById(R.id.page_image); + if (bitmap == null) { + Log.d(TAG, "Page not found: " + index); + updateViewForUser(v, false, true); + } else { + iv.setImageBitmap(bitmap); + mQuranPage.refresh(); + updateViewForUser(v, false, false); + QuranSettings.getInstance().setLastPage(mCurrentPageNumber); + QuranSettings.save(mContext.prefs); + } + + //clear for GC + iv = null; + bitmap = null; + v = null; + } + } +} diff --git a/src/com/quran/labs/androidquran/common/TranslationPageFeeder.java b/src/com/quran/labs/androidquran/common/TranslationPageFeeder.java new file mode 100644 index 0000000000..f3b26df1b6 --- /dev/null +++ b/src/com/quran/labs/androidquran/common/TranslationPageFeeder.java @@ -0,0 +1,142 @@ +package com.quran.labs.androidquran.common; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import android.database.Cursor; +import android.database.SQLException; +import android.text.Html; +import android.util.Log; +import android.view.View; +import android.widget.ScrollView; +import android.widget.TextView; + +import com.quran.labs.androidquran.R; +import com.quran.labs.androidquran.TranslationActivity; +import com.quran.labs.androidquran.data.DatabaseHandler; +import com.quran.labs.androidquran.data.QuranInfo; +import com.quran.labs.androidquran.util.ArabicStyle; +import com.quran.labs.androidquran.util.QuranSettings; +import com.quran.labs.androidquran.widgets.QuranPageCurlView; +import com.quran.labs.androidquran.widgets.QuranPageCurlView.OnPageFlipListener; + +public class TranslationPageFeeder extends QuranPageFeeder + implements OnPageFlipListener { + + private static final String TAG = "TranslationPageFeeder"; + + private TranslationsDBAdapter dba; + private Map cache = new HashMap(); + + public TranslationPageFeeder(TranslationActivity context, + QuranPageCurlView quranPage, int page_layout, + TranslationsDBAdapter dba) { + super(context, quranPage, page_layout); + this.dba = dba; + } + + @Override + protected View createPage(int index) { + View v = mInflater.inflate(mPageLayout, null); + + ScrollView sv = (ScrollView)v.findViewById(R.id.page_scroller); + if (sv == null) + v.setTag(new Boolean(false)); + else + v.setTag(new Boolean(true)); + + String text = null; + if (cache.containsKey("page_" + index)) + text = cache.get("page_" + index); + if (text == null){ + text = getTranslation(index); + if (text != null && !"".equals(text)) + cache.put("page_" + index, text); + } + + TextView tv = (TextView)v.findViewById(R.id.translationText); + tv.setText(Html.fromHtml(ArabicStyle.reshape(text))); + tv.setTextSize(QuranSettings.getInstance().getTranslationTextSize()); + + updateViewForUser(v, false, false); + return v; + } + + @Override + protected void updateViewForUser(View v, boolean loading, + boolean pageNotFound){ + TextView tv = (TextView)v.findViewById(R.id.translationText); + Log.d(TAG, "text: " + tv.getText().toString()); + tv.setVisibility(View.VISIBLE); + } + + private String getTranslation(int page){ + if (dba.getActiveTranslation() == null) + return ""; + + String translation = ""; + Integer[] bounds = QuranInfo.getPageBounds(page); + TranslationItem[] translationLists = {dba.getActiveTranslation()}; + + List> translations = new ArrayList>(); + for (TranslationItem tl : translationLists){ + Map currentTranslation = getVerses(tl.getFileName(), bounds); + if (currentTranslation != null){ + translations.add(currentTranslation); + } + } + + int numTranslations = translationLists.length; + + int i = bounds[0]; + for (; i <= bounds[2]; i++){ + int j = (i == bounds[0])? bounds[1] : 1; + for (;;){ + int numAdded = 0; + String key = i + ":" + j++; + for (int t = 0; t < numTranslations; t++){ + if (translations.get(t) == null) continue; + String text = translations.get(t).get(key); + if (text != null){ + numAdded++; + translation += "" + key + ": " + text + "
"; + } + } + if (numAdded == 0) break; + } + } + + return translation; + } + + private Map getVerses(String translation, Integer[] bounds){ + DatabaseHandler handler = null; + try { + Map ayahs = new HashMap(); + handler = new DatabaseHandler(translation); + for (int i = bounds[0]; i <= bounds[2]; i++){ + int max = (i == bounds[2])? bounds[3] : QuranInfo.getNumAyahs(i); + int min = (i == bounds[0])? bounds[1] : 1; + Cursor res = handler.getVerses(i, min, max); + if ((res == null) || (!res.moveToFirst())) continue; + do { + int sura = res.getInt(0); + int ayah = res.getInt(1); + String text = res.getString(2); + ayahs.put(sura + ":" + ayah, text); + } + while (res.moveToNext()); + res.close(); + } + handler.closeDatabase(); + return ayahs; + } + catch (SQLException ex){ + ex.printStackTrace(); + if (handler != null) handler.closeDatabase(); + return null; + } + } +} diff --git a/src/com/quran/labs/androidquran/widgets/GalleryFriendlyScrollView.java b/src/com/quran/labs/androidquran/widgets/GalleryFriendlyScrollView.java deleted file mode 100644 index 8561058bb9..0000000000 --- a/src/com/quran/labs/androidquran/widgets/GalleryFriendlyScrollView.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.quran.labs.androidquran.widgets; - -import android.content.Context; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.widget.ScrollView; - -public class GalleryFriendlyScrollView extends ScrollView { - public GalleryFriendlyScrollView(Context context) { - super(context); - } - - public GalleryFriendlyScrollView(Context context, AttributeSet attrs){ - super(context, attrs); - } - - @Override - public boolean onTouchEvent(MotionEvent ev) { - return super.onTouchEvent(ev); - } - -} diff --git a/src/com/quran/labs/androidquran/widgets/QuranGallery.java b/src/com/quran/labs/androidquran/widgets/QuranGallery.java deleted file mode 100644 index 268d948d62..0000000000 --- a/src/com/quran/labs/androidquran/widgets/QuranGallery.java +++ /dev/null @@ -1,120 +0,0 @@ -package com.quran.labs.androidquran.widgets; - -import android.content.Context; -import android.util.AttributeSet; -import android.view.KeyEvent; -import android.view.MotionEvent; -import android.widget.Gallery; - -import com.quran.labs.androidquran.R; -import com.quran.labs.androidquran.common.INavigatorListener; - -// http://stackoverflow.com/questions/5286115 -public class QuranGallery extends Gallery { - - // Gallery Scrolling Speed - public static final int GALLERY_SCROLLING_SPEED = 250; - private static final int LANDSCAPE_SCROLLING_SPEED = 50; - - private float mInitialX; - private float mInitialY; - private boolean mNeedToRebase; - private boolean mIgnore; - private INavigatorListener navigatorListener = null; - - - public QuranGallery(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @Override - public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, - float distanceY) { - if (mNeedToRebase) { - mNeedToRebase = false; - distanceX = 0; - } - return super.onScroll(e1, e2, distanceX, distanceY); - } - - @Override - public boolean onKeyDown(int keyCode, KeyEvent event) { - GalleryFriendlyScrollView scroller = null; - // Get the scroller from the layout - Only present in landscape mode - scroller = (GalleryFriendlyScrollView) findViewById(R.id.pageScrollView); - switch(keyCode) { - case KeyEvent.KEYCODE_DPAD_DOWN: - if (scroller != null) { - scroller.scrollBy(0, LANDSCAPE_SCROLLING_SPEED); - } - break; - case KeyEvent.KEYCODE_DPAD_UP: - if (scroller != null) { - scroller.scrollBy(0, -LANDSCAPE_SCROLLING_SPEED); - } - break; - case KeyEvent.KEYCODE_DPAD_LEFT: - if (navigatorListener != null) - navigatorListener.onLeftKey(); - break; - case KeyEvent.KEYCODE_DPAD_RIGHT: - if (navigatorListener != null) - navigatorListener.onRightKey(); - break; - } - return super.onKeyDown(keyCode, event); - } - - @Override - public boolean onInterceptTouchEvent(MotionEvent e) { - switch (e.getAction()) { - case MotionEvent.ACTION_DOWN: { - mIgnore = false; - mNeedToRebase = true; - mInitialX = e.getX(); - mInitialY = e.getY(); - return false; - } - - case MotionEvent.ACTION_MOVE: { - if (!mIgnore) { - float deltaX = Math.abs(e.getX() - mInitialX); - float deltaY = Math.abs(e.getY() - mInitialY); - mIgnore = deltaX < deltaY; - return !mIgnore; - } - return false; - } - default: { - return super.onInterceptTouchEvent(e); - } - } - } - - /** - * Checkout this post at stack overflow - * http://stackoverflow.com/questions/2373617/how-to-stop-scrolling-in-a-gallery-widget - */ - @Override - public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { - boolean leftScroll = false; - if (e1 != null && e2 != null) - leftScroll = isScrollingLeft(e1, e2); - - if (leftScroll) { - velocityX = GALLERY_SCROLLING_SPEED; - } else { - velocityX = -GALLERY_SCROLLING_SPEED; - } - - return super.onFling(e1, e2, velocityX, velocityY); - } - - private boolean isScrollingLeft(MotionEvent e1, MotionEvent e2) { - return e2.getX() > e1.getX(); - } - - public void setNavigatorListener(INavigatorListener navigatorListener) { - this.navigatorListener = navigatorListener; - } -} diff --git a/src/com/quran/labs/androidquran/widgets/QuranImageView.java b/src/com/quran/labs/androidquran/widgets/QuranImageView.java deleted file mode 100644 index 81ed148210..0000000000 --- a/src/com/quran/labs/androidquran/widgets/QuranImageView.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.quran.labs.androidquran.widgets; - -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.util.AttributeSet; -import android.widget.ImageView; - -import com.quran.labs.androidquran.data.ApplicationConstants; -import com.quran.labs.androidquran.util.QuranScreenInfo; - -public class QuranImageView extends ImageView { - - public QuranImageView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } - - public QuranImageView(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public QuranImageView(Context context) { - super(context); - } - - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - - QuranScreenInfo qsi = QuranScreenInfo.getInstance(); - Paint p = new Paint(); - p.setColor(Color.parseColor(ApplicationConstants.PAGE_BORDER_COLOR)); - int length = qsi.getHeight(); - int width = qsi.getWidth(); - canvas.drawLine(0, 0, 0, length, p); - canvas.drawLine(width, 0, width, length, p); - - invalidate(); - } -} diff --git a/src/com/quran/labs/androidquran/widgets/QuranPageCurlView.java b/src/com/quran/labs/androidquran/widgets/QuranPageCurlView.java new file mode 100644 index 0000000000..eb027d84b0 --- /dev/null +++ b/src/com/quran/labs/androidquran/widgets/QuranPageCurlView.java @@ -0,0 +1,1256 @@ +package com.quran.labs.androidquran.widgets; + +import java.lang.ref.WeakReference; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.Rect; +import android.os.Handler; +import android.os.Message; +import android.os.Parcelable; +import android.text.TextPaint; +import android.util.AttributeSet; +import android.util.Log; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewConfiguration; +import android.widget.ScrollView; + +import com.quran.labs.androidquran.R; + +/* + * Adapted and modified from http://code.google.com/p/android-page-curl + * + * Modifications to take 3 pages at a time for previous, current, and next pages + * Use SoftReference for pages + * + * TODO: enable RTL flipping as a parameter + * + * TODO: How do I pass on click events to my "children"? They're not really my children, + * I'm just holding them as my children anyways but they're really orphan views + * with no root ViewGroup. I'm not giving them their rights because i'm + * stealing touch from them. May Allah forgive me (4:10). Should be more like + * this: (4:6) ie. take when needed to help and give them their rights + * + * Update: resolved by calling dispatchTouchEvent() on child. But still, children + * do not show focus or appear to the user that they have been clicked although they have! + * Guess I'm still failing on some parental rights. + * + * Update: DONE! + * + * TODO: Problem when doing a "perfect" touch click (ie. ACTION_DOWN followed by ACTION_UP with + * no ACTION_MOVE in between). Page flip direction not determined and causes problems + * + * Update: Fixed it (I think). But still in review + */ +public class QuranPageCurlView extends View { + + /** Log tag */ + private final static String TAG = "QuranPageCurlView"; + + public interface OnPageFlipListener { + + public static final int PREVIOUS_PAGE = 1; + public static final int NEXT_PAGE = 2; + + /* + * Will be called before page Flipping begins + */ + public void onPageFlipBegin(QuranPageCurlView pageView, + final int flipDirection); + + /* + * Will be called when page Flipping ends + */ + public void onPageFlipEnd(QuranPageCurlView pageView, + final int flipDirection); + + } + + // Debug text paint stuff + private Paint mTextPaint; + private TextPaint mTextPaintShadow; + + /** Px / Draw call */ + private int mCurlSpeed; + + /** Fixed update time used to create a smooth curl animation */ + private int mUpdateRate; + + /** The initial offset for x and y axis movements */ + private int mInitialEdgeOffset; + + /** The mode we will use */ + private int mCurlMode; + + /** The page background color */ + private int mBackgroundColor; + + /** Simple curl mode. Curl target will move only in one axis. */ + public static final int CURLMODE_SIMPLE = 0; + + /** Dynamic curl mode. Curl target will move on both X and Y axis. */ + public static final int CURLMODE_DYNAMIC = 1; + + /** Enable/Disable debug mode */ + private boolean bEnableDebugMode = false; + + /** The context which owns us */ + private WeakReference mContext; + + /** Handler used to auto flip time based */ + private FlipAnimationHandler mAnimationHandler; + private ScrollAnimationHandler mScrollAnimationHandler; + + /** The finger position */ + private Vector2D mFinger; + + /** Page curl edge */ + private Paint mCurlEdgePaint, mEmptyPagePaint; + + /** If false no draw call has been done */ + private boolean bViewDrawn; + + /** Defines the flip direction that is currently considered */ + private boolean bFlipRight; + + /** If TRUE we are currently auto-flipping */ + private boolean bFlipping; + + /** TRUE if the user moves the pages */ + @SuppressWarnings("unused") + private boolean bUserMoves; + + /** Used to control touch input blocking */ + private boolean bBlockTouchInput = false; + + /** Enable input after the next draw event */ + private boolean bEnableInputAfterDraw = false; + + /** Current page number */ + @SuppressWarnings("unused") + private int mIndex = 0; + + /* + * Must hold minimum 3 items: current(shown),next(shown), previous Works + * well with book anyways because a book has minimum 3 pages (front cover, 1 + * page, back cover) + * + * Something to ponder about: Is 4th page needed (next-next page) if + * creating bitmap takes too long?? + */ + + /* + * Is page flipping allowed + */ + private boolean bAllowFlip = false; + + /* + * When last ACTION_DOWN event was received what was the page flip direction + */ + private boolean bFlipRightOnDown = false; + + /* + * When last ACTION_MOVE event was received what was the page flip direction + */ + private boolean bFlipRightOnLastMove = false; + + /* + * Page Flip Listener + */ + private OnPageFlipListener mFlipListener; + + /* + * Drawing Views for Curling + */ + View mForegroundView; + View mBackgroundView; + + /* + * Keep Hard references to certain needed pages to prolong their life so GC + * doesn't kill them (5:32) + */ + private View mPreviousPageView = null; + private View mCurrentPageView = null; + private View mNextPageView = null; + + // Offset of translated page + private Vector2D mPageOffset; + + // Previous finger coordinates + private Vector2D mPreviousFinger; + + // Width of the clip edge of the translated page + private static final float clipOffset = 3; + + /* + * Has a decision been taken on who to give touch events to + * in this current touch cycle + */ + private boolean dispatchDecisionTaken = false; + + /* + * Has the dispatch decision just been taken now? + */ + private boolean dispatchDecisionTakenNow = false; + + /* + * Will I be giving touch events to me? + */ + private boolean dispatchTouchToMe = false; + + /* + * When the last ACTION_DOWN was received what was the finger position + */ + private Vector2D mPositionOnDown; + + /* + * Distance after which I need to take a dispatch decision + * + * Also used to account for fingers having slight movement even if a "perfect" + * click was intended by the user(ie. ACTION_DOWN followed immediately by ACTION_UP) + * innama al a3malu bilniyyat... + * + */ + private static final int FINGER_MOVEMENT_SLOP_DISTANCE = ViewConfiguration.getTouchSlop(); + + /* + * Max time between ACTION_DOWN and ACTION_UP to consider this a click + * (completely heuristics) + */ + private static final int FINGER_CLICK_TIME_MAX = (int) (ViewConfiguration.getTapTimeout() * 1.5); //in milliseconds + + + /** + * Inner class used to represent a 2D point. + */ + private class Vector2D { + public float x, y; + + public Vector2D(float x, float y) { + this.x = x; + this.y = y; + } + + @Override + public String toString() { + return "(" + this.x + "," + this.y + ")"; + } + + @SuppressWarnings("unused") + public float length() { + return (float) Math.sqrt(x * x + y * y); + } + + @SuppressWarnings("unused") + public float lengthSquared() { + return (x * x) + (y * y); + } + + public boolean equals(Object o) { + if (o instanceof Vector2D) { + Vector2D p = (Vector2D) o; + return p.x == x && p.y == y; + } + return false; + } + + @SuppressWarnings("unused") + public Vector2D reverse() { + return new Vector2D(-x, -y); + } + + @SuppressWarnings("unused") + public Vector2D sum(Vector2D b) { + return new Vector2D(x + b.x, y + b.y); + } + + @SuppressWarnings("unused") + public Vector2D sub(Vector2D b) { + return new Vector2D(x - b.x, y - b.y); + } + + @SuppressWarnings("unused") + public float dot(Vector2D vec) { + return (x * vec.x) + (y * vec.y); + } + + @SuppressWarnings("unused") + public float cross(Vector2D a, Vector2D b) { + return a.cross(b); + } + + public float cross(Vector2D vec) { + return x * vec.y - y * vec.x; + } + + public float distanceSquared(Vector2D other) { + float dx = other.x - x; + float dy = other.y - y; + + return (dx * dx) + (dy * dy); + } + + public float distance(Vector2D other) { + return (float) Math.sqrt(distanceSquared(other)); + } + + public float dotProduct(Vector2D other) { + return other.x * x + other.y * y; + } + + @SuppressWarnings("unused") + public Vector2D normalize() { + float magnitude = (float) Math.sqrt(dotProduct(this)); + return new Vector2D(x / magnitude, y / magnitude); + } + + @SuppressWarnings("unused") + public Vector2D mult(float scalar) { + return new Vector2D(x * scalar, y * scalar); + } + } + + /** + * Inner class used to make a fixed timed animation of the curl effect. + */ + class FlipAnimationHandler extends Handler { + @Override + public void handleMessage(Message msg) { + QuranPageCurlView.this.FlipAnimationStep(); + } + + public void sleep(long millis) { + this.removeMessages(0); + sendMessageDelayed(obtainMessage(0), millis); + } + } + + class ScrollAnimationHandler extends Handler { + + int mCount = 0; + long updateRate = 0; + + @Override + public void handleMessage(Message msg) { + //Log.d(TAG, "animating scroll"); + QuranPageCurlView.this.invalidate(); + mCount--; + this.sleep(); + } + + public void sleep() { + this.removeMessages(0); + if (mCount > 0) { + sendMessageDelayed(obtainMessage(0), updateRate); + } + } + + public void setCount(int count){ + mCount = count; + } + + public void setUpdateRate(long millis){ + updateRate = millis; + } + } + + /** + * Base + * + * @param context + */ + public QuranPageCurlView(Context context) { + super(context); + init(context); + ResetClipEdge(); + } + + /** + * Construct the object from an XML file. Valid Attributes: + * + * @see android.view.View#View(android.content.Context, + * android.util.AttributeSet) + */ + public QuranPageCurlView(Context context, AttributeSet attrs) { + super(context, attrs); + init(context); + + // Get the data from the XML AttributeSet + { + TypedArray a = context.obtainStyledAttributes(attrs, + R.styleable.QuranPageCurlView); + + // Get data + bEnableDebugMode = a.getBoolean(R.styleable.QuranPageCurlView_enableDebugMode, bEnableDebugMode); + mCurlSpeed = a.getInt(R.styleable.QuranPageCurlView_curlSpeed, mCurlSpeed); + mUpdateRate = a.getInt(R.styleable.QuranPageCurlView_updateRate, mUpdateRate); + mInitialEdgeOffset = a.getInt(R.styleable.QuranPageCurlView_initialEdgeOffset, mInitialEdgeOffset); + mCurlMode = a.getInt(R.styleable.QuranPageCurlView_curlMode, mCurlMode); + mBackgroundColor = a.getColor(R.styleable.QuranPageCurlView_backgroundColor, mBackgroundColor); + mCurlEdgePaint.setColor(mBackgroundColor); + + Log.i(TAG, "mCurlSpeed: " + mCurlSpeed); + Log.i(TAG, "mUpdateRate: " + mUpdateRate); + Log.i(TAG, "mInitialEdgeOffset: " + mInitialEdgeOffset); + Log.i(TAG, "mCurlMode: " + mCurlMode); + Log.i(TAG, "mBackgroundColor: " + mBackgroundColor); + + // recycle object (so it can be used by others) + a.recycle(); + } + + ResetClipEdge(); + } + + public QuranPageCurlView(Context context, AttributeSet attrs, int defStyle) { + this(context, attrs); + } + + /** + * Initialize the view + */ + private final void init(Context context) { + // Foreground text paint + mTextPaint = new Paint(); + mTextPaint.setAntiAlias(true); + mTextPaint.setTextSize(16); + mTextPaint.setColor(0xFF000000); + + // The shadow + mTextPaintShadow = new TextPaint(); + mTextPaintShadow.setAntiAlias(true); + mTextPaintShadow.setTextSize(16); + mTextPaintShadow.setColor(0x00000000); + + // Cache the context + mContext = new WeakReference(context); + + // Base padding + setPadding(3, 3, 3, 3); + + // The focus flags are needed + setFocusable(true); + setFocusableInTouchMode(true); + + mFinger = new Vector2D(0, 0); + mPageOffset = new Vector2D(0, 0); + mPreviousFinger = new Vector2D(0, 0); + mPositionOnDown = new Vector2D(0, 0); + + // Create our curl animation handler + mAnimationHandler = new FlipAnimationHandler(); + mScrollAnimationHandler = new ScrollAnimationHandler(); + + mBackgroundColor = Color.WHITE; + + // Create our edge paint + mCurlEdgePaint = new Paint(); + mCurlEdgePaint.setColor(mBackgroundColor); + mCurlEdgePaint.setAntiAlias(true); + mCurlEdgePaint.setStyle(Paint.Style.FILL); + mCurlEdgePaint.setShadowLayer(5, -5, 5, 0x99000000); + + // Create empty page paint + mEmptyPagePaint = new Paint(); + mEmptyPagePaint.setColor(Color.GRAY); + mEmptyPagePaint.setAntiAlias(true); + mEmptyPagePaint.setStyle(Paint.Style.FILL); + + // Set the default props, those come from an XML :D + mCurlSpeed = 30; + mUpdateRate = 33; + mInitialEdgeOffset = 20; + mCurlMode = 1; + + + Log.d(TAG, "touch slop: " + FINGER_MOVEMENT_SLOP_DISTANCE + " time slop: " + FINGER_CLICK_TIME_MAX); + } + + public void setOnPageFlipListener(OnPageFlipListener listener) { + mFlipListener = listener; + } + + /* + * Before displaying the view, need to layout and measure page according to + * our current View's layout specs + */ + private void pageViewMeasureAndLayout(View v) { + if (v != null) { + v.measure(MeasureSpec.makeMeasureSpec(getWidth(), + MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec( + getHeight(), MeasureSpec.EXACTLY)); + v.layout(0, 0, getWidth(), getHeight()); + } + } + + /* + * Add new next page (if flipping forward) + */ + public void addNextPage(View v) { + // Shift pages to make room for next page + mPreviousPageView = mCurrentPageView; + mCurrentPageView = mNextPageView; + mNextPageView = v; + } + + /* + * Add new previous page (if flipping backwards) + */ + public void addPreviousPage(View v) { + // Shift pages to make room for previous page + mNextPageView = mCurrentPageView; + mCurrentPageView = mPreviousPageView; + mPreviousPageView = v; + } + + /* + * Set current page + */ + public void addCurrentPage(View v) { + mCurrentPageView = v; + } + + public View getCurrentPage() { + return mCurrentPageView; + } + + public void scrollPage(int scrollerId, int direction) { + ScrollView sv = (ScrollView) mCurrentPageView.findViewById(scrollerId); + if (sv != null){ + sv.arrowScroll(direction); + mScrollAnimationHandler.setUpdateRate(mUpdateRate); + mScrollAnimationHandler.setCount(20); // 33ms x 20 = ~0.75 second + mScrollAnimationHandler.sleep(); + } + } + + /** + * Reset points to it's initial clip edge state + */ + public void ResetClipEdge() { + // Set our base movement + mPreviousFinger.x = 0; + + + // Add some offset to push it off the edge so it is not seen + mPageOffset.x = getWidth() + 30; + } + + /** + * Return the context which created use. Can return null if the context has + * been erased. + */ + @SuppressWarnings("unused") + private Context GetContext() { + return mContext.get(); + } + + /** + * See if the current curl mode is dynamic + */ + public boolean IsCurlModeDynamic() { + return mCurlMode == CURLMODE_DYNAMIC; + } + + /** + * Set the curl speed in px/frame + */ + public void SetCurlSpeed(int curlSpeed) { + if (curlSpeed < 1) + throw new IllegalArgumentException( + "curlSpeed must be greated than 0"); + mCurlSpeed = curlSpeed; + } + + /** + * Get the current curl speed in px/frame + */ + public int GetCurlSpeed() { + return mCurlSpeed; + } + + /** + * Set the update rate for the curl animation + */ + public void SetUpdateRate(int updateRate) { + if (updateRate < 1) + throw new IllegalArgumentException( + "updateRate must be greated than 0"); + mUpdateRate = updateRate; + } + + /** + * Get the current animation update rate in fps + */ + public int GetUpdateRate() { + return mUpdateRate; + } + + /** + * Set the initial pixel offset for the curl edge + */ + public void SetInitialEdgeOffset(int initialEdgeOffset) { + if (initialEdgeOffset < 0) + throw new IllegalArgumentException( + "initialEdgeOffset can not negative"); + mInitialEdgeOffset = initialEdgeOffset; + } + + /** + * Get the initial pixel offset for the curl edge + * + * @return int - px + */ + public int GetInitialEdgeOffset() { + return mInitialEdgeOffset; + } + + /** + * Set the curl mode. + */ + public void SetCurlMode(int curlMode) { + if (curlMode != CURLMODE_SIMPLE && curlMode != CURLMODE_DYNAMIC) + throw new IllegalArgumentException("Invalid curlMode"); + mCurlMode = curlMode; + } + + /** + * Return an integer that represents the current curl mode. + */ + public int GetCurlMode() { + return mCurlMode; + } + + /** + * Enable debug mode. This will draw a lot of data in the view so you can + * track what is happening + * + * @param bFlag + * - boolean flag + */ + public void SetEnableDebugMode(boolean bFlag) { + bEnableDebugMode = bFlag; + } + + /** + * Check if we are currently in debug mode. + * + * @return boolean - If TRUE debug mode is on, FALSE otherwise. + */ + public boolean IsDebugModeEnabled() { + return bEnableDebugMode; + } + + /** + * @see android.view.View#measure(int, int) + */ + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int finalWidth, finalHeight; + finalWidth = measureWidth(widthMeasureSpec); + finalHeight = measureHeight(heightMeasureSpec); + setMeasuredDimension(finalWidth, finalHeight); + } + + /** + * Determines the width of this view + * + * @param measureSpec + * A measureSpec packed into an int + * @return The width of the view, honoring constraints from measureSpec + */ + private int measureWidth(int measureSpec) { + int result = 0; + int specMode = MeasureSpec.getMode(measureSpec); + int specSize = MeasureSpec.getSize(measureSpec); + + if (specMode == MeasureSpec.EXACTLY) { + // We were told how big to be + result = specSize; + } else { + result = ((View) getParent()).getWidth(); + } + + return result; + } + + /** + * Determines the height of this view + * + * @param measureSpec + * A measureSpec packed into an int + * @return The height of the view, honoring constraints from measureSpec + */ + private int measureHeight(int measureSpec) { + int result = 0; + int specMode = MeasureSpec.getMode(measureSpec); + int specSize = MeasureSpec.getSize(measureSpec); + + if (specMode == MeasureSpec.EXACTLY) { + // We were told how big to be + result = specSize; + } else { + result = ((View) getParent()).getHeight(); + } + return result; + } + + public void doPageFlip(int direction) { + + switch(direction){ + case OnPageFlipListener.PREVIOUS_PAGE: + bFlipRight = bFlipRightOnDown = bFlipRightOnLastMove = true; + mPageOffset.x = getWidth(); + break; + case OnPageFlipListener.NEXT_PAGE: + bFlipRight = bFlipRightOnDown = bFlipRightOnLastMove = false; + // set up canvas for next page + nextDrawView(); + + // Start from far left + mPageOffset.x = 1; + break; + } + + bUserMoves = false; + bFlipping = true; + if (mFlipListener != null && (bFlipRightOnDown == bFlipRightOnLastMove)) { + mFlipListener.onPageFlipBegin(this, + bFlipRight ? OnPageFlipListener.PREVIOUS_PAGE : OnPageFlipListener.NEXT_PAGE); + } + FlipAnimationStep(); + } + + /** + * Determine whether or not to dispatch the touch event to myself to draw or + * to my immediate child (eg. button pres, scrolling etc) + */ + @Override + public boolean dispatchTouchEvent(MotionEvent event) { + //Log.d(TAG, "\n\ndispatching..... " + event.getAction()); + if (mCurrentPageView != null) { + boolean hasScroller = ((Boolean) mCurrentPageView.getTag()).booleanValue(); + + /* + // If there is no scroll view then always dispatch events to me + if (!hasScroller) { + return super.dispatchTouchEvent(event); + } + */ + + /** + * Strategy explained: + * + * First, give to both me and children, then when distance goes + * beyond a certain point, then we find out if movement is + * vertical or horizontal oriented. I use this distance to make + * a final decision on who takes the remaining touches + */ + + mFinger.x = event.getX(); + mFinger.y = event.getY(); + + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + // record first down touch + mPositionOnDown.x = event.getX(); + mPositionOnDown.y = event.getY(); + + dispatchDecisionTaken = false; + dispatchTouchToMe = false; + break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + //Reset positions (do i need this?) + mPositionOnDown.x = 0; + mPositionOnDown.y = 0; + + // animate scroll if my child?? + if (!dispatchDecisionTaken || (dispatchDecisionTaken && !dispatchTouchToMe)){ + mScrollAnimationHandler.setUpdateRate(mUpdateRate); + mScrollAnimationHandler.setCount(30); // 33ms x 30 = ~1 second + mScrollAnimationHandler.sleep(); + } + + break; + case MotionEvent.ACTION_MOVE: + // as we move calculate distance and make final decision + if (!dispatchDecisionTaken && (mFinger.distance(mPositionOnDown) > FINGER_MOVEMENT_SLOP_DISTANCE)){ + + dispatchDecisionTaken = true; + dispatchDecisionTakenNow = true; + + final float xMovement = Math.abs(mFinger.x - mPositionOnDown.x); + final float yMovement = Math.abs(mFinger.y - mPositionOnDown.y); + + // Horizontal movement mainly => page flip + if (xMovement >= yMovement){ + dispatchTouchToMe = true; + } else { //Vertical movement mainly => scrolling + // but only assign to child if it has a scroller + dispatchTouchToMe = hasScroller? false : true; + } + } + break; + } + + //Log.d(TAG, "On Down Pos: " + mPositionOnDown); + //Log.d(TAG, "my finger: " + mFinger); + //Log.d(TAG, "decisionTaken: " + dispatchDecisionTaken); + //Log.d(TAG, "taken now: " + dispatchDecisionTakenNow); + //Log.d(TAG, "to me: " + dispatchTouchToMe); + + // Let's see what the decisions were + if (dispatchDecisionTaken){ + if (dispatchTouchToMe){ + final boolean consumed = super.dispatchTouchEvent(event); + /* + * Send cancel to my children first + */ + if (dispatchDecisionTakenNow) { + dispatchDecisionTakenNow = false; + event.setAction(MotionEvent.ACTION_CANCEL); + mCurrentPageView.dispatchTouchEvent(event); + } + + return consumed; + } else { + final boolean childConsumed = mCurrentPageView.dispatchTouchEvent(event); + + // Need to redraw screen to show updated children if they changed + if (childConsumed) + invalidate(); + + /* + * Send cancel to myself first + */ + if (dispatchDecisionTakenNow) { + dispatchDecisionTakenNow = false; + event.setAction(MotionEvent.ACTION_CANCEL); + super.dispatchTouchEvent(event); + } + + return childConsumed; + } + } else { + // Both of us take the touch event until decision is taken + final boolean childConsumed = mCurrentPageView.dispatchTouchEvent(event); + return (super.dispatchTouchEvent(event) || childConsumed); + } + } + + Log.d(TAG, "my current view is null!"); + // current view is null so I always get it + return super.dispatchTouchEvent(event); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + //Log.d(TAG, "dispatched to me"); + + int width = getWidth(); + + /* If this was a click, then fire onClickListener and cancel current page flip because + * it seems that this was not intended for page flip. + * + * Click is defined as: + * - down/up where time is smaller than FINGER_CLICK_TIME_MAX + * - down/up where distance between down and up is smaller than FINGER_MOVEMENT_SLOP_DISTANCE + * (if dispatchDecisionTaken is true, then we've moved too far already) + */ + if ((event.getAction() == MotionEvent.ACTION_UP) && + ((event.getEventTime() - event.getDownTime()) <= FINGER_CLICK_TIME_MAX) && + !dispatchDecisionTaken) { + float xPos = event.getX(); + + /* + * Only perform click if the tap was in the center of the screen. + * Otherwise, just perform the ACTION_UP as normal which should + * flip the page depending which side of the screen the tap was on + */ + if ((xPos > width/4) && (xPos < width*3/4)){ + event.setAction(MotionEvent.ACTION_CANCEL); + performClick(); + } + } + + + // Blocking touch due to a page flip animation that is ongoing + if (!bBlockTouchInput) { + + // Get our finger position + mFinger.x = event.getX(); + mFinger.y = event.getY(); + + // Depending on the action do what we need to + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + mPreviousFinger.x = mFinger.x; + + bAllowFlip = false; + + // If we moved over the half of the display flip to next + if (mPreviousFinger.x > (width >> 1)) { + // Start from far right + mPageOffset.x = getWidth(); + + + // Set the right movement flag + bFlipRight = true; + bFlipRightOnDown = true; + bFlipRightOnLastMove = true; + + if (mPreviousPageView != null) { + bAllowFlip = true; + } + } else { + // Set the left movement flag + bFlipRight = false; + bFlipRightOnDown = false; + bFlipRightOnLastMove = false; + + if (mNextPageView != null) { + bAllowFlip = true; + + // set up canvas for next page + nextDrawView(); + + // Start from far left + mPageOffset.x = 1; + } + } + + break; + + case MotionEvent.ACTION_UP: + if (bAllowFlip) { + bUserMoves = false; + bFlipping = true; + if (mFlipListener != null && (bFlipRightOnDown == bFlipRightOnLastMove)) { + mFlipListener.onPageFlipBegin(this, + bFlipRight ? OnPageFlipListener.PREVIOUS_PAGE : OnPageFlipListener.NEXT_PAGE); + } + FlipAnimationStep(); + } + break; + case MotionEvent.ACTION_CANCEL: + if (bAllowFlip) { + bUserMoves = false; + bFlipping = true; + bFlipRightOnLastMove = !bFlipRightOnDown; // make sure we dont call pageflip listener + + // return back to original page + if (bFlipRightOnDown) + bFlipRight = false; + else + bFlipRight = true; + + FlipAnimationStep(); + } + break; + case MotionEvent.ACTION_MOVE: + if (bAllowFlip) { + bUserMoves = true; + + // calculate new position + mPageOffset.x += (mFinger.x - mPreviousFinger.x); + + // Cap movement to page limits + mPageOffset.x = Math.min(getWidth() - 1, mPageOffset.x); + mPageOffset.x = Math.max(0, mPageOffset.x); + + /* + * Make sure that we've moved enough beyond the finger slop distance + * or else a long finger click can cause strange behavior because + * the direction will be incorrectly determined + */ + if (dispatchDecisionTaken){ + + // Get movement direction + if (mFinger.x < mPreviousFinger.x) { + bFlipRight = true; + bFlipRightOnLastMove = true; + } else { + bFlipRight = false; + bFlipRightOnLastMove = false; + } + } + + // Store finger postion for next round + mPreviousFinger.x = mFinger.x; + + // Force a new draw call + this.invalidate(); + } + + break; + } + + } + + /* + * TODO: Only consume event if we need to? Probably better to change wallahu + * a3lam but it's not critical inshaAllah + */ + return true; + } + + /** + * Execute a step of the flip animation + */ + public void FlipAnimationStep() { + if (!bFlipping) + return; + + int width = getWidth(); + + // No input when flipping + bBlockTouchInput = true; + + // Handle speed + float curlSpeed = mCurlSpeed; + if (!bFlipRight) + curlSpeed *= -1; + + // Move page offset + mPageOffset.x -= curlSpeed; + + // Check for endings :D + if (mPageOffset.x < 0 || mPageOffset.x > width) { + bFlipping = false; + + // Call listener to let them know flipping is done + if (mFlipListener != null && (bFlipRightOnDown == bFlipRightOnLastMove)) { + mFlipListener.onPageFlipEnd(this, + bFlipRight ? OnPageFlipListener.PREVIOUS_PAGE : OnPageFlipListener.NEXT_PAGE); + } + + if (bFlipRight) { + previousDrawView(); + } + + ResetClipEdge(); + + // Enable touch input after the next draw event + bEnableInputAfterDraw = true; + } else { + // Cap movement to page limits + mPageOffset.x = Math.min(getWidth() - 1, mPageOffset.x); + mPageOffset.x = Math.max(0, mPageOffset.x); + + mAnimationHandler.sleep(mUpdateRate); + } + + // Force a new draw call + this.invalidate(); + } + + public void nextDrawView() { + mForegroundView = mNextPageView; + mBackgroundView = mCurrentPageView; // new next? + } + + public void previousDrawView() { + mForegroundView = mCurrentPageView; + mBackgroundView = mPreviousPageView; + } + + // --------------------------------------------------------------- + // Drawing methods + // --------------------------------------------------------------- + + @Override + protected void onDraw(Canvas canvas) { + + // We need to initialize all size data when we first draw the view + if (!bViewDrawn) { + bViewDrawn = true; + onFirstDrawEvent(canvas); + } + + canvas.drawColor(Color.WHITE); + + Rect rect = new Rect(); + rect.left = 0; + rect.top = 0; + rect.right = getWidth(); + rect.bottom = getHeight(); + + // First Page render + Paint paint = new Paint(); + + // Draw our elements + drawForegroundView(canvas, rect, paint); + drawCurlEdge(canvas); + drawBackgroundView(canvas, rect, paint); + + // Check if we can re-enable input + if (bEnableInputAfterDraw) { + bBlockTouchInput = false; + bEnableInputAfterDraw = false; + } + + } + + /** + * Called on the first draw event of the view + * + * @param canvas + */ + protected void onFirstDrawEvent(Canvas canvas) { + ResetClipEdge(); + } + + /** + * Create a Path used as a mask to draw the background page + * + * @return + */ + private Path createForegroundPath() { + + Path path = new Path(); + path.moveTo(0, 0); + path.lineTo(mPageOffset.x, 0); + path.lineTo(mPageOffset.x, getHeight()); + path.lineTo(0, getHeight()); + path.lineTo(0, 0); + return path; + } + + /** + * Draw the foreground + * + * @param canvas + * @param rect + * @param paint + */ + private void drawForegroundView(Canvas canvas, Rect rect, Paint paint) { + + Path mask = createForegroundPath(); + + // Save current canvas so we do not mess it up + canvas.save(); + canvas.clipPath(mask); + + if (mForegroundView != null) { + pageViewMeasureAndLayout(mForegroundView); + mForegroundView.draw(canvas); + } else { + canvas.drawRect(0, 0, getWidth(), getHeight(), mEmptyPagePaint); + } + + canvas.restore(); + } + + /** + * Draw the background image. + * + * @param canvas + * @param rect + * @param paint + */ + private void drawBackgroundView(Canvas canvas, Rect rect, Paint paint) { + // Save current canvas so we do not mess it up + canvas.save(); + canvas.translate(mPageOffset.x, 0); + + // canvas.clipPath(mask); + + if (mBackgroundView != null) { + pageViewMeasureAndLayout(mBackgroundView); + mBackgroundView.draw(canvas); + } else { + canvas.drawRect(0, 0, getWidth(), getHeight(), mEmptyPagePaint); + } + + canvas.restore(); + } + + /** + * Creates a path used to draw the curl edge in. + * + * @return + */ + private Path createCurlEdgePath() { + Path path = new Path(); + path.moveTo(mPageOffset.x, 0); + path.lineTo(mPageOffset.x + clipOffset, 0); + path.lineTo(mPageOffset.x + clipOffset, getHeight()); + path.lineTo(mPageOffset.x, getHeight()); + path.lineTo(mPageOffset.x, 0); + return path; + } + + /** + * Draw the curl page edge + * + * @param canvas + */ + private void drawCurlEdge(Canvas canvas) { + Path path = createCurlEdgePath(); + canvas.drawPath(path, mCurlEdgePaint); + } + + // --------------------------------------------------------------- + // Debug draw methods + // --------------------------------------------------------------- + + /** + * Draw a text with a nice shadow + */ + public static void drawTextShadowed(Canvas canvas, String text, float x, + float y, Paint textPain, Paint shadowPaint) { + canvas.drawText(text, x - 1, y, shadowPaint); + canvas.drawText(text, x, y + 1, shadowPaint); + canvas.drawText(text, x + 1, y, shadowPaint); + canvas.drawText(text, x, y - 1, shadowPaint); + canvas.drawText(text, x, y, textPain); + } + + /** + * Draw a text with a nice shadow centered in the X axis + * + * @param canvas + * @param text + * @param y + * @param textPain + * @param shadowPaint + */ + public static void drawCentered(Canvas canvas, String text, float y, + Paint textPain, Paint shadowPaint) { + float posx = (canvas.getWidth() - textPain.measureText(text)) / 2; + drawTextShadowed(canvas, text, posx, y, textPain, shadowPaint); + } + + public void refresh(boolean initialSetup){ + if (initialSetup){ + previousDrawView(); + } + + this.invalidate(); + } + + public void refresh() { + refresh(false); + } + + @Override + protected void onRestoreInstanceState(Parcelable state) { + // TODO Auto-generated method stub + super.onRestoreInstanceState(state); + } + + @Override + protected Parcelable onSaveInstanceState() { + // TODO Auto-generated method stub + return super.onSaveInstanceState(); + } + + + +} \ No newline at end of file