package pro.dbro.glance;
import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.UriPermission;
import android.net.Uri;
import android.os.Build;
import android.text.Html;
import android.util.Log;
import android.widget.TextView;
import android.widget.Toast;
import com.parse.FindCallback;
import com.parse.ParseException;
import com.parse.ParseObject;
import com.parse.ParseQuery;
import com.squareup.otto.Bus;
import java.util.List;
import pro.dbro.glance.events.HttpUrlParsedEvent;
import pro.dbro.glance.events.NextChapterEvent;
import pro.dbro.glance.formats.Epub;
import pro.dbro.glance.formats.HtmlPage;
import pro.dbro.glance.formats.SpritzerMedia;
import pro.dbro.glance.formats.UnsupportedFormatException;
import pro.dbro.glance.lib.Spritzer;
/**
* A higher-level {@link pro.dbro.glance.lib.Spritzer} that operates
* on Uris pointing to .epubs on disk or http urls, instead
* of a plain String
*/
// TODO: Save State for multiple books
public class AppSpritzer extends Spritzer {
public static final boolean VERBOSE = true;
public static final int SPECIAL_MESSAGE_WPM = 100;
private int mChapter;
private SpritzerMedia mMedia;
private Uri mMediaUri;
private boolean mSpritzingSpecialMessage;
public AppSpritzer(Bus bus, TextView target) {
super(target);
setEventBus(bus);
restoreState(true);
}
public AppSpritzer(Bus bus, TextView target, Uri mediaUri) {
super(target);
setEventBus(bus);
openMedia(mediaUri);
}
public void setMediaUri(Uri uri) {
pause();
openMedia(uri);
}
private void openMedia(Uri uri) {
if (isHttpUri(uri)) {
openHtmlPage(uri);
} else {
openEpub(uri);
}
}
private void openEpub(Uri epubUri) {
try {
mMediaUri = epubUri;
mMedia = Epub.fromUri(mTarget.getContext(), mMediaUri);
restoreState(false);
} catch (UnsupportedFormatException e) {
reportFileUnsupported();
}
}
private void openHtmlPage(Uri htmlUri) {
try {
mMediaUri = htmlUri;
mMedia = HtmlPage.fromUri(mTarget.getContext().getApplicationContext(), htmlUri.toString(), new HtmlPage.HtmlPageParsedCallback() {
@Override
public void onPageParsed(HtmlPage result) {
restoreState(false);
if (mBus != null) {
mBus.post(new HttpUrlParsedEvent(result));
}
}
});
} catch (UnsupportedFormatException e) {
reportFileUnsupported();
}
}
public boolean isSpritzingSpecialMessage() {
return mSpritzingSpecialMessage;
}
public SpritzerMedia getMedia() {
return mMedia;
}
public void printChapter(int chapter) {
mChapter = chapter;
setText(loadCleanStringFromChapter(mChapter));
saveState();
}
public int getCurrentChapter() {
return mChapter;
}
public int getMaxChapter() {
return (mMedia == null) ? 0 : mMedia.countChapters() - 1;
}
public boolean isMediaSelected() {
return mMedia != null;
}
protected void processNextWord() throws InterruptedException {
super.processNextWord();
if (mPlaying && mPlayingRequested && isWordListComplete() && mChapter < getMaxChapter()) {
// If we are Spritzing a special message, don't automatically proceed to the next chapter
if (mSpritzingSpecialMessage) {
mSpritzingSpecialMessage = false;
return;
}
while (isWordListComplete() && mChapter < getMaxChapter()) {
printNextChapter();
if (mBus != null) {
mBus.post(new NextChapterEvent(mChapter));
}
}
}
}
private void printNextChapter() {
setText(loadCleanStringFromChapter(mChapter++));
saveState();
if (VERBOSE)
Log.i(TAG, "starting next chapter: " + mChapter + " length " + mDisplayWordList.size());
}
/**
* Load the given chapter as sanitized text, proceeding
* to the next chapter until a non-zero length result is found.
*
* This method is useful because some "Chapters" contain only HTML data
* that isn't useful to a Spritzer.
*
* @param chapter the first chapter to load
* @return the sanitized text of the first non-zero length chapter
*/
private String loadCleanStringFromNextNonEmptyChapter(int chapter) {
int chapterToTry = chapter;
String result = "";
while(result.length() == 0 && chapterToTry <= getMaxChapter()) {
result = loadCleanStringFromChapter(chapterToTry);
chapterToTry++;
}
return result;
}
/**
* Load the given chapter as sanitized text.
*
* @param chapter the target chapter.
* @return the sanitized chapter text.
*/
private String loadCleanStringFromChapter(int chapter) {
return mMedia.loadChapter(chapter);
}
public void saveState() {
if (mMedia != null) {
if (VERBOSE) Log.i(TAG, "Saving state at chapter " + mChapter + " word: " + mCurWordIdx);
GlancePrefsManager.saveState(
mTarget.getContext(),
mChapter,
mMediaUri.toString(),
mCurWordIdx,
mMedia.getTitle(),
mWPM);
}
}
@SuppressLint("NewApi")
private void restoreState(boolean openLastMediaUri) {
final GlancePrefsManager.SpritzState state = GlancePrefsManager.getState(mTarget.getContext());
String content = "";
if (openLastMediaUri) {
// Open the last selected media
if (state.hasUri()) {
Uri mediaUri = state.getUri();
if (Build.VERSION.SDK_INT >= 19 && !isHttpUri(mediaUri)) {
boolean uriPermissionPersisted = false;
List<UriPermission> uriPermissions = mTarget.getContext().getContentResolver().getPersistedUriPermissions();
for (UriPermission permission : uriPermissions) {
if (permission.getUri().equals(mediaUri)) {
uriPermissionPersisted = true;
openMedia(mediaUri);
break;
}
}
if (!uriPermissionPersisted) {
Log.w(TAG, String.format("Permission not persisted for uri: %s. Clearing SharedPreferences ", mediaUri.toString()));
GlancePrefsManager.clearState(mTarget.getContext());
return;
}
} else {
openMedia(mediaUri);
}
}
} else if (state.hasTitle() && mMedia.getTitle().compareTo(state.getTitle()) == 0) {
// Resume media at previous point
mChapter = state.getChapter();
content = loadCleanStringFromNextNonEmptyChapter(mChapter);
setWpm(state.getWpm());
mCurWordIdx = state.getWordIdx();
if (VERBOSE) Log.i(TAG, "Resuming " + mMedia.getTitle() + " from chapter " + mChapter + " word " + mCurWordIdx);
} else {
// Begin content anew
mChapter = 0;
mCurWordIdx = 0;
setWpm(state.getWpm());
content = loadCleanStringFromNextNonEmptyChapter(mChapter);
}
final String finalContent = content;
if (!mPlaying && finalContent.length() > 0) {
setWpm(SPECIAL_MESSAGE_WPM);
// Set mSpritzingSpecialMessage to true, so processNextWord doesn't
// automatically proceed to the next chapter
mSpritzingSpecialMessage = true;
mTarget.setEnabled(false);
setTextAndStart(mTarget.getContext().getString(R.string.touch_to_start), new SpritzerCallback() {
@Override
public void onSpritzerFinished() {
setText(finalContent);
setWpm(state.getWpm());
mSpritzHandler.sendMessage(mSpritzHandler.obtainMessage(MSG_SET_ENABLED));
}
}, false);
}
}
private void reportFileUnsupported() {
Toast.makeText(mTarget.getContext(), mTarget.getContext().getString(R.string.unsupported_file), Toast.LENGTH_LONG).show();
}
public static boolean isHttpUri(Uri uri) {
return uri.getScheme() != null && uri.getScheme().contains("http");
}
/**
* Return a String representing the maxChars most recently
* Spritzed characters.
*
* @param maxChars The max number of characters to return. Pass a value less than 1 for no limit.
* @return The maxChars number of most recently spritzed characters during this segment
*/
public String getHistoryString(int maxChars) {
if (maxChars <= 0) maxChars = Integer.MAX_VALUE;
if (mCurWordIdx < 2 || mDisplayWordList.size() < 2) return "";
StringBuilder builder = new StringBuilder();
int numWords = 0;
while (builder.length() + mDisplayWordList.get(mCurWordIdx - (numWords + 2)).length() < maxChars) {
builder.insert(0, mDisplayWordList.get(mCurWordIdx - (numWords + 2)) + " ");
numWords++;
if (mCurWordIdx - (numWords + 2) < 0) break;
}
return builder.toString();
}
}