package me.devsaki.hentoid.abstracts;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.app.AlertDialog;
import android.app.NotificationManager;
import android.app.SearchManager;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.view.GravityCompat;
import android.support.v4.view.MenuItemCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.SearchView;
import android.support.v7.widget.Toolbar;
import android.view.ActionMode;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.TextView;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import me.devsaki.hentoid.HentoidApp;
import me.devsaki.hentoid.R;
import me.devsaki.hentoid.activities.ImportActivity;
import me.devsaki.hentoid.adapters.ContentAdapter;
import me.devsaki.hentoid.adapters.ContentAdapter.ContentsWipedListener;
import me.devsaki.hentoid.database.SearchContent;
import me.devsaki.hentoid.database.SearchContent.ContentListener;
import me.devsaki.hentoid.database.domains.Content;
import me.devsaki.hentoid.events.DownloadEvent;
import me.devsaki.hentoid.listener.ItemClickListener.ItemSelectListener;
import me.devsaki.hentoid.util.ConstsImport;
import me.devsaki.hentoid.util.ConstsPrefs;
import me.devsaki.hentoid.util.FileHelper;
import me.devsaki.hentoid.util.Helper;
import me.devsaki.hentoid.util.LogHelper;
import static me.devsaki.hentoid.util.Helper.DURATION.LONG;
/**
* Created by avluis on 08/27/2016.
* Common elements for use by EndlessFragment and PagerFragment
*/
public abstract class DownloadsFragment extends BaseFragment implements ContentListener,
ContentsWipedListener, ItemSelectListener {
protected static final int SHOW_RESULT = 3;
private static final int SHOW_LOADING = 1;
private static final int SHOW_BLANK = 2;
private static final String LIST_STATE_KEY = "list_state";
private static final String TAG = LogHelper.makeLogTag(DownloadsFragment.class);
protected static String query = "";
private final Handler searchHandler = new Handler();
protected Context mContext;
protected int qtyPages;
protected int currentPage = 1;
protected ContentAdapter mAdapter;
protected LinearLayoutManager llm;
protected RecyclerView mListView;
protected List<Content> contents;
protected List<Content> result = new ArrayList<>();
protected SearchContent search;
protected LinearLayout toolTip;
protected Toolbar toolbar;
protected boolean newContent;
protected boolean override;
protected boolean isLastPage;
protected boolean isLoaded;
private ActionMode mActionMode;
private int mDrawerState;
private DrawerLayout mDrawerLayout;
private boolean shouldHide;
private MenuItem searchMenu;
private Parcelable mListState;
private Button btnPage;
private SearchView searchView;
private SwipeRefreshLayout refreshLayout;
private boolean orderUpdated;
private boolean isSelected;
private boolean selectTrigger = false;
// Called when the action mode is created; startActionMode() was called.
private final ActionMode.Callback mActionModeCallback = new ActionMode.Callback() {
// Called when action mode is first created.
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
// Inflate a menu resource providing context menu items
MenuInflater inflater = mode.getMenuInflater();
inflater.inflate(R.menu.menu_context_menu, menu);
return true;
}
// Called each time the action mode is shown. Always called after onCreateActionMode,
// but may be called multiple times if the mode is invalidated.
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
menu.findItem(R.id.action_delete).setVisible(!selectTrigger);
menu.findItem(R.id.action_share).setVisible(!selectTrigger);
menu.findItem(R.id.action_archive).setVisible(!selectTrigger);
menu.findItem(R.id.action_delete_sweep).setVisible(selectTrigger);
return true;
}
// Called when the user selects a contextual menu item.
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
switch (item.getItemId()) {
case R.id.action_share:
mAdapter.sharedSelectedItems();
mode.finish();
return true;
case R.id.action_delete:
mAdapter.purgeSelectedItems();
mode.finish();
return true;
case R.id.action_archive:
mAdapter.archiveSelectedItems();
mode.finish();
return true;
case R.id.action_delete_sweep:
mAdapter.purgeSelectedItems();
mode.finish();
return true;
default:
return false;
}
}
// Called when the user exits the action mode.
@Override
public void onDestroyActionMode(ActionMode mode) {
clearSelection();
mActionMode = null;
}
};
private SharedPreferences prefs;
private int order;
private long backButtonPressed;
private String settingDir;
private TextView loadingText;
private TextView emptyText;
private boolean permissionChecked;
private ObjectAnimator animator;
@Override
public void onResume() {
super.onResume();
checkPermissions();
if (mListState != null) {
llm.onRestoreInstanceState(mListState);
}
}
// Validate permissions
private void checkPermissions() {
if (Helper.permissionsCheck(getActivity(), ConstsImport.RQST_STORAGE_PERMISSION, true)) {
queryPrefs();
checkResults();
} else {
LogHelper.d(TAG, "Storage permission denied!");
if (permissionChecked) {
reset();
}
permissionChecked = true;
}
}
protected void queryPrefs() {
LogHelper.d(TAG, "Querying Prefs.");
boolean shouldUpdate = false;
if (settingDir.isEmpty()) {
LogHelper.d(TAG, "Where are my files?!");
Intent intent = new Intent(getActivity(), ImportActivity.class);
startActivity(intent);
getActivity().finish();
}
String settingDir = FileHelper.getRoot();
if (!this.settingDir.equals(settingDir)) {
LogHelper.d(TAG, "Library directory has changed!");
this.settingDir = settingDir;
cleanResults();
shouldUpdate = true;
}
if (Helper.isAtLeastAPI(Build.VERSION_CODES.LOLLIPOP)) {
checkStorage();
}
int qtyPages = Integer.parseInt(
prefs.getString(
ConstsPrefs.PREF_QUANTITY_PER_PAGE_LISTS,
ConstsPrefs.PREF_QUANTITY_PER_PAGE_DEFAULT + ""));
if (this.qtyPages != qtyPages) {
LogHelper.d(TAG, "qtyPages updated.");
this.qtyPages = qtyPages;
setQuery("");
shouldUpdate = true;
}
int order = prefs.getInt(
ConstsPrefs.PREF_ORDER_CONTENT_LISTS, ConstsPrefs.PREF_ORDER_CONTENT_ALPHABETIC);
if (this.order != order) {
LogHelper.d(TAG, "order updated.");
orderUpdated = true;
this.order = order;
}
if (shouldUpdate) {
update();
}
}
private void checkStorage() {
if (FileHelper.isSAF()) {
File storage = new File(settingDir);
if (FileHelper.getExtSdCardFolder(storage) == null) {
LogHelper.d(TAG, "Where are my files?!");
Helper.toast(getActivity(),
"Could not find library!\nPlease check your storage device.", LONG);
setQuery(" ");
Handler handler = new Handler();
handler.postDelayed(() -> {
getActivity().finish();
Runtime.getRuntime().exit(0);
}, 3000);
}
checkSDHealth();
}
}
private void checkSDHealth() {
File file = new File(settingDir, "test");
OutputStream output = null;
try {
output = FileHelper.getOutputStream(file);
// build
byte[] bytes = "test".getBytes();
// write
output.write(bytes);
FileHelper.sync(output);
output.flush();
} catch (NullPointerException npe) {
LogHelper.e(TAG, npe, "Invalid Stream");
Helper.toast(R.string.sd_access_error);
new AlertDialog.Builder(getActivity())
.setMessage(R.string.sd_access_fatal_error)
.setTitle("Error!")
.setPositiveButton(android.R.string.ok, null)
.show();
} catch (IOException e) {
LogHelper.e(TAG, e, "IOException while checking SD Health");
} finally {
// finished
if (output != null) {
try {
output.close();
} catch (IOException e) {
// Ignore
}
}
if (file.exists()) {
LogHelper.d(TAG, "Test file removed: " + FileHelper.removeFile(file));
}
}
}
protected abstract void checkResults();
private void reset() {
Helper.reset(HentoidApp.getAppContext(), getActivity());
}
@Override
public void onPause() {
super.onPause();
clearSelection();
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mListState = llm.onSaveInstanceState();
outState.putParcelable(LIST_STATE_KEY, mListState);
}
@Override
public void onViewStateRestored(@Nullable Bundle state) {
super.onViewStateRestored(state);
if (state != null) {
mListState = state.getParcelable(LIST_STATE_KEY);
}
}
@Override
public void onViewCreated(View view, Bundle state) {
super.onViewCreated(view, state);
if (mListState != null) {
mListState = state.getParcelable(LIST_STATE_KEY);
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
setHasOptionsMenu(true);
mContext = getContext();
prefs = HentoidApp.getSharedPrefs();
settingDir = FileHelper.getRoot();
order = prefs.getInt(
ConstsPrefs.PREF_ORDER_CONTENT_LISTS, ConstsPrefs.PREF_ORDER_CONTENT_ALPHABETIC);
qtyPages = Integer.parseInt(
prefs.getString(
ConstsPrefs.PREF_QUANTITY_PER_PAGE_LISTS,
ConstsPrefs.PREF_QUANTITY_PER_PAGE_DEFAULT + ""));
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, final ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_downloads, container, false);
initUI(rootView);
attachScrollListener();
attachOnClickListeners(rootView);
return rootView;
}
private void initUI(View rootView) {
mListView = (RecyclerView) rootView.findViewById(R.id.list);
loadingText = (TextView) rootView.findViewById(R.id.loading);
emptyText = (TextView) rootView.findViewById(R.id.empty);
mListView.setHasFixedSize(true);
llm = new LinearLayoutManager(mContext);
mListView.setLayoutManager(llm);
mAdapter = new ContentAdapter(mContext, result, this);
mListView.setAdapter(mAdapter);
if (mAdapter.getItemCount() == 0) {
mListView.setVisibility(View.GONE);
loadingText.setVisibility(View.VISIBLE);
}
mDrawerLayout = (DrawerLayout) getActivity().findViewById(R.id.drawer_layout);
mDrawerLayout.addDrawerListener(new DrawerLayout.SimpleDrawerListener() {
@Override
public void onDrawerStateChanged(int newState) {
mDrawerState = newState;
getActivity().invalidateOptionsMenu();
}
});
btnPage = (Button) rootView.findViewById(R.id.btnPage);
toolbar = (Toolbar) rootView.findViewById(R.id.downloads_toolbar);
toolTip = (LinearLayout) rootView.findViewById(R.id.tooltip);
refreshLayout = (SwipeRefreshLayout) rootView.findViewById(R.id.swipe_container);
}
protected abstract void attachScrollListener();
private void attachOnClickListeners(View rootView) {
attachPrevious(rootView);
attachNext(rootView);
attachRefresh(rootView);
toolTip.setOnClickListener(v -> commitRefresh());
refreshLayout.setEnabled(false);
refreshLayout.setOnRefreshListener(this::commitRefresh);
}
private void attachPrevious(View rootView) {
ImageButton btnPrevious = (ImageButton) rootView.findViewById(R.id.btnPrevious);
btnPrevious.setOnClickListener(v -> {
if (currentPage > 1 && isLoaded) {
currentPage--;
update();
} else if (qtyPages > 0 && isLoaded) {
Helper.toast(mContext, R.string.not_previous_page);
} else {
LogHelper.d(TAG, R.string.not_limit_per_page);
}
});
}
private void attachNext(View rootView) {
ImageButton btnNext = (ImageButton) rootView.findViewById(R.id.btnNext);
btnNext.setOnClickListener(v -> {
if (qtyPages <= 0) {
LogHelper.d(TAG, R.string.not_limit_per_page);
} else {
if (!isLastPage && isLoaded) {
currentPage++;
update();
} else if (isLastPage) {
Helper.toast(mContext, R.string.not_next_page);
}
}
});
}
protected abstract void attachRefresh(View rootView);
@Override
public boolean onBackPressed() {
// If the drawer is open, back will close it
if (mDrawerLayout != null && mDrawerLayout.isDrawerOpen(GravityCompat.START)) {
mDrawerLayout.closeDrawers();
backButtonPressed = 0;
return false;
}
if (isSelected) {
clearSelection();
backButtonPressed = 0;
return false;
}
if (backButtonPressed + 2000 > System.currentTimeMillis()) {
return true;
} else {
backButtonPressed = System.currentTimeMillis();
Helper.toast(mContext, R.string.press_back_again);
if (llm != null) {
llm.scrollToPositionWithOffset(0, 0);
}
}
if (!query.isEmpty()) {
clearQuery(1);
}
return false;
}
protected void clearQuery(int option) {
LogHelper.d(TAG, "Clearing query with option: " + option);
if (searchView != null && option == 1) {
searchView.clearFocus();
searchView.setIconified(true);
}
setQuery(query = "");
update();
}
protected void commitRefresh() {
toolTip.setVisibility(View.GONE);
refreshLayout.setRefreshing(false);
refreshLayout.setEnabled(false);
newContent = false;
mAdapter.updateContentList();
cleanResults();
update();
resetCount();
}
private void resetCount() {
LogHelper.d(TAG, "Download Count: " + HentoidApp.getDownloadCount());
HentoidApp.setDownloadCount(0);
NotificationManager manager = (NotificationManager) mContext.getSystemService(
Context.NOTIFICATION_SERVICE);
manager.cancel(0);
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onDownloadEvent(DownloadEvent event) {
Double percent = event.percent;
if (percent >= 0) {
LogHelper.d(TAG, "Download Progress: " + percent);
} else if (isLoaded) {
showReloadToolTip();
}
}
@Override
public void onCreateOptionsMenu(final Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.menu_content_list, menu);
// Associate searchable configuration with the SearchView
final SearchManager searchManager = (SearchManager)
mContext.getSystemService(Context.SEARCH_SERVICE);
searchMenu = menu.findItem(R.id.action_search);
MenuItemCompat.setOnActionExpandListener(searchMenu,
new MenuItemCompat.OnActionExpandListener() {
@Override
public boolean onMenuItemActionExpand(MenuItem item) {
toggleSortMenuItem(menu, false);
return true;
}
@Override
public boolean onMenuItemActionCollapse(MenuItem item) {
toggleSortMenuItem(menu, true);
if (!("").equals(query)) {
query = "";
submitSearchQuery(query, 300);
}
return true;
}
});
searchView = (SearchView) MenuItemCompat.getActionView(searchMenu);
searchView.setSearchableInfo(searchManager.getSearchableInfo(
getActivity().getComponentName()));
searchView.setIconifiedByDefault(true);
searchView.setQueryHint(getString(R.string.search_hint));
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String s) {
submitSearchQuery(s);
searchView.clearFocus();
return true;
}
@Override
public boolean onQueryTextChange(String s) {
if (shouldHide && (!s.isEmpty())) {
submitSearchQuery(s, 1000);
}
if (shouldHide && orderUpdated) {
clearQuery(0);
orderUpdated = false;
}
if (!shouldHide && (!s.isEmpty())) {
clearQuery(1);
}
return true;
}
});
SharedPreferences.Editor editor = HentoidApp.getSharedPrefs().edit();
if (order == 0) {
menu.findItem(R.id.action_order_alphabetic).setVisible(false);
menu.findItem(R.id.action_order_by_date).setVisible(true);
} else {
menu.findItem(R.id.action_order_alphabetic).setVisible(true);
menu.findItem(R.id.action_order_by_date).setVisible(false);
}
// Save current sort order
editor.putInt(ConstsPrefs.PREF_ORDER_CONTENT_LISTS, order).apply();
}
@Override
public void onPrepareOptionsMenu(Menu menu) {
boolean drawerOpen = mDrawerLayout.isDrawerOpen(GravityCompat.START);
shouldHide = (mDrawerState != DrawerLayout.STATE_DRAGGING &&
mDrawerState != DrawerLayout.STATE_SETTLING && !drawerOpen);
if (!shouldHide) {
MenuItemCompat.collapseActionView(searchMenu);
menu.findItem(R.id.action_search).setVisible(false);
toggleSortMenuItem(menu, false);
}
}
private void toggleSortMenuItem(Menu menu, boolean show) {
if (order == 0) {
menu.findItem(R.id.action_order_by_date).setVisible(show);
} else {
menu.findItem(R.id.action_order_alphabetic).setVisible(show);
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_order_alphabetic:
cleanResults();
orderUpdated = true;
order = ConstsPrefs.PREF_ORDER_CONTENT_ALPHABETIC;
update();
getActivity().invalidateOptionsMenu();
return true;
case R.id.action_order_by_date:
cleanResults();
orderUpdated = true;
order = ConstsPrefs.PREF_ORDER_CONTENT_BY_DATE;
update();
getActivity().invalidateOptionsMenu();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
private void submitSearchQuery(String s) {
submitSearchQuery(s, 0);
}
private void submitSearchQuery(final String s, long delay) {
query = s;
searchHandler.removeCallbacksAndMessages(null);
searchHandler.postDelayed(() -> {
setQuery(s);
cleanResults();
update();
}, delay);
}
protected void checkContent(boolean clear) {
if (clear) {
resetCount();
} else {
if (HentoidApp.getDownloadCount() > 0) {
if (isLoaded) {
showReloadToolTip();
}
} else {
setCurrentPage();
showToolbar(true, false);
}
}
}
private void showReloadToolTip() {
if (toolTip.getVisibility() == View.GONE) {
toolTip.setVisibility(View.VISIBLE);
refreshLayout.setEnabled(true);
newContent = true;
} else {
LogHelper.d(TAG, "Tooltip visible.");
}
}
private void cleanResults() {
if (contents != null) {
contents.clear();
contents = null;
}
if (result != null) {
result.clear();
result = null;
}
if (mAdapter != null) {
mAdapter.notifyDataSetChanged();
}
currentPage = 1;
}
private void setQuery(String query) {
DownloadsFragment.query = query;
currentPage = 1;
}
private void clearSelection() {
if (mAdapter != null) {
if (mListView.getScrollState() == RecyclerView.SCROLL_STATE_IDLE) {
mAdapter.clearSelections();
selectTrigger = false;
showToolbar(true, false);
}
isSelected = false;
}
}
protected void update() {
toggleUI(SHOW_LOADING);
searchContent();
setCurrentPage();
}
private void setCurrentPage() {
btnPage.setText(String.valueOf(currentPage));
}
protected void toggleUI(int mode) {
switch (mode) {
case SHOW_LOADING:
mListView.setVisibility(View.GONE);
emptyText.setVisibility(View.GONE);
loadingText.setVisibility(View.VISIBLE);
showToolbar(false, false);
startAnimation();
break;
case SHOW_BLANK:
mListView.setVisibility(View.GONE);
emptyText.setVisibility(View.VISIBLE);
loadingText.setVisibility(View.GONE);
showToolbar(false, false);
break;
case SHOW_RESULT:
mListView.setVisibility(View.VISIBLE);
emptyText.setVisibility(View.GONE);
loadingText.setVisibility(View.GONE);
showToolbar(true, false);
break;
default:
stopAnimation();
loadingText.setVisibility(View.GONE);
break;
}
}
private void startAnimation() {
final int POWER_LEVEL = 9000;
Drawable[] compoundDrawables = loadingText.getCompoundDrawables();
for (Drawable drawable : compoundDrawables) {
if (drawable == null) {
continue;
}
animator = ObjectAnimator.ofInt(drawable, "level", 0, POWER_LEVEL);
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.setRepeatMode(ValueAnimator.REVERSE);
animator.start();
}
}
private void stopAnimation() {
Drawable[] compoundDrawables = loadingText.getCompoundDrawables();
for (Drawable drawable : compoundDrawables) {
if (drawable == null) {
continue;
}
animator.cancel();
}
}
protected void searchContent() {
isLoaded = false;
search = new SearchContent(mContext, query, currentPage, qtyPages,
order == ConstsPrefs.PREF_ORDER_CONTENT_BY_DATE);
search.retrieveResults(this);
}
protected abstract void showToolbar(boolean show, boolean override);
@Override
public void onContentReady(boolean success) {
if (success) {
LogHelper.d(TAG, "Content results have loaded.");
isLoaded = true;
if (search.getContent() == null) {
LogHelper.d(TAG, "Result: Nothing to match.");
displayNoResults();
} else {
displayResults();
}
}
}
protected abstract void displayResults();
protected void updatePager() {
// TODO: Test if result.size == qtyPages (meaning; last page, exact size)
isLastPage = result.size() < qtyPages;
LogHelper.d(TAG, "Results: " + result.size());
}
protected void displayNoResults() {
if (isLoaded && !("").equals(query)) {
emptyText.setText(R.string.search_entry_not_found);
toggleUI(SHOW_BLANK);
} else if (isLoaded) {
emptyText.setText(R.string.downloads_empty);
toggleUI(SHOW_BLANK);
} else {
LogHelper.w(TAG, "Why are we in here?");
}
}
@Override
public void onContentFailed(boolean failure) {
if (failure) {
LogHelper.d(TAG, "Content results failed to load.");
isLoaded = false;
}
}
@Override
public void onItemSelected(int selectedCount) {
isSelected = true;
showToolbar(false, true);
if (selectedCount == 1) {
mAdapter.notifyDataSetChanged();
}
if (selectedCount >= 2) {
selectTrigger = true;
}
if (mActionMode == null) {
mActionMode = toolbar.startActionMode(mActionModeCallback);
}
if (mActionMode != null) {
mActionMode.invalidate();
mActionMode.setTitle(
selectedCount + (selectedCount > 1 ? " items selected" : " item selected"));
}
}
@Override
public void onItemClear(int itemCount) {
if (mActionMode != null) {
if (itemCount >= 1) {
mActionMode.setTitle(
itemCount + (itemCount > 1 ? " items selected" : " item selected"));
} else {
selectTrigger = false;
mActionMode.invalidate();
mActionMode.setTitle("");
mAdapter.notifyDataSetChanged();
}
}
if (itemCount == 1 && selectTrigger) {
selectTrigger = false;
if (mActionMode != null) {
mActionMode.invalidate();
}
}
if (itemCount < 1) {
clearSelection();
showToolbar(true, false);
if (mActionMode != null) {
mActionMode.finish();
}
}
}
@Override
public void onContentsWiped() {
LogHelper.d(TAG, "All items cleared!");
displayNoResults();
clearSelection();
cleanResults();
}
}