package com.totsp.crossword; import android.Manifest; import android.app.AlertDialog; import android.app.Dialog; import android.app.NotificationManager; import android.app.ProgressDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.PackageManager; import android.content.res.Configuration; import android.graphics.Color; import android.net.Uri; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.support.annotation.NonNull; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; import android.support.v7.view.ActionMode; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.helper.ItemTouchHelper; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.SubMenu; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.ListView; import android.widget.TextView; import android.support.design.widget.FloatingActionButton; import com.totsp.crossword.firstrun.FirstrunActivity; import com.totsp.crossword.io.IO; import com.totsp.crossword.net.Downloader; import com.totsp.crossword.net.Downloaders; import com.totsp.crossword.net.Scrapers; import com.totsp.crossword.puz.Puzzle; import com.totsp.crossword.puz.PuzzleMeta; import com.totsp.crossword.shortyz.R; import com.totsp.crossword.shortyz.ShortyzApplication; import com.totsp.crossword.view.CircleProgressBar; import com.totsp.crossword.view.recycler.RecyclerItemClickListener; import com.totsp.crossword.view.recycler.RemovableRecyclerViewAdapter; import com.totsp.crossword.view.recycler.SeparatedRecyclerViewAdapter; import com.totsp.crossword.view.recycler.ShowHideOnScroll; import java.io.File; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.logging.Logger; @SuppressWarnings("SimpleDateFormat") public class BrowseActivity extends ShortyzActivity implements RecyclerItemClickListener.OnItemClickListener{ private static final String MENU_ARCHIVES = "Archives"; private static final int DOWNLOAD_DIALOG_ID = 0; private static final int REQUEST_WRITE_STORAGE = 1002; private static final long DAY = 24L * 60L * 60L * 1000L; private static final Logger LOGGER = Logger.getLogger(BrowseActivity.class.getCanonicalName()); private Accessor accessor = Accessor.DATE_DESC; private SeparatedRecyclerViewAdapter currentAdapter = null; private Dialog downloadDialog; private File archiveFolder = new File(Environment.getExternalStorageDirectory(), "crosswords/archive"); private File contextFile; private File crosswordsFolder = new File(Environment.getExternalStorageDirectory(), "crosswords"); private FileHandle lastOpenedHandle = null; private Handler handler = new Handler(); private List<String> sourceList = new ArrayList<String>(); private RecyclerView puzzleList; private ListView sources; private NotificationManager nm; private View lastOpenedView = null; private boolean viewArchive; private MenuItem gamesItem; private boolean signedIn; private boolean hasWritePermissions; private int playIcon = R.drawable.ic_play_games_badge_green; private FloatingActionButton download; private int highlightColor; private int normalColor; private HashSet<FileHandle> selected = new HashSet<>(); private ActionMode actionMode; private final ActionMode.Callback actionModeCallback = new ActionMode.Callback() { @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { //4 actionMode = mode; MenuItem item = menu.add("Delete"); item.setIcon(android.R.drawable.ic_menu_delete); utils.onActionBarWithText(item); item = menu.add(viewArchive ? "Un-archive" : "Archive"); utils.onActionBarWithText(item); item.setIcon(R.drawable.ic_action_remove); download.setVisibility(View.GONE); return true; } @Override public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) { return false; } @Override public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) { if(menuItem.getTitle().equals("Delete")){ for(FileHandle handle : selected){ deleteFile(handle.file); } puzzleList.invalidate(); actionMode.finish(); } else if(menuItem.getTitle().equals("Archive")){ for(FileHandle handle : selected){ moveTo(handle.file, archiveFolder); } puzzleList.invalidate(); actionMode.finish(); } else if(menuItem.getTitle().equals("Un-archive")){ for(FileHandle handle : selected){ moveTo(handle.file, crosswordsFolder); } puzzleList.invalidate(); actionMode.finish(); } return true; } @Override public void onDestroyActionMode(ActionMode mode) { selected.clear(); render(); download.setVisibility(View.VISIBLE); actionMode = null; } }; private int primaryTextColor; @Override public boolean onContextItemSelected(MenuItem item) { File meta = new File(this.contextFile.getParent(), contextFile.getName().substring(0, contextFile.getName().lastIndexOf(".")) + ".shortyz"); if (item.getTitle() .equals("Delete")) { this.contextFile.delete(); if (meta.exists()) { meta.delete(); } render(); return true; } else if (item.getTitle() .equals("Archive")) { this.archiveFolder.mkdirs(); moveTo(contextFile, archiveFolder); render(); return true; } else if (item.getTitle() .equals("Un-archive")) { moveTo(contextFile, crosswordsFolder); render(); return true; } else if ("Mark as Updated".equals(item.getTitle())) { try { Puzzle p = IO.load(this.contextFile); p.setUpdatable(false); IO.save(p, this.contextFile); render(); } catch (IOException e) { e.printStackTrace(); } } return super.onContextItemSelected(item); } @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); utils.onActionBarWithoutText(this.gamesItem = menu.add("Sign In") .setIcon(this.playIcon)); if(utils.isNightModeAvailable()) { utils.onActionBarWithoutText(menu.add("Night Mode") .setIcon(R.drawable.night_toggle)); } SubMenu sortMenu = menu.addSubMenu("Sort") .setIcon(android.R.drawable.ic_menu_sort_alphabetically); sortMenu.add("By Date (Descending)") .setIcon(android.R.drawable.ic_menu_day); sortMenu.add("By Date (Ascending)") .setIcon(android.R.drawable.ic_menu_day); sortMenu.add("By Source") .setIcon(android.R.drawable.ic_menu_upload); utils.onActionBarWithText(sortMenu); menu.add("Cleanup") .setIcon(android.R.drawable.ic_menu_manage); menu.add(MENU_ARCHIVES) .setIcon(android.R.drawable.ic_menu_view); menu.add("Help") .setIcon(android.R.drawable.ic_menu_help); menu.add("Settings") .setIcon(android.R.drawable.ic_menu_preferences); return true; } public void onSignInSucceeded(){ this.playIcon = R.drawable.ic_play_games_badge_white; if(this.gamesItem != null){ this.gamesItem.setIcon( this.playIcon ); } this.signedIn = true; } private void setListItemColor(View v, boolean selected){ if(selected) { v.setBackgroundColor(highlightColor); ((TextView) v.findViewById(R.id.puzzle_name)).setTextColor(Color.WHITE); } else { v.setBackgroundColor(normalColor); ((TextView) v.findViewById(R.id.puzzle_name)).setTextColor(primaryTextColor); } } @SuppressWarnings("deprecation") @Override public boolean onOptionsItemSelected(MenuItem item) { if(item.getTitle().equals("Night Mode")){ this.utils.toggleNightMode(this); } else if(item.getTitle().equals("Sign In")){ if(this.signedIn){ System.out.println("Showing achievements."); startActivityForResult(this.mHelper.getGamesClient().getAchievementsIntent(), 0); } else { Intent i = new Intent(this, GamesSignIn.class); this.startActivity(i); return true; } } else if (item.getTitle() .equals("Download")) { showDialog(DOWNLOAD_DIALOG_ID); return true; } else if (item.getTitle() .equals("Settings")) { Intent i = new Intent(this, PreferencesActivity.class); this.startActivity(i); return true; } else if (item.getTitle() .equals("Crosswords") || item.getTitle() .equals(MENU_ARCHIVES)) { this.viewArchive = !viewArchive; item.setTitle(viewArchive ? "Crosswords" : MENU_ARCHIVES); //menu item title this.setTitle(!viewArchive ? "Puzzles" : MENU_ARCHIVES); //activity title render(); return true; } else if (item.getTitle() .equals("Cleanup")) { this.cleanup(); return true; } else if (item.getTitle() .equals("Help")) { Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse("file:///android_asset/filescreen.html"), this, HTMLActivity.class); this.startActivity(i); } else if (item.getTitle() .equals("By Source")) { this.accessor = Accessor.SOURCE; prefs.edit() .putInt("sort", 2) .apply(); this.render(); } else if (item.getTitle() .equals("By Date (Ascending)")) { this.accessor = Accessor.DATE_ASC; prefs.edit() .putInt("sort", 1) .apply(); this.render(); } else if (item.getTitle() .equals("By Date (Descending)")) { this.accessor = Accessor.DATE_DESC; prefs.edit() .putInt("sort", 0) .apply(); this.render(); } else if("Send Debug Package".equals(item.getTitle())){ Intent i = ShortyzApplication.sendDebug(); if(i != null) this.startActivity(i); } return false; } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if ((resultCode == RESULT_OK) && (downloadDialog != null) && downloadDialog.isShowing()) { // If the user hit close in the browser download activity, we close the dialog. downloadDialog.dismiss(); } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if(!((ShortyzApplication) getApplication()).getSettings().getBoolean("didFirstRun", false)){ Intent i = new Intent(Intent.ACTION_VIEW, null, this, FirstrunActivity.class); this.startActivity(i); finish(); return; } this.setTitle("Puzzles"); //this.utils.hideTitleOnPortrait(this); setDefaultKeyMode(DEFAULT_KEYS_SHORTCUT); this.setContentView(R.layout.browse); this.showMenuAlways(); this.puzzleList = (RecyclerView) this.findViewById(R.id.puzzleList); this.puzzleList.setLayoutManager(new LinearLayoutManager(this)); this.puzzleList.addOnItemTouchListener(new RecyclerItemClickListener(this, this.puzzleList, this)); ItemTouchHelper helper = new ItemTouchHelper(new ItemTouchHelper.SimpleCallback( ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.START | ItemTouchHelper.END, ItemTouchHelper.START | ItemTouchHelper.END) { @Override public int getSwipeDirs(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { if (!(viewHolder instanceof FileViewHolder) || prefs.getBoolean("disableSwipe", false)) { return 0; // Don't swipe the headers. } return super.getSwipeDirs(recyclerView, viewHolder); } @Override public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder viewHolder1) { return false; } @Override public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { if(!selected.isEmpty()){ return; } if(!(viewHolder instanceof FileViewHolder)){ return; } FileHandle handle = (FileHandle) ((FileViewHolder) viewHolder).itemView.getTag(); System.out.println(" SWIPED "+handle.file.getAbsolutePath()); if("DELETE".equals(prefs.getString("swipeAction", "DELETE"))) { deleteFile(handle.file); } else { if (viewArchive) { moveTo(handle.file, crosswordsFolder); } else { moveTo(handle.file, archiveFolder); } } currentAdapter.onItemDismiss(viewHolder.getAdapterPosition()); puzzleList.invalidate(); } }); helper.attachToRecyclerView(this.puzzleList); this.sources = (ListView) this.findViewById(R.id.sourceList); upgradePreferences(); this.nm = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE); switch (prefs.getInt("sort", 0)) { case 2: this.accessor = Accessor.SOURCE; break; case 1: this.accessor = Accessor.DATE_ASC; break; default: this.accessor = Accessor.DATE_DESC; } download = (FloatingActionButton) this.findViewById(R.id.button_floating_action); if(download != null) { download.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { BrowseActivity.this.showDialog(DOWNLOAD_DIALOG_ID); } }); download.setImageBitmap(createBitmap("icons1.ttf", ",")); this.puzzleList.setOnTouchListener(new ShowHideOnScroll(download)); } highlightColor = ContextCompat.getColor(this, R.color.accent); normalColor = ContextCompat.getColor(this, R.color.background_light); primaryTextColor = ContextCompat.getColor(this, R.color.textColorPrimary); if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { new AlertDialog.Builder(this) .setTitle("Allow Permissions") .setMessage("Please allow writing to storage when prompted. Shortyz needs this permission to store downloaded crossword files and cannot work without it.") .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { ActivityCompat.requestPermissions(BrowseActivity.this, new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE }, REQUEST_WRITE_STORAGE); } }) .create() .show(); } else { ActivityCompat.requestPermissions(this, new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE }, REQUEST_WRITE_STORAGE); } return; } else { hasWritePermissions = true; } startInitialActivityOrFinishLoading(); } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { switch (requestCode) { case REQUEST_WRITE_STORAGE: if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { hasWritePermissions = true; startInitialActivityOrFinishLoading(); } } } private void startInitialActivityOrFinishLoading() { if (!crosswordsFolder.exists()) { this.downloadTen(); Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse("file:///android_asset/welcome.html"), this, HTMLActivity.class); this.startActivity(i); return; } else if (prefs.getBoolean("release_4.3.0", true)) { prefs.edit() .putBoolean("release_4.3.0", false) .apply(); Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse("file:///android_asset/release.html"), this, HTMLActivity.class); this.startActivity(i); return; } render(); this.checkDownload(); puzzleList.invalidate(); } @Override protected Dialog onCreateDialog(int id) { switch (id) { case DOWNLOAD_DIALOG_ID: DownloadPickerDialogBuilder.OnDownloadSelectedListener downloadButtonListener = new DownloadPickerDialogBuilder.OnDownloadSelectedListener() { public void onDownloadSelected(Date d, List<Downloader> downloaders, int selected) { List<Downloader> toDownload = new LinkedList<Downloader>(); boolean scrape; LOGGER.info("Downloaders: " + selected + " of " + downloaders); if (selected == 0) { // Download all available. toDownload.addAll(downloaders); toDownload.remove(0); scrape = true; } else { // Only download selected. toDownload.add(downloaders.get(selected)); scrape = false; } download(d, toDownload, scrape); } }; Date d = new Date(); @SuppressWarnings("deprecation") DownloadPickerDialogBuilder dpd = new DownloadPickerDialogBuilder(this, downloadButtonListener, d.getYear() + 1900, d.getMonth(), d.getDate(), new Provider<Downloaders>() { public Downloaders get() { return new Downloaders(prefs, nm, BrowseActivity.this); } }); downloadDialog = dpd.getInstance(); return downloadDialog; } return null; } @Override protected void onResume() { super.onResume(); if (this.currentAdapter == null) { this.render(); } else { if (lastOpenedHandle != null) { try { lastOpenedHandle.meta = IO.meta(lastOpenedHandle.file); CircleProgressBar bar = (CircleProgressBar) lastOpenedView.findViewById(R.id.puzzle_progress); if (lastOpenedHandle.meta.updatable) { bar.setPercentComplete(-1); } else { bar.setPercentComplete(lastOpenedHandle.getProgress()); } } catch (Exception e) { e.printStackTrace(); } } } this.checkDownload(); } private SeparatedRecyclerViewAdapter buildList(final Dialog dialog, File directory, Accessor accessor) { directory.mkdirs(); long incept = System.currentTimeMillis(); ArrayList<FileHandle> files = new ArrayList<FileHandle>(); FileHandle[] puzFiles = null; if (!directory.exists()) { showSDCardHelp(); return new SeparatedRecyclerViewAdapter(R.layout.puzzle_list_header); } String sourceMatch = null; if (this.sources != null) { sourceMatch = ((SourceListAdapter) sources.getAdapter()).current; if (SourceListAdapter.ALL_SOURCES.equals(sourceMatch)) { sourceMatch = null; } } HashSet<String> sourcesTemp = new HashSet<String>(); for (File f : directory.listFiles()) { // if this is taking a while and we are off the EDT, pop up the dialog. if ((dialog != null) && ((System.currentTimeMillis() - incept) > 2000) && !dialog.isShowing()) { handler.post(new Runnable() { public void run() { try { dialog.show(); } catch (RuntimeException e) { e.printStackTrace(); } } }); } if (f.getName() .endsWith(".puz")) { PuzzleMeta m = null; try { m = IO.meta(f); } catch (IOException e) { e.printStackTrace(); } FileHandle h = new FileHandle(f, m); sourcesTemp.add(h.getSource()); if ((sourceMatch == null) || sourceMatch.equals(h.getSource())) { files.add(h); } } } puzFiles = files.toArray(new FileHandle[files.size()]); try { Arrays.sort(puzFiles, accessor); } catch (Exception e) { e.printStackTrace(); } SeparatedRecyclerViewAdapter adapter = new SeparatedRecyclerViewAdapter(R.layout.puzzle_list_header); String lastHeader = null; ArrayList<FileHandle> current = new ArrayList<FileHandle>(); for (FileHandle handle : puzFiles) { String check = accessor.getLabel(handle); if (!((lastHeader == null) || lastHeader.equals(check))) { FileAdapter fa = new FileAdapter(current); adapter.addSection(lastHeader, fa); current = new ArrayList<FileHandle>(); } lastHeader = check; current.add(handle); } if (lastHeader != null) { FileAdapter fa = new FileAdapter(current); adapter.addSection(lastHeader, fa); current = new ArrayList<FileHandle>(); } if (this.sources != null) { this.sourceList.clear(); this.sourceList.addAll(sourcesTemp); Collections.sort(this.sourceList); this.handler.post(new Runnable(){ public void run(){ ((SourceListAdapter) sources.getAdapter()).notifyDataSetInvalidated(); } }); } return adapter; } private void checkDownload() { if (!hasWritePermissions) return; long lastDL = prefs.getLong("dlLast", 0); if (prefs.getBoolean("dlOnStartup", true) && ((System.currentTimeMillis() - (long) (12 * 60 * 60 * 1000)) > lastDL)) { this.download(new Date(), null, true); prefs.edit() .putLong("dlLast", System.currentTimeMillis()) .apply(); } } private FileHandle[] getFileHandlesFromDirectory(File directory) { ArrayList<FileHandle> files = new ArrayList<>(); FileHandle[] puzFiles; for (File f : (directory == null ? new File[0] : directory.listFiles())) { if (f.getName() .endsWith(".puz")) { PuzzleMeta m = null; try { m = IO.meta(f); } catch (IOException e) { e.printStackTrace(); } files.add(new FileHandle(f, m)); } } puzFiles = files.toArray(new FileHandle[files.size()]); Arrays.sort(puzFiles); files.clear(); return puzFiles; } private long getMaxAgeMs(String preferenceValue) { long cleanupValue = Long.parseLong(preferenceValue) + 1; long maxAge = (cleanupValue == 0) ? 0 : (System.currentTimeMillis() - (cleanupValue * 24 * 60 * 60 * 1000)); return maxAge; } private void cleanup() { boolean deleteOnCleanup = prefs.getBoolean("deleteOnCleanup", false); long maxAge = getMaxAgeMs(prefs.getString("cleanupAge", "2")); long archiveMaxAge = getMaxAgeMs(prefs.getString("archiveCleanupAge", "-1")); ArrayList<FileHandle> toArchive = new ArrayList<FileHandle>(); ArrayList<FileHandle> toDelete = new ArrayList<FileHandle>(); for (FileHandle h : getFileHandlesFromDirectory(this.crosswordsFolder)) { if ((h.getProgress() == 100) || (h.getDate() .getTime() < maxAge)) { if (deleteOnCleanup) { toDelete.add(h); } else { toArchive.add(h); } } } if (archiveMaxAge > 0) { for (FileHandle h : getFileHandlesFromDirectory(this.archiveFolder)) { if (h.getDate().getTime() < archiveMaxAge) { toDelete.add(h); } } } for (FileHandle h : toDelete) { File meta = new File( h.file.getPath().substring(0, h.file.getPath().lastIndexOf(".")) + ".shortyz"); h.file.delete(); meta.delete(); } for (FileHandle h : toArchive) { moveTo(h.file, this.archiveFolder); } render(); } private void deleteFile(File puzFile){ File meta = new File(puzFile.getParentFile(), puzFile.getName().substring(0, puzFile.getName().lastIndexOf(".")) + ".shortyz"); puzFile.delete(); meta.delete(); } private void moveTo(File puzFile, File directory){ File meta = new File(puzFile.getParentFile(), puzFile.getName().substring(0, puzFile.getName().lastIndexOf(".")) + ".shortyz"); puzFile.renameTo(new File(directory, puzFile.getName())); meta.renameTo(new File(directory, meta.getName())); } private void download(final Date d, final List<Downloader> downloaders, final boolean scrape) { if (!hasWritePermissions) return; final Downloaders dls = new Downloaders(prefs, nm, this); LOGGER.info("Downloading from "+downloaders); new Thread(new Runnable() { public void run() { dls.download(d, downloaders); if (scrape) { Scrapers scrapes = new Scrapers(prefs, nm, BrowseActivity.this); scrapes.scrape(); } handler.post(new Runnable() { public void run() { BrowseActivity.this.render(); } }); } }).start(); } private void downloadTen() { if (!hasWritePermissions) return; new Thread(new Runnable() { public void run() { Downloaders dls = new Downloaders(prefs, nm, BrowseActivity.this); dls.supressMessages(true); Scrapers scrapes = new Scrapers(prefs, nm, BrowseActivity.this); scrapes.supressMessages(true); scrapes.scrape(); Date d = new Date(); for (int i = 0; i < 5; i++) { d = new Date(d.getTime() - DAY); dls.download(d); handler.post(new Runnable() { public void run() { BrowseActivity.this.render(); } }); } } }).start(); } private void render() { if (!hasWritePermissions) return; if ((this.sources != null) && (this.sources.getAdapter() == null)) { final SourceListAdapter adapter = new SourceListAdapter(this, this.sourceList); this.sources.setAdapter(adapter); this.sources.setOnItemClickListener(new OnItemClickListener() { public void onItemClick(AdapterView<?> list, View view, int arg2, long arg3) { String selected = (String) view.getTag(); adapter.current = selected; adapter.notifyDataSetInvalidated(); render(); } }); } final ProgressDialog dialog = new ProgressDialog(this); dialog.setMessage("Please Wait..."); dialog.setCancelable(false); final File directory = viewArchive ? BrowseActivity.this.archiveFolder : BrowseActivity.this.crosswordsFolder; directory.mkdirs(); //Only spawn a thread if there are a lot of puzzles. // Using SDK rev as a proxy to decide whether you have a slow processor or not. if (((android.os.Build.VERSION.SDK_INT >= 5) && directory.exists() && (directory.list().length > 500)) || ((android.os.Build.VERSION.SDK_INT < 5) && directory.exists() && (directory.list().length > 160))) { Runnable r = new Runnable() { public void run() { currentAdapter = BrowseActivity.this.buildList(dialog, directory, BrowseActivity.this.accessor); BrowseActivity.this.handler.post(new Runnable() { public void run() { BrowseActivity.this.puzzleList.setAdapter(currentAdapter); if (dialog.isShowing()) { dialog.hide(); } } }); } }; new Thread(r).start(); } else { this.puzzleList.setAdapter(currentAdapter = this.buildList(null, directory, accessor)); } } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); } private void upgradePreferences() { if (this.prefs.getString("keyboardType", null) == null) { if (this.prefs.getBoolean("useNativeKeyboard", false)) { this.prefs.edit() .putString("keyboardType", "NATIVE") .apply(); } else { Configuration config = getBaseContext() .getResources() .getConfiguration(); if ((config.navigation == Configuration.NAVIGATION_NONAV) || (config.navigation == Configuration.NAVIGATION_UNDEFINED)) { this.prefs.edit() .putString("keyboardType", "CONDENSED_ARROWS") .apply(); } else { this.prefs.edit() .putString("keyboardType", "CONDENSED") .apply(); } } } } @Override public void onItemClick(final View v, int position) { if (!(v.getTag() instanceof FileHandle)) { return; } if (!selected.isEmpty()) { updateSelection(v); } else { handler.postDelayed(new Runnable() { @Override public void run() { lastOpenedView = v; lastOpenedHandle = ((FileHandle) v.getTag()); if (lastOpenedHandle == null || lastOpenedHandle.file == null) { return; } File puzFile = lastOpenedHandle.file; Intent i = new Intent(Intent.ACTION_EDIT, Uri.fromFile(puzFile), BrowseActivity.this, PlayActivity.class); startActivity(i); } }, 450); } } @Override public void onItemLongClick(View v, int position) { if (!(v.getTag() instanceof FileHandle)) { return; } if (actionMode == null) { getSupportActionBar().startActionMode(actionModeCallback); } updateSelection(v); } private void updateSelection(View v) { if (selected.contains(v.getTag())) { setListItemColor(v, false); selected.remove(v.getTag()); } else { setListItemColor(v, true); selected.add((FileHandle) v.getTag()); } if (selected.isEmpty()) { actionMode.finish(); } } public static interface Provider<T> { T get(); } private static ArrayList<FileHandle> toArrayList(FileHandle[] o){ ArrayList<FileHandle> result = new ArrayList<>(); result.addAll(Arrays.asList(o)); return result; } private class FileAdapter extends RemovableRecyclerViewAdapter<FileViewHolder> { final SimpleDateFormat df = new SimpleDateFormat("EEEEEEEEE\n MMM dd, yyyy"); final ArrayList<FileHandle> objects; public FileAdapter(ArrayList<FileHandle> objects) { this.objects = objects; } @Override public FileViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()) .inflate(R.layout.puzzle_list_item, parent, false); return new FileViewHolder(view); } @Override public void onBindViewHolder(FileViewHolder holder, int position) { View view = holder.itemView; FileHandle handle = objects.get(position); view.setTag(handle); TextView date = (TextView) view.findViewById(R.id.puzzle_date); date.setText(df.format(handle.getDate())); if (accessor == Accessor.SOURCE) { date.setVisibility(View.VISIBLE); } else { date.setVisibility(View.GONE); } TextView title = (TextView) view.findViewById(R.id.puzzle_name); title.setText(handle.getTitle()); CircleProgressBar bar = (CircleProgressBar) view.findViewById(R.id.puzzle_progress); bar.setPercentComplete(handle.getProgress()); TextView caption = (TextView) view.findViewById(R.id.puzzle_caption); caption.setText(handle.getCaption()); setListItemColor(view, selected.contains(handle)); } @Override public int getItemCount() { return objects.size(); } @Override public void remove(int position) { objects.remove(position); } } private class FileViewHolder extends RecyclerView.ViewHolder { public FileViewHolder(View itemView) { super(itemView); } } }