/** This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. **/ /** This file is part of Save For Offline, an Android app which saves / downloads complete webpages for offine reading. **/ /** If you modify, redistribute, or write something based on this or parts of it, you MUST, I repeat, you MUST comply with the GPLv2+ license. This means that if you use or modify my code, you MUST release the source code of your modified version, if / when this is required under the terms of the license. If you cannot / do not want to do this, DO NOT USE MY CODE. Thanks. (I've added this message to to the source because it's been used in severeral proprietary closed source apps, which I don't want, and which is also a violation of the liense.) **/ /** Written by Jonas Czech (JonasCz, stackoverflow.com/users/4428462/JonasCz and github.com/JonasCz). (4428462jonascz/eafc4d1afq) **/ package jonas.tool.saveForOffline; import android.app.*; import android.content.*; import android.database.sqlite.*; import android.graphics.*; import android.net.Uri; import android.os.*; import android.preference.*; import android.view.*; import android.webkit.MimeTypeMap; import android.widget.*; import android.widget.AdapterView.*; import java.io.*; import java.util.*; import java.util.concurrent.*; public class MainActivity extends Activity implements SearchView.OnQueryTextListener { private DisplayAdapter gridAdapter; private Database mHelper; private SQLiteDatabase dataBase; private TextView noSavedPages; private TextView helpText; private DisplayAdapter.SortOrder sortOrder = DisplayAdapter.SortOrder.NEWEST_FIRST; private GridView mainGrid; private SearchView mSearchView; private String searchQuery = ""; private ProgressDialog pageLoadDialog; private AlertDialog dialogSortItemsBy; private ActionBar actionbar; private int scrollPosition; private boolean defaultHtmlViewer; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); PreferenceManager.setDefaultValues(this, R.xml.preferences, false); SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this); if (sharedPref.getBoolean("dark_mode", false)) { setTheme(android.R.style.Theme_Holo); } defaultHtmlViewer = sharedPref.getBoolean("default_htmlviewer", false); setContentView(R.layout.main); mainGrid = (GridView) findViewById(R.id.List); mainGrid.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL); mainGrid.setMultiChoiceModeListener(new ModeCallback()); int list_layout_type = Integer.parseInt(sharedPref.getString("layout" , "1")); switch (list_layout_type) { case 1: break; case 2: mainGrid.setNumColumns(-1); break; case 4: mainGrid.setNumColumns(1); break; case 5: mainGrid.setNumColumns(1); break; case 6: mainGrid.setNumColumns(1); break; default: } sortOrder = sortOrder.fromInt(sharedPref.getInt("current_sort_order", 0)); pageLoadDialog = new ProgressDialog(MainActivity.this); actionbar = getActionBar(); setUpGridClickListener(); noSavedPages = (TextView) findViewById(R.id.textNoSavedPages); helpText = (TextView) findViewById(R.id.how_to_text); gridAdapter = new DisplayAdapter(MainActivity.this); mainGrid.setAdapter(gridAdapter); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) {super.onActivityResult(requestCode, resultCode, data); if (requestCode == 1 && resultCode == RESULT_FIRST_USER) { recreate(); } } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu items for use in the action bar MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.main_activity_actions, menu); MenuItem searchItem = menu.findItem(R.id.action_search); mSearchView = (SearchView) searchItem.getActionView(); mSearchView.setIconifiedByDefault(true); mSearchView.setOnQueryTextListener(this); mSearchView.setOnSearchClickListener(new View.OnClickListener() { @Override public void onClick(View view) { onQueryTextChange(mSearchView.getQuery().toString()); } }); return super.onCreateOptionsMenu(menu); } public boolean onQueryTextChange(String newText) { searchQuery = newText; displayData(newText); if (newText.length() == 0) { actionbar.setSubtitle(R.string.action_bar_subtitle_showing_all); } else { if (gridAdapter.getCount() == 1) {actionbar.setSubtitle(R.string.one_search_result);} else if (gridAdapter.getCount() == 0) {actionbar.setSubtitle(R.string.no_search_results);} else {actionbar.setSubtitle(gridAdapter.getCount() + " " + getResources().getString(R.string.num_search_results));} } return false; } public boolean onQueryTextSubmit(String query) { displayData(query); return false; } public boolean onClose() { displayData(""); return false; } @Override protected void onPause() { super.onPause(); scrollPosition = mainGrid.getFirstVisiblePosition(); } @Override protected void onResume() { super.onResume(); pageLoadDialog.cancel(); displayData(searchQuery); mainGrid.setSelection(scrollPosition); if (searchQuery.length() == 0) { actionbar.setSubtitle(R.string.action_bar_subtitle_showing_all); } } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle presses on the action bar items switch (item.getItemId()) { case R.id.action_add: Intent i = new Intent(getApplicationContext(), AddActivity.class); startActivity(i); return true; case R.id.action_sort_by: AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this); builder.setSingleChoiceItems(R.array.sort_by, DisplayAdapter.SortOrder.toInt(sortOrder), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { sortOrder = DisplayAdapter.SortOrder.fromInt(which); SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(MainActivity.this).edit(); editor.putInt("current_sort_order", which); editor.commit(); displayData(searchQuery); dialogSortItemsBy.cancel(); } }); dialogSortItemsBy = builder.create(); dialogSortItemsBy.show(); return true; case R.id.ic_action_settings: Intent settings = new Intent(this, Preferences.class); startActivityForResult(settings, 1); return true; default: return super.onOptionsItemSelected(item); } } private void setUpGridClickListener() { //click to show saved page mainGrid.setOnItemClickListener(new OnItemClickListener() { public void onItemClick(AdapterView<?> arg0, View clickedView, int position, long id) { clickedView.setBackgroundColor(Color.parseColor("#FFC107")); pageLoadDialog.setMessage("Please wait while loading..."); pageLoadDialog.setIndeterminate(true); pageLoadDialog.setCancelable(false); pageLoadDialog.show(); String filepath = gridAdapter.getPropertiesByPosition(position, Database.FILE_LOCATION); if (defaultHtmlViewer) { startDefaultHtmlViewer(filepath); } else { try { Intent i = new Intent(getApplicationContext(), ViewActivity.class); i.putExtra(Database.ORIGINAL_URL, gridAdapter.getPropertiesByPosition(position, Database.ORIGINAL_URL)); i.putExtra(Database.TITLE, gridAdapter.getPropertiesByPosition(position, Database.TITLE)); i.putExtra(Database.ID, gridAdapter.getPropertiesByPosition(position, Database.ID)); i.putExtra(Database.FILE_LOCATION, filepath); i.putExtra(Database.THUMBNAIL, gridAdapter.getPropertiesByPosition(position, Database.THUMBNAIL)); i.putExtra(Database.TIMESTAMP, gridAdapter.getPropertiesByPosition(position, Database.TIMESTAMP)); startActivity(i); } catch (Exception e){ pageLoadDialog.cancel(); Toast.makeText(MainActivity.this, "No application to open file", Toast.LENGTH_SHORT).show(); } } } }); } private void startDefaultHtmlViewer(String filepath) { pageLoadDialog.cancel(); final File file = new File(filepath); MimeTypeMap myMime = MimeTypeMap.getSingleton(); String mimeType = myMime.getMimeTypeFromExtension(getFileExtension(filepath).substring(1)); Intent i = new Intent(); i.setAction(android.content.Intent.ACTION_VIEW); i.setDataAndType(Uri.fromFile(file), mimeType); try { startActivity(i); } catch (Exception e) { Toast.makeText(MainActivity.this, "No application to open file", Toast.LENGTH_SHORT).show(); } } private String getFileExtension(String filepath) { final File file = new File(filepath); String filename = file.getName().toString(); String ext = null; try { ext = filename.substring(filename.lastIndexOf("."), filename.length()); } catch (IndexOutOfBoundsException e) { ext = ""; } return ext; } private void displayData(String searchQuery) { gridAdapter.refreshData(searchQuery, sortOrder, true); if (gridAdapter.getCount() == 0 && !searchQuery.equals("")) { noSavedPages.setText("No search results"); noSavedPages.setVisibility(View.VISIBLE); noSavedPages.setGravity(Gravity.CENTER_HORIZONTAL); mainGrid.setVisibility(View.GONE); } else if (gridAdapter.getCount() == 0) { noSavedPages.setText("No saved pages"); noSavedPages.setVisibility(View.VISIBLE); helpText.setVisibility(View.VISIBLE); mainGrid.setVisibility(View.GONE); } else { helpText.setVisibility(View.GONE); noSavedPages.setVisibility(View.GONE); mainGrid.setVisibility(View.VISIBLE); } } class ModeCallback implements ListView.MultiChoiceModeListener { private EditText e ; @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { getMenuInflater().inflate(R.menu.main_activity_multi_choice, menu); mode.setTitle("Select Items"); return true; } @Override public boolean onPrepareActionMode(ActionMode mode, Menu menu) { gridAdapter.selectedViewsPositions.clear(); return true; } @Override public boolean onActionItemClicked(final ActionMode mode, MenuItem item) { switch (item.getItemId()) { case R.id.action_rename: AlertDialog.Builder rename_dialog = new AlertDialog.Builder(MainActivity.this); View layout = getLayoutInflater().inflate(R.layout.rename_dialog, null); rename_dialog.setView(layout); e = (EditText) layout.findViewById(R.id.rename_dialog_edit); TextView t = (TextView) layout.findViewById(R.id.rename_dialog_text); if (gridAdapter.selectedViewsPositions.size() == 1) { e.setText(gridAdapter.getPropertiesByPosition(gridAdapter.selectedViewsPositions.get(0), Database.TITLE)); e.selectAll(); } else { t.setText("Enter new title for these " + gridAdapter.selectedViewsPositions.size() + " saved pages :"); } rename_dialog.setPositiveButton("Rename", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { mHelper = new Database(MainActivity.this); dataBase = mHelper.getWritableDatabase(); for (Integer position: gridAdapter.selectedViewsPositions) { ContentValues values=new ContentValues(); values.put(Database.TITLE, e.getText().toString() ); dataBase.update(Database.TABLE_NAME, values, Database.ID + "=" + gridAdapter.getPropertiesByPosition(position, Database.ID), null); } if (gridAdapter.selectedViewsPositions.size() == 1) { Toast.makeText(MainActivity.this, "Saved page renamed", Toast.LENGTH_LONG).show(); } else { Toast.makeText(MainActivity.this, "Renamed " + gridAdapter.selectedViewsPositions.size() + " saved pages", Toast.LENGTH_LONG).show(); } dataBase.close(); displayData(""); mode.finish(); } }); rename_dialog.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { mode.finish(); } }); AlertDialog rename_dialog_alert = rename_dialog.create(); rename_dialog_alert.show(); return true; case R.id.action_delete: AlertDialog.Builder build; build = new AlertDialog.Builder(MainActivity.this); if (gridAdapter.selectedViewsPositions.size() == 1) { build.setMessage("Do you want to delete ?\r\n" + gridAdapter.getPropertiesByPosition(gridAdapter.selectedViewsPositions.get(0), Database.TITLE)); } else { build.setMessage("Delete these " + gridAdapter.selectedViewsPositions.size() + " saved pages ?"); } build.setPositiveButton("Delete", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { new deleteItemsTask().execute(gridAdapter.selectedViewsPositions.toArray()); mode.finish(); } }); build.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { dialog.cancel(); mode.finish(); } }); AlertDialog alert = build.create(); alert.show(); break; default: break; } return true; } private class deleteItemsTask extends AsyncTask<Object, Integer, Integer> { ProgressDialog pd = null; int currentProgress = 0; @Override protected Integer doInBackground(Object[] selectedPositions) { dataBase = new Database(MainActivity.this).getWritableDatabase(); for (final Object position : selectedPositions) { String fileLocation = gridAdapter.getPropertiesByPosition((Integer) position, Database.FILE_LOCATION); DirectoryHelper.deleteDirectory(new File(fileLocation).getParentFile()); dataBase.delete(Database.TABLE_NAME, Database.ID + "=" + gridAdapter.getPropertiesByPosition((Integer)position, Database.ID), null); currentProgress++; publishProgress(currentProgress); } dataBase.close(); return selectedPositions.length; } @Override protected void onPreExecute() { pd = new ProgressDialog(MainActivity.this); pd.setMessage("Deleting items..."); pd.setIndeterminate(false); pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); pd.setMax(gridAdapter.selectedViewsPositions.size()); pd.setCancelable(false); pd.setCanceledOnTouchOutside(false); pd.show(); } @Override protected void onPostExecute(Integer result) { pd.hide(); pd.cancel(); displayData(""); Toast.makeText(MainActivity.this, "Deleted " + result + " saved pages", Toast.LENGTH_LONG).show(); } @Override protected void onProgressUpdate(Integer[] values) { pd.setProgress(values[0]); } } @Override public void onDestroyActionMode(ActionMode mode) { gridAdapter.selectedViewsPositions.clear(); } @Override public void onItemCheckedStateChanged(ActionMode mode, int position, long itemId, boolean checked) { Integer pos = position; View gridCellLayout = mainGrid.getChildAt(position - mainGrid.getFirstVisiblePosition()).findViewById(R.id.gridCellLayout); if (checked) { gridAdapter.selectedViewsPositions.add(pos); gridCellLayout.setBackgroundColor(Color.parseColor("#FFC107")); } else { gridAdapter.selectedViewsPositions.remove(pos); gridCellLayout.setBackgroundColor(Color.parseColor("#E2E2E2")); } final int checkedCount = gridAdapter.selectedViewsPositions.size(); switch (checkedCount) { case 0: mode.setSubtitle("Tap to select items"); findViewById(R.id.action_delete).setEnabled(false); break; case 1: mode.setSubtitle("One item selected"); findViewById(R.id.action_delete).setEnabled(true); break; default: mode.setSubtitle(checkedCount + " items selected"); findViewById(R.id.action_delete).setEnabled(true); break; } } } }