package com.dteviot.epubviewer;
import java.util.ArrayList;
import com.dteviot.epubviewer.epub.Book;
import android.content.Context;
import android.graphics.Picture;
import android.graphics.Rect;
import android.net.Uri;
import android.speech.tts.TextToSpeech;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
/*
* Holds the logic for the App's
* special WebView handling
*/
public abstract class EpubWebView extends WebView {
private final static float FLING_THRESHOLD_VELOCITY = 200;
/*
* The book view will show
*/
private Book mBook;
private GestureDetector mGestureDetector;
/*
* "root" page we're currently showing
*/
private Uri mCurrentResourceUri;
/*
* Position of document
*/
private float mScrollY = 0.0f;
/*
* Note that we're loading from a bookmark
*/
private boolean mScrollWhenPageLoaded = false;
/*
* To speak the text
*/
private TextToSpeechWrapper mTtsWrapper;
/*
* The page, as text
*/
private ArrayList<String> mText;
private WebViewClient mWebViewClient;
/*
* Current line being spoken
*/
private int mTextLine;
/*
* Pick an initial default
*/
private float mFlingMinDistance = 320;
/*
* The total available area for drawing on
*/
private Rect mRawScreenDimensions;
public EpubWebView(Context context) {
this(context, null);
}
public EpubWebView(Context context, AttributeSet attrs) {
super(context, attrs);
mGestureDetector = new GestureDetector(context, mGestureListener);
WebSettings settings = getSettings();
settings.setCacheMode(WebSettings.LOAD_NO_CACHE);
settings.setPluginState(WebSettings.PluginState.ON_DEMAND);
settings.setBuiltInZoomControls(true);
addWebSettings(settings);
setWebViewClient(mWebViewClient = createWebViewClient());
setWebChromeClient(new WebChromeClient());
}
/*
* @ return Android version appropriate WebViewClient
*/
abstract protected WebViewClient createWebViewClient();
/*
* Do any Web settings specific to the derived class
*/
abstract protected void addWebSettings(WebSettings settings);
/*
* Book to show
*/
public void setBook(String fileName) {
// if book already loaded, don't load again
if ((mBook == null) || !mBook.getFileName().equals(fileName)) {
mBook = new Book(fileName);
}
}
public Book getBook() {
return mBook;
}
protected WebViewClient getWebViewClient() {
return mWebViewClient;
}
/*
* Chapter of book to show
*/
public void loadChapter(Uri resourceUri) {
if (mBook != null) {
// if no chapter resourceName supplied, default to first one.
if (resourceUri == null) {
resourceUri = mBook.firstChapter();
}
if (resourceUri != null) {
mCurrentResourceUri = resourceUri;
// prevent cache, because WebSettings.LOAD_NO_CACHE doesn't always work.
clearCache(false);
LoadUri(resourceUri);
}
}
}
/*
* @ return load contents of URI into WebView,
* implementation is android version dependent
*/
protected abstract void LoadUri(Uri uri);
@Override
public boolean onTouchEvent(MotionEvent event) {
return mGestureDetector.onTouchEvent(event) ||
super.onTouchEvent(event);
}
GestureDetector.SimpleOnGestureListener mGestureListener = new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onFling(MotionEvent event1, MotionEvent event2, float velocityX, float velocityY) {
// if no book, nothing to do
if (mBook == null) {
return false;
}
// also ignore swipes that are vertical, too slow, or too short.
float deltaX = event2.getX() - event1.getX();
float deltaY = event2.getY() - event1.getY();
if ((Math.abs(deltaX) < Math.abs(deltaY))
|| (Math.abs(deltaX) < mFlingMinDistance)
|| (Math.abs(velocityX) < FLING_THRESHOLD_VELOCITY)) {
return false;
} else {
if (deltaX < 0) {
return changeChapter(mBook.nextResource(mCurrentResourceUri));
} else {
return changeChapter(mBook.previousResource(mCurrentResourceUri));
}
}
}
/*
* If double tap at top/bottom fifth of screen, scroll page up/down
*
*/
@Override
public boolean onDoubleTap (MotionEvent e) {
float y = e.getY();
if (y <= mRawScreenDimensions.height() / 5) {
pageUp(false);
return true;
} else if (4 * mRawScreenDimensions.height() / 5 <= y) {
pageDown(false);
return true;
} else {
return false;
}
}
};
private boolean changeChapter(Uri resourceUri) {
if (resourceUri != null) {
loadChapter(resourceUri);
return true;
} else {
Utility.showToast(getContext(), R.string.end_of_book);
return false;
}
}
/*
* Store current view into bookmark
*/
public Bookmark getBookmark() {
if ((mBook != null) && (mCurrentResourceUri != null)) {
float contentHeight = (float)getContentHeight();
contentHeight = (contentHeight == 0.0f) ? 0.0f : getScrollY() / contentHeight;
return new Bookmark (
mBook.getFileName(),
mCurrentResourceUri,
contentHeight
);
}
return null;
}
public void gotoBookmark(Bookmark bookmark) {
if (!bookmark.isEmpty()) {
mScrollY = bookmark.mScrollY;
// get notify when content height is available, for setting Y scroll position
mScrollWhenPageLoaded = true;
setBook(bookmark.getFileName());
loadChapter(bookmark.getResourceUri());
}
}
public void speak(TextToSpeechWrapper ttsWrapper) {
mTtsWrapper = ttsWrapper;
if ((mBook != null) && (mCurrentResourceUri != null)) {
mText = new ArrayList<String>();
XmlUtil.parseXmlResource(
mBook.fetch(mCurrentResourceUri).getData(),
new XhtmlToText(mText), null);
mTextLine = 0;
mTtsWrapper.setOnUtteranceCompletedListener(mCompletedListener);
if (0 < mText.size()) {
mTtsWrapper.speak(mText.get(0));
}
}
}
/*
* Send next piece of text to Text to speech
*/
private TextToSpeech.OnUtteranceCompletedListener mCompletedListener =
new TextToSpeech.OnUtteranceCompletedListener() {
@Override
public void onUtteranceCompleted(String utteranceId) {
if (mTextLine < mText.size() - 1) {
mTtsWrapper.speak(mText.get(++mTextLine));
}
}
};
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mRawScreenDimensions = new Rect(0, 0, w, h);
mFlingMinDistance = w / 2;
}
/*
* Called when page is loaded,
* if we're scrolling to a bookmark, we need to set the
* page size listener here. Otherwise it can be called
* for pages other than the one we're interested in
*/
@SuppressWarnings("deprecation")
protected void onPageLoaded() {
if (mScrollWhenPageLoaded) {
setPictureListener(mPictureListener);
mScrollWhenPageLoaded = false;
}
}
/*
* Need to wait until view has figured out how big web page is
* Otherwise, we can't scroll to last position because
* getContentHeight() returns 0.
* At current time, there is no replacement for PictureListener
*/
@SuppressWarnings("deprecation")
private PictureListener mPictureListener = new PictureListener() {
@Override
@Deprecated
public void onNewPicture(WebView view, Picture picture) {
// stop listening
setPictureListener(null);
scrollTo(0, (int)(getContentHeight() * mScrollY));
mScrollY = 0.0f;
}
};
}