/**
* *******************************************************************************
* Copyright 2016 Tom Praschan
* <p/>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p/>
* http://www.apache.org/licenses/LICENSE-2.0
* <p/>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ******************************************************************************
*/
package de.tap.easy_xkcd.fragments.comics;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.RectF;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Vibrator;
import android.support.customtabs.CustomTabsIntent;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.util.SparseArray;
import android.view.GestureDetector;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.kogitune.activity_transition.ExitActivityTransition;
import com.tap.xkcd_reader.R;
import java.io.File;
import java.io.FileOutputStream;
import java.lang.reflect.Field;
import java.util.Arrays;
import de.tap.easy_xkcd.Activities.MainActivity;
import de.tap.easy_xkcd.Activities.SearchResultsActivity;
import de.tap.easy_xkcd.CustomTabHelpers.BrowserFallback;
import de.tap.easy_xkcd.CustomTabHelpers.CustomTabActivityHelper;
import de.tap.easy_xkcd.database.DatabaseManager;
import de.tap.easy_xkcd.fragments.overview.OverviewListFragment;
import de.tap.easy_xkcd.misc.HackyViewPager;
import de.tap.easy_xkcd.utils.Comic;
import de.tap.easy_xkcd.utils.PrefHelper;
import de.tap.easy_xkcd.utils.ThemePrefs;
import uk.co.senab.photoview.PhotoView;
import uk.co.senab.photoview.PhotoViewAttacher;
/**
* Superclass for ComicBrowserFragment, OfflineFragment & FavoritesFragment
*/
public abstract class ComicFragment extends android.support.v4.app.Fragment {
public int lastComicNumber;
public int newestComicNumber;
public int favoriteIndex = 0;
public SparseArray<Comic> comicMap = new SparseArray<>();
protected HackyViewPager pager;
protected ComicAdapter adapter;
public static boolean fromSearch = false;
static final String LAST_FAV = "last fav";
static final String LAST_COMIC = "Last Comic";
protected PrefHelper prefHelper;
protected ThemePrefs themePrefs;
protected DatabaseManager databaseManager;
public ExitActivityTransition transition;
protected View inflateLayout(int resId, LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(resId, container, false);
setHasOptionsMenu(true);
pager = (HackyViewPager) view.findViewById(R.id.pager);
pager.setOffscreenPageLimit(2);
prefHelper = ((MainActivity) getActivity()).getPrefHelper();
themePrefs = ((MainActivity) getActivity()).getThemePrefs();
databaseManager = ((MainActivity) getActivity()).getDatabaseManager();
if (!(this instanceof FavoritesFragment)) {
if (savedInstanceState != null) {
lastComicNumber = savedInstanceState.getInt(LAST_COMIC);
} else if (lastComicNumber == 0) {
lastComicNumber = prefHelper.getLastComic();
}
if (MainActivity.overviewLaunch && !SearchResultsActivity.isOpen
) {
MainActivity.overviewLaunch = false;
((MainActivity) getActivity()).showOverview(false);
}
}
if (((MainActivity) getActivity()).getCurrentFragment() == R.id.nav_browser && prefHelper.subtitleEnabled() && (this instanceof ComicBrowserFragment || this instanceof OfflineFragment))
((MainActivity) getActivity()).getToolbar().setSubtitle(String.valueOf(lastComicNumber));
return view;
}
abstract protected class ComicAdapter extends PagerAdapter {
protected Context mContext;
protected LayoutInflater mLayoutInflater;
protected Boolean fingerLifted = true;
protected int count;
public ComicAdapter(Context context, int count) {
this.count = count;
mContext = context;
mLayoutInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
protected View setupPager(ViewGroup container, int position) {
final View itemView = mLayoutInflater.inflate(R.layout.pager_item, container, false);
itemView.setTag(position);
final PhotoView pvComic = (PhotoView) itemView.findViewById(R.id.ivComic);
final TextView tvAlt = (TextView) itemView.findViewById(R.id.tvAlt);
if (!prefHelper.defaultZoom()) {
pvComic.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
pvComic.setMaximumScale(10f);
}
if (prefHelper.altByDefault())
tvAlt.setVisibility(View.VISIBLE);
pvComic.setOnDoubleTapListener(new GestureDetector.OnDoubleTapListener() {
@Override
public boolean onDoubleTap(MotionEvent e) {
if (pvComic.getScale() < 0.5f * pvComic.getMaximumScale()) {
pvComic.setScale(0.5f * pvComic.getMaximumScale(), true);
} else if (pvComic.getScale() < pvComic.getMaximumScale()) {
pvComic.setScale(pvComic.getMaximumScale(), true);
} else {
pvComic.setScale(1.0f, true);
}
return true;
}
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
if (!prefHelper.altLongTap()) {
if (prefHelper.classicAltStyle()) {
toggleVisibility(tvAlt);
} else {
android.support.v7.app.AlertDialog.Builder mDialog = new android.support.v7.app.AlertDialog.Builder(getActivity());
mDialog.setMessage(tvAlt.getText());
mDialog.show();
}
}
return false;
}
@Override
public boolean onDoubleTapEvent(MotionEvent e) {
if (e.getAction() == MotionEvent.ACTION_UP)
fingerLifted = true;
if (e.getAction() == MotionEvent.ACTION_DOWN)
fingerLifted = false;
return false;
}
});
pvComic.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
if (fingerLifted && prefHelper.altLongTap()) {
if (prefHelper.altVibration())
((Vibrator) getActivity().getSystemService(MainActivity.VIBRATOR_SERVICE)).vibrate(10);
setAltText(false);
}
return true;
}
});
if (themePrefs.invertColors(false))
pvComic.setColorFilter(themePrefs.getNegativeColorFilter());
if (prefHelper.scrollDisabledWhileZoom() && prefHelper.defaultZoom())
pvComic.setOnMatrixChangeListener(new PhotoViewAttacher.OnMatrixChangedListener() {
@Override
public void onMatrixChanged(RectF rectF) {
if (pvComic.getScale() > 1.4) {
pager.setLocked(true);
} else {
pager.setLocked(false);
}
}
});
return itemView;
}
protected int getGifId(int position) {
switch (position + 1) {
case 961:
return R.raw.eternal_flame;
case 1116:
return R.raw.traffic_lights;
case 1264:
return R.raw.slideshow;
default:
return 0;
}
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((RelativeLayout) object);
}
}
protected void animateToolbar() {
Toolbar toolbar = ((MainActivity) getActivity()).getToolbar();
if (toolbar.getAlpha() == 0) {
toolbar.setTranslationY(-300);
toolbar.animate().setDuration(300).translationY(0).alpha(1);
View view;
for (int i = 0; i < toolbar.getChildCount(); i++) {
view = toolbar.getChildAt(i);
view.setTranslationY(-300);
view.animate().setStartDelay(50 * (i + 1)).setDuration(70 * (i + 1)).translationY(0);
}
}
}
protected void saveComic(int number, Bitmap bitmap) {
try {
File sdCard = prefHelper.getOfflinePath();
File dir = new File(sdCard.getAbsolutePath() + "/easy xkcd");
//noinspection ResultOfMethodCallIgnored
dir.mkdirs();
File file = new File(dir, String.valueOf(number) + ".png");
FileOutputStream fos = new FileOutputStream(file);
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
fos.flush();
fos.close();
} catch (Exception e) {
Log.e("Error", "Saving to external storage failed");
try {
FileOutputStream fos = getActivity().openFileOutput(String.valueOf(number), Context.MODE_PRIVATE);
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
fos.close();
} catch (Exception e2) {
e2.printStackTrace();
}
}
}
public class SaveComicImageTask extends AsyncTask<Boolean, Void, Void> {
protected int mAddedNumber = lastComicNumber;
private Bitmap mBitmap;
private Comic mAddedComic;
private boolean downloadImage;
@Override
protected Void doInBackground(Boolean... downloadImage) {
this.downloadImage = downloadImage[0];
if (this.downloadImage) {
mAddedComic = comicMap.get(lastComicNumber);
try {
String url = mAddedComic.getComicData()[2];
mBitmap = Glide
.with(getActivity())
.load(url)
.asBitmap()
.diskCacheStrategy(DiskCacheStrategy.SOURCE)
.into(-1, -1)
.get();
} catch (Exception e) {
databaseManager.setFavorite(mAddedNumber, false);
Log.e("Saving Image failed!", e.toString());
}
prefHelper.addTitle(mAddedComic.getComicData()[0], mAddedNumber);
prefHelper.addAlt(mAddedComic.getComicData()[1], mAddedNumber);
}
databaseManager.setFavorite(mAddedNumber, true);
return null;
}
@Override
protected void onPostExecute(Void dummy) {
if (downloadImage) {
saveComic(mAddedNumber, mBitmap);
}
//refresh the FavoritesFragment
FavoritesFragment f = (FavoritesFragment) getActivity().getSupportFragmentManager().findFragmentByTag("favorites");
if (f != null)
f.refresh();
//Sometimes the floating action button does not animate back to the bottom when the snackbar is dismissed, so force it to its original position
((MainActivity) getActivity()).getFab().forceLayout();
getActivity().invalidateOptionsMenu();
}
}
protected class DeleteComicImageTask extends AsyncTask<Boolean, Void, Void> {
protected int mRemovedNumber = lastComicNumber;
protected View.OnClickListener oc;
@Override
protected Void doInBackground(final Boolean... deleteImage) {
if (deleteImage[0]) {
//delete the image from internal storage
getActivity().deleteFile(String.valueOf(mRemovedNumber));
//delete from external storage
File sdCard = prefHelper.getOfflinePath();
File dir = new File(sdCard.getAbsolutePath() + "/easy xkcd");
File file = new File(dir, String.valueOf(mRemovedNumber) + ".png");
//noinspection ResultOfMethodCallIgnored
file.delete();
prefHelper.addTitle("", mRemovedNumber);
prefHelper.addAlt("", mRemovedNumber);
}
oc = new View.OnClickListener() {
@Override
public void onClick(View v) {
new SaveComicImageTask().execute(deleteImage[0]);
}
};
databaseManager.setFavorite(mRemovedNumber, false);
Snackbar.make(((MainActivity) getActivity()).getFab(), R.string.snackbar_remove, Snackbar.LENGTH_LONG)
.setAction(R.string.snackbar_undo, oc)
.show();
return null;
}
@Override
protected void onPostExecute(Void v) {
//refresh the favorites fragment
FavoritesFragment f = (FavoritesFragment) getActivity().getSupportFragmentManager().findFragmentByTag("favorites");
if (f != null)
f.refresh();
}
}
public boolean zoomReset() {
int index;
if (this instanceof FavoritesFragment)
index = favoriteIndex;
else
index = lastComicNumber - 1;
if (prefHelper.altBackButton() && !(pager.findViewWithTag(index).findViewById(R.id.tvAlt).getVisibility() == View.VISIBLE))
return setAltText(false);
try {
PhotoView pv = (PhotoView) pager.findViewWithTag(index).findViewById(R.id.ivComic);
float scale = pv.getScale();
if (scale != 1f) {
pv.setScale(1f, true);
return true;
} else {
return false;
}
} catch (NullPointerException e) {
Log.e("error", "pv nullPointer");
return false;
}
}
protected boolean explainComic(int number) {
String url = "https://explainxkcd.com/" + String.valueOf(number);
CustomTabsIntent.Builder intentBuilder = new CustomTabsIntent.Builder();
intentBuilder.setToolbarColor(themePrefs.getPrimaryColor(false));
CustomTabActivityHelper.openCustomTab(getActivity(), intentBuilder.build(), Uri.parse(url), new BrowserFallback());
return true;
}
protected boolean openComicInBrowser(int number) {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://xkcd.com/" + String.valueOf(number)));
startActivity(intent);
return true;
}
protected boolean showTranscript(String trans) {
android.support.v7.app.AlertDialog.Builder mDialog = new android.support.v7.app.AlertDialog.Builder(getActivity());
mDialog.setMessage(trans);
mDialog.show();
return true;
}
protected boolean addBookmark(int bookmark) {
if (prefHelper.getBookmark() == 0)
Toast.makeText(getActivity(), R.string.bookmark_toast, Toast.LENGTH_LONG).show();
prefHelper.setBookmark(bookmark);
OverviewListFragment.bookmark = bookmark;
return true;
}
protected void shareComicUrl(Comic comic) {
//shares the comics url along with its title
Intent share = new Intent(android.content.Intent.ACTION_SEND);
share.setType("text/plain");
share.putExtra(Intent.EXTRA_SUBJECT, comic.getComicData()[0]);
share.putExtra(Intent.EXTRA_TEXT, " https://" + (prefHelper.shareMobile() ? "m." : "") + "xkcd.com/" + comic.getComicNumber() + "/");
startActivity(Intent.createChooser(share, this.getResources().getString(R.string.share_url)));
}
protected void shareComicImage(Uri uri, Comic comic) {
Intent share = new Intent(Intent.ACTION_SEND);
share.setType("image/*");
share.putExtra(Intent.EXTRA_STREAM, uri);
share.putExtra(Intent.EXTRA_SUBJECT, comic.getComicData()[0]);
String extraText = prefHelper.shareAlt() ? comic.getComicData()[1] : "";
if (prefHelper.includeLink())
extraText += " https://" + (prefHelper.shareMobile() ? "m." : "") + "xkcd.com/" + comic.getComicNumber() + "/";
if (!extraText.equals(""))
share.putExtra(Intent.EXTRA_TEXT, extraText);
startActivity(Intent.createChooser(share, this.getResources().getString(R.string.share_image)));
}
protected Uri getURI(int number) {
File sdCard = prefHelper.getOfflinePath();
File dir = new File(sdCard.getAbsolutePath() + "/easy xkcd");
File path = new File(dir, String.valueOf(number) + ".png");
return Uri.fromFile(path);
}
public void getPreviousRandom() {
lastComicNumber = prefHelper.getPreviousRandom(lastComicNumber);
pager.setCurrentItem(lastComicNumber - 1, false);
}
public boolean getRandomComic() {
lastComicNumber = prefHelper.getRandomNumber(lastComicNumber);
pager.setCurrentItem(lastComicNumber - 1, false);
return true;
}
protected boolean getLatestComic() {
lastComicNumber = newestComicNumber;
pager.setCurrentItem(lastComicNumber - 1, false);
return true;
}
protected void scrollViewPager() {
if (lastComicNumber != 0) {
try {
Field field = ViewPager.class.getDeclaredField("mRestoredCurItem");
field.setAccessible(true);
field.set(pager, lastComicNumber - 1);
} catch (Exception e) {
e.printStackTrace();
}
}
}
abstract public void updatePager();
protected boolean setAltText(boolean fromMenu) {
//If the user selected the menu item for the first time, show the toast
if (prefHelper.showAltTip() && fromMenu) {
Toast toast = Toast.makeText(getActivity(), R.string.action_alt_tip, Toast.LENGTH_LONG);
toast.show();
prefHelper.setAltTip(false);
}
//Show alt text
int index;
if (this instanceof FavoritesFragment)
index = favoriteIndex;
else
index = lastComicNumber - 1;
TextView tvAlt = (TextView) pager.findViewWithTag(index).findViewById(R.id.tvAlt);
if (prefHelper.classicAltStyle()) {
toggleVisibility(tvAlt);
} else {
android.support.v7.app.AlertDialog.Builder mDialog = new android.support.v7.app.AlertDialog.Builder(getActivity());
mDialog.setMessage(tvAlt.getText());
mDialog.show();
}
return true;
}
protected void toggleVisibility(View view) {
// Switches a view's visibility between GONE and VISIBLE
if (view.getVisibility() == View.GONE) {
view.setVisibility(View.VISIBLE);
} else {
view.setVisibility(View.GONE);
}
}
protected void pageSelected(int position) {
try {
getActivity().invalidateOptionsMenu();
} catch (NullPointerException e) {
e.printStackTrace();
}
databaseManager.setRead(position + 1, true);
lastComicNumber = position + 1;
if (prefHelper.subtitleEnabled() && ((MainActivity) getActivity()).getCurrentFragment() == R.id.nav_browser)
((MainActivity) getActivity()).getToolbar().setSubtitle(String.valueOf(lastComicNumber));
animateToolbar();
}
@Override
public void onStop() {
if (!(this instanceof FavoritesFragment))
prefHelper.setLastComic(lastComicNumber);
super.onStop();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_alt:
return setAltText(true);
case R.id.action_explain:
return explainComic(lastComicNumber);
case R.id.action_latest:
return getLatestComic();
case R.id.action_browser:
return openComicInBrowser(lastComicNumber);
case R.id.action_trans:
return showTranscript(comicMap.get(lastComicNumber).getTranscript());
case R.id.action_boomark:
return addBookmark(lastComicNumber);
}
return super.onOptionsItemSelected(item);
}
@Override
public void onPrepareOptionsMenu(Menu menu) {
//Update the favorites icon
MenuItem fav = menu.findItem(R.id.action_favorite);
if (databaseManager.checkFavorite(lastComicNumber)) {
fav.setIcon(R.drawable.ic_action_favorite);
fav.setTitle(R.string.action_favorite_remove);
} else {
fav.setIcon(R.drawable.ic_favorite_outline);
fav.setTitle(R.string.action_favorite);
}
//If the FAB is visible, hide the random comic menu item
if (getActivity() != null) {
FloatingActionButton fab = ((MainActivity) getActivity()).getFab();
menu.findItem(R.id.action_random).setVisible(fab != null && fab.getVisibility() == View.GONE);
}
menu.findItem(R.id.action_alt).setVisible(prefHelper.showAltTip());
if (Arrays.binarySearch(getResources().getIntArray(R.array.interactive_comics), lastComicNumber) >= 0)
menu.findItem(R.id.action_browser).setVisible(true);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
if (!(this instanceof FavoritesFragment))
inflater.inflate(R.menu.menu_comic_fragment, menu);
super.onCreateOptionsMenu(menu, inflater);
}
@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
savedInstanceState.putInt(LAST_COMIC, lastComicNumber);
savedInstanceState.putInt(LAST_FAV, favoriteIndex);
super.onSaveInstanceState(savedInstanceState);
}
public void scrollTo(int pos, boolean smooth) {
pager.setCurrentItem(pos, smooth);
}
}