package org.fox.ttrss.offline; import org.fox.ttrss.CommonActivity; import org.fox.ttrss.PreferencesActivity; import org.fox.ttrss.R; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.select.Elements; import android.annotation.SuppressLint; import android.app.AlertDialog; import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.content.Intent; import android.content.SharedPreferences; import android.database.Cursor; import android.database.sqlite.SQLiteStatement; import android.net.Uri; import android.os.Bundle; import android.preference.PreferenceManager; import android.provider.BaseColumns; import android.support.v7.view.ActionMode; import android.util.Log; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.Window; import android.widget.EditText; import android.widget.SearchView; import android.widget.TextView; public class OfflineActivity extends CommonActivity { private final String TAG = this.getClass().getSimpleName(); protected SharedPreferences m_prefs; protected Menu m_menu; private ActionMode m_headlinesActionMode; private HeadlinesActionModeCallback m_headlinesActionModeCallback; private String m_lastImageHitTestUrl; @SuppressLint("NewApi") private class HeadlinesActionModeCallback implements ActionMode.Callback { @Override public boolean onPrepareActionMode(ActionMode mode, Menu menu) { return false; } @Override public void onDestroyActionMode(ActionMode mode) { m_headlinesActionMode = null; deselectAllArticles(); } @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.headlines_action_menu, menu); return true; } @Override public boolean onActionItemClicked(ActionMode mode, MenuItem item) { onOptionsItemSelected(item); return false; } }; @Override public boolean onContextItemSelected(android.view.MenuItem item) { /* AdapterContextMenuInfo info = (AdapterContextMenuInfo) item .getMenuInfo(); */ final OfflineArticlePager ap = (OfflineArticlePager)getSupportFragmentManager().findFragmentByTag(FRAG_ARTICLE); switch (item.getItemId()) { case R.id.article_img_open: if (getLastContentImageHitTestUrl() != null) { try { Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(getLastContentImageHitTestUrl())); startActivity(intent); } catch (Exception e) { e.printStackTrace(); toast(R.string.error_other_error); } } return true; case R.id.article_img_copy: if (getLastContentImageHitTestUrl() != null) { copyToClipboard(getLastContentImageHitTestUrl()); } return true; case R.id.article_img_share: if (getLastContentImageHitTestUrl() != null) { Intent intent = new Intent(Intent.ACTION_SEND); intent.setType("image/png"); intent.putExtra(Intent.EXTRA_SUBJECT, getLastContentImageHitTestUrl()); intent.putExtra(Intent.EXTRA_TEXT, getLastContentImageHitTestUrl()); startActivity(Intent.createChooser(intent, getLastContentImageHitTestUrl())); } return true; case R.id.article_img_view_caption: if (getLastContentImageHitTestUrl() != null) { String content = ""; Cursor article = getArticleById(ap.getSelectedArticleId()); if (article != null) { content = article.getString(article.getColumnIndex("content")); article.close(); } // Android doesn't give us an easy way to access title tags; // we'll use Jsoup on the body text to grab the title text // from the first image tag with this url. This will show // the wrong text if an image is used multiple times. Document doc = Jsoup.parse(content); Elements es = doc.getElementsByAttributeValue("src", getLastContentImageHitTestUrl()); if (es.size() > 0){ if (es.get(0).hasAttr("title")){ Dialog dia = new Dialog(this); if (es.get(0).hasAttr("alt")){ dia.setTitle(es.get(0).attr("alt")); } else { dia.setTitle(es.get(0).attr("title")); } TextView titleText = new TextView(this); if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) { titleText.setPaddingRelative(24, 24, 24, 24); } else { titleText.setPadding(24, 24, 24, 24); } titleText.setTextSize(16); titleText.setText(es.get(0).attr("title")); dia.setContentView(titleText); dia.show(); } else { toast(R.string.no_caption_to_display); } } else { toast(R.string.no_caption_to_display); } } return true; default: Log.d(TAG, "onContextItemSelected, unhandled id=" + item.getItemId()); return super.onContextItemSelected(item); } } @Override public void onCreate(Bundle savedInstanceState) { m_prefs = PreferenceManager .getDefaultSharedPreferences(getApplicationContext()); setAppTheme(m_prefs); super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_PROGRESS); setProgressBarVisibility(false); setContentView(R.layout.login); setLoadingStatus(R.string.blank, false); findViewById(R.id.loading_container).setVisibility(View.GONE); initMenu(); Intent intent = getIntent(); if (intent.getExtras() != null) { if (intent.getBooleanExtra("initial", false)) { intent = new Intent(OfflineActivity.this, OfflineFeedsActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); startActivityForResult(intent, 0); finish(); } } /* if (savedInstanceState != null) { } */ m_headlinesActionModeCallback = new HeadlinesActionModeCallback(); } @Override public void onSaveInstanceState(Bundle out) { super.onSaveInstanceState(out); } protected void selectArticles(int feedId, boolean isCat, int mode) { switch (mode) { case 0: SQLiteStatement stmtSelectAll = null; if (isCat) { stmtSelectAll = getWritableDb().compileStatement( "UPDATE articles SET selected = 1 WHERE feed_id IN (SELECT "+BaseColumns._ID+" FROM feeds WHERE cat_id = ?)"); } else { stmtSelectAll = getWritableDb().compileStatement( "UPDATE articles SET selected = 1 WHERE feed_id = ?"); } stmtSelectAll.bindLong(1, feedId); stmtSelectAll.execute(); stmtSelectAll.close(); break; case 1: SQLiteStatement stmtSelectUnread = null; if (isCat) { stmtSelectUnread = getWritableDb().compileStatement( "UPDATE articles SET selected = 1 WHERE feed_id IN (SELECT "+BaseColumns._ID+" FROM feeds WHERE cat_id = ?) AND unread = 1"); } else { stmtSelectUnread = getWritableDb().compileStatement( "UPDATE articles SET selected = 1 WHERE feed_id = ? AND unread = 1"); } stmtSelectUnread.bindLong(1, feedId); stmtSelectUnread.execute(); stmtSelectUnread.close(); break; case 2: deselectAllArticles(); break; } } @Override public boolean onOptionsItemSelected(MenuItem item) { final OfflineHeadlinesFragment ohf = (OfflineHeadlinesFragment) getSupportFragmentManager() .findFragmentByTag(FRAG_HEADLINES); /* final OfflineFeedsFragment off = (OfflineFeedsFragment) getSupportFragmentManager() .findFragmentByTag(FRAG_FEEDS); */ /* final OfflineFeedCategoriesFragment ocf = (OfflineFeedCategoriesFragment) getSupportFragmentManager() .findFragmentByTag(FRAG_CATS); */ final OfflineArticlePager oap = (OfflineArticlePager) getSupportFragmentManager() .findFragmentByTag(FRAG_ARTICLE); switch (item.getItemId()) { /* case android.R.id.home: finish(); return true; */ case R.id.headlines_toggle_sidebar: if (true && !isSmallScreen()) { SharedPreferences.Editor editor = m_prefs.edit(); editor.putBoolean("headlines_hide_sidebar", !m_prefs.getBoolean("headlines_hide_sidebar", false)); editor.commit(); if (ohf != null && ohf.isAdded()) { ohf.getView().setVisibility(m_prefs.getBoolean("headlines_hide_sidebar", false) ? View.GONE : View.VISIBLE); } } return true; case R.id.go_online: switchOnline(); return true; case R.id.search: if (ohf != null && isCompatMode()) { Dialog dialog = new Dialog(this); final EditText edit = new EditText(this); AlertDialog.Builder builder = new AlertDialog.Builder(this) .setTitle(R.string.search) .setPositiveButton(getString(R.string.search), new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { String query = edit.getText().toString().trim(); ohf.setSearchQuery(query); } }) .setNegativeButton(getString(R.string.cancel), new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // } }).setView(edit); dialog = builder.create(); dialog.show(); } return true; case R.id.preferences: Intent intent = new Intent(this, PreferencesActivity.class); startActivityForResult(intent, 0); return true; case R.id.headlines_view_mode: if (ohf != null) { Dialog dialog = new Dialog(this); String viewMode = getViewMode(); //Log.d(TAG, "viewMode:" + getViewMode()); int selectedIndex = 0; if (viewMode.equals("all_articles")) { selectedIndex = 0; } else if (viewMode.equals("marked")) { selectedIndex = 1; } else if (viewMode.equals("published")) { selectedIndex = 2; } else if (viewMode.equals("unread")) { selectedIndex = 3; } AlertDialog.Builder builder = new AlertDialog.Builder(this) .setTitle(R.string.headlines_set_view_mode) .setSingleChoiceItems( new String[] { /* getString(R.string.headlines_adaptive), */ getString(R.string.headlines_all_articles), getString(R.string.headlines_starred), getString(R.string.headlines_published), getString(R.string.headlines_unread) }, selectedIndex, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { switch (which) { /* case 0: setViewMode("adaptive"); break; */ case 0: setViewMode("all_articles"); break; case 1: setViewMode("marked"); break; case 2: setViewMode("published"); break; case 3: setViewMode("unread"); break; } dialog.cancel(); refresh(); } }); dialog = builder.create(); dialog.show(); } return true; case R.id.headlines_select: if (ohf != null) { Dialog dialog = new Dialog(this); AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(R.string.headlines_select_dialog); builder.setSingleChoiceItems(new String[] { getString(R.string.headlines_select_all), getString(R.string.headlines_select_unread), getString(R.string.headlines_select_none) }, 0, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { selectArticles(ohf.getFeedId(), ohf.getFeedIsCat(), which); initMenu(); refresh(); dialog.cancel(); } }); dialog = builder.create(); dialog.show(); } return true; case R.id.headlines_mark_as_read: if (ohf != null) { final int feedId = ohf.getFeedId(); final boolean isCat = ohf.getFeedIsCat(); int count = getUnreadArticleCount(feedId, isCat); boolean confirm = m_prefs.getBoolean("confirm_headlines_catchup", true); if (count > 0) { if (confirm) { AlertDialog.Builder builder = new AlertDialog.Builder( OfflineActivity.this) .setMessage(getString(R.string.mark_num_headlines_as_read, count)) .setPositiveButton(R.string.catchup, new Dialog.OnClickListener() { public void onClick(DialogInterface dialog, int which) { catchupFeed(feedId, isCat); } }) .setNegativeButton(R.string.dialog_cancel, new Dialog.OnClickListener() { public void onClick(DialogInterface dialog, int which) { } }); AlertDialog dlg = builder.create(); dlg.show(); } else { catchupFeed(feedId, isCat); } } } return true; case R.id.share_article: if (true) { int articleId = oap.getSelectedArticleId(); shareArticle(articleId); } return true; case R.id.toggle_marked: if (oap != null) { int articleId = oap.getSelectedArticleId(); SQLiteStatement stmt = getWritableDb().compileStatement( "UPDATE articles SET modified = 1, marked = NOT marked WHERE " + BaseColumns._ID + " = ?"); stmt.bindLong(1, articleId); stmt.execute(); stmt.close(); refresh(); } return true; /* case R.id.selection_select_none: deselectAllArticles(); return true; */ case R.id.selection_toggle_unread: if (getSelectedArticleCount() > 0) { SQLiteStatement stmt = getWritableDb() .compileStatement( "UPDATE articles SET modified = 1, unread = NOT unread WHERE selected = 1"); stmt.execute(); stmt.close(); refresh(); } return true; case R.id.selection_toggle_marked: if (getSelectedArticleCount() > 0) { SQLiteStatement stmt = getWritableDb() .compileStatement( "UPDATE articles SET modified = 1, marked = NOT marked WHERE selected = 1"); stmt.execute(); stmt.close(); refresh(); } return true; case R.id.selection_toggle_published: if (getSelectedArticleCount() > 0) { SQLiteStatement stmt = getWritableDb() .compileStatement( "UPDATE articles SET modified = 1, published = NOT published WHERE selected = 1"); stmt.execute(); stmt.close(); refresh(); } return true; case R.id.toggle_published: if (oap != null) { int articleId = oap.getSelectedArticleId(); SQLiteStatement stmt = getWritableDb().compileStatement( "UPDATE articles SET modified = 1, published = NOT published WHERE " + BaseColumns._ID + " = ?"); stmt.bindLong(1, articleId); stmt.execute(); stmt.close(); refresh(); } return true; case R.id.catchup_above: if (oap != null) { int articleId = oap.getSelectedArticleId(); int feedId = oap.getFeedId(); boolean isCat = oap.getFeedIsCat(); SQLiteStatement stmt = null; if (isCat) { stmt = getWritableDb().compileStatement( "UPDATE articles SET modified = 1, unread = 0 WHERE " + "updated >= (SELECT updated FROM articles WHERE " + BaseColumns._ID + " = ?) " + "AND feed_id IN (SELECT "+BaseColumns._ID+" FROM feeds WHERE cat_id = ?)"); } else { stmt = getWritableDb().compileStatement( "UPDATE articles SET modified = 1, unread = 0 WHERE " + "updated >= (SELECT updated FROM articles WHERE " + BaseColumns._ID + " = ?) " + "AND feed_id = ?"); } stmt.bindLong(1, articleId); stmt.bindLong(2, feedId); stmt.execute(); stmt.close(); refresh(); } return true; case R.id.set_unread: if (oap != null) { int articleId = oap.getSelectedArticleId(); SQLiteStatement stmt = getWritableDb().compileStatement( "UPDATE articles SET modified = 1, unread = 1 WHERE " + BaseColumns._ID + " = ?"); stmt.bindLong(1, articleId); stmt.execute(); stmt.close(); refresh(); } return true; default: Log.d(TAG, "onOptionsItemSelected, unhandled id=" + item.getItemId()); return super.onOptionsItemSelected(item); } } @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.offline_menu, menu); m_menu = menu; initMenu(); return true; } @SuppressLint("NewApi") protected void initMenu() { if (m_menu != null) { m_menu.setGroupVisible(R.id.menu_group_headlines, false); m_menu.setGroupVisible(R.id.menu_group_article, false); m_menu.setGroupVisible(R.id.menu_group_feeds, false); OfflineHeadlinesFragment hf = (OfflineHeadlinesFragment) getSupportFragmentManager().findFragmentByTag(FRAG_HEADLINES); if (hf != null) { if (hf.getSelectedArticleCount() > 0 && m_headlinesActionMode == null) { m_headlinesActionMode = startSupportActionMode(m_headlinesActionModeCallback); } else if (hf.getSelectedArticleCount() == 0 && m_headlinesActionMode != null) { m_headlinesActionMode.finish(); } } OfflineArticlePager ap = (OfflineArticlePager) getSupportFragmentManager().findFragmentByTag(FRAG_ARTICLE); if (ap != null) { int articleId = ap.getSelectedArticleId(); Cursor article = getArticleById(articleId); if (article != null) { boolean unread = article.getInt(article.getColumnIndex("unread")) == 1; boolean marked = article.getInt(article.getColumnIndex("marked")) == 1; boolean published = article.getInt(article.getColumnIndex("published")) == 1; m_menu.findItem(R.id.toggle_marked).setIcon(marked ? R.drawable.ic_important_light : R.drawable.ic_unimportant_light); m_menu.findItem(R.id.toggle_published).setIcon(published ? R.drawable.ic_menu_published_light : R.drawable.ic_menu_unpublished_light); m_menu.findItem(R.id.set_unread).setIcon(unread ? R.drawable.ic_unread_light : R.drawable.ic_read_light); article.close(); } } if (!isCompatMode()) { MenuItem search = m_menu.findItem(R.id.search); SearchView searchView = (SearchView) search.getActionView(); if (searchView != null) { searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { private String query = ""; @Override public boolean onQueryTextSubmit(String query) { OfflineHeadlinesFragment frag = (OfflineHeadlinesFragment) getSupportFragmentManager() .findFragmentByTag(FRAG_HEADLINES); if (frag != null) { frag.setSearchQuery(query); this.query = query; } return false; } @Override public boolean onQueryTextChange(String newText) { if (newText.equals("") && !newText.equals(this.query)) { OfflineHeadlinesFragment frag = (OfflineHeadlinesFragment) getSupportFragmentManager() .findFragmentByTag(FRAG_HEADLINES); if (frag != null) { frag.setSearchQuery(newText); this.query = newText; } } return false; } }); } } } } private void switchOnline() { SharedPreferences localPrefs = getSharedPreferences("localprefs", Context.MODE_PRIVATE); SharedPreferences.Editor editor = localPrefs.edit(); editor.putBoolean("offline_mode_active", false); editor.commit(); Intent refresh = new Intent(this, org.fox.ttrss.OnlineActivity.class); startActivity(refresh); finish(); } protected Cursor getArticleById(int articleId) { Cursor c = getReadableDb().query("articles", null, BaseColumns._ID + "=?", new String[] { String.valueOf(articleId) }, null, null, null); c.moveToFirst(); return c; } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (m_prefs.getBoolean("use_volume_keys", false)) { OfflineArticlePager ap = (OfflineArticlePager) getSupportFragmentManager().findFragmentByTag(FRAG_ARTICLE); if (ap != null && ap.isAdded()) { switch (keyCode) { case KeyEvent.KEYCODE_VOLUME_UP: ap.selectArticle(false); return true; case KeyEvent.KEYCODE_VOLUME_DOWN: ap.selectArticle(true); return true; } } } return super.onKeyDown(keyCode, event); } // Handle onKeyUp too to suppress beep @Override public boolean onKeyUp(int keyCode, KeyEvent event) { if (m_prefs.getBoolean("use_volume_keys", false)) { switch (keyCode) { case KeyEvent.KEYCODE_VOLUME_UP: case KeyEvent.KEYCODE_VOLUME_DOWN: return true; } } return super.onKeyUp(keyCode, event); } protected Cursor getFeedById(int feedId) { Cursor c = getReadableDb().query("feeds", null, BaseColumns._ID + "=?", new String[] { String.valueOf(feedId) }, null, null, null); c.moveToFirst(); return c; } protected Cursor getCatById(int catId) { Cursor c = getReadableDb().query("categories", null, BaseColumns._ID + "=?", new String[] { String.valueOf(catId) }, null, null, null); c.moveToFirst(); return c; } protected Intent getShareIntent(Cursor article) { if (article != null) { String title = article.getString(article.getColumnIndex("title")); String link = article.getString(article.getColumnIndex("link")); Intent intent = new Intent(Intent.ACTION_SEND); intent.setType("text/plain"); intent.putExtra(Intent.EXTRA_SUBJECT, title); intent.putExtra(Intent.EXTRA_TEXT, link); return intent; } else { return null; } } protected void shareArticle(int articleId) { Cursor article = getArticleById(articleId); if (article != null) { shareArticle(article); article.close(); } } private void shareArticle(Cursor article) { if (article != null) { Intent intent = getShareIntent(article); startActivity(Intent.createChooser(intent, getString(R.string.share_article))); } } protected int getSelectedArticleCount() { Cursor c = getReadableDb().query("articles", new String[] { "COUNT(*)" }, "selected = 1", null, null, null, null); c.moveToFirst(); int selected = c.getInt(0); c.close(); return selected; } protected int getUnreadArticleCount(int feedId, boolean isCat) { Cursor c; if (isCat) { c = getReadableDb().query("articles", new String[] { "COUNT(*)" }, "unread = 1 AND feed_id IN (SELECT "+BaseColumns._ID+" FROM feeds WHERE cat_id = ?)", new String[] { String.valueOf(feedId) }, null, null, null); } else { c = getReadableDb().query("articles", new String[] { "COUNT(*)" }, "unread = 1 AND feed_id = ?", new String[] { String.valueOf(feedId) }, null, null, null); } c.moveToFirst(); int selected = c.getInt(0); c.close(); return selected; } protected void deselectAllArticles() { getWritableDb().execSQL("UPDATE articles SET selected = 0 "); refresh(); } protected void refresh() { OfflineFeedsFragment ff = (OfflineFeedsFragment) getSupportFragmentManager() .findFragmentByTag(FRAG_FEEDS); if (ff != null) { ff.refresh(); } OfflineFeedCategoriesFragment cf = (OfflineFeedCategoriesFragment) getSupportFragmentManager() .findFragmentByTag(FRAG_CATS); if (cf != null) { cf.refresh(); } OfflineHeadlinesFragment ohf = (OfflineHeadlinesFragment) getSupportFragmentManager() .findFragmentByTag(FRAG_HEADLINES); if (ohf != null) { ohf.refresh(); } initMenu(); } public void catchupFeed(int feedId, boolean isCat) { if (isCat) { SQLiteStatement stmt = getWritableDb().compileStatement( "UPDATE articles SET modified = 1, unread = 0 WHERE feed_id IN (SELECT "+ BaseColumns._ID+" FROM feeds WHERE cat_id = ?)"); stmt.bindLong(1, feedId); stmt.execute(); stmt.close(); } else { SQLiteStatement stmt = getWritableDb().compileStatement( "UPDATE articles SET modified = 1, unread = 0 WHERE feed_id = ?"); stmt.bindLong(1, feedId); stmt.execute(); stmt.close(); } refresh(); } public void setLastContentImageHitTestUrl(String url) { m_lastImageHitTestUrl = url; } public String getLastContentImageHitTestUrl() { return m_lastImageHitTestUrl; } public void setViewMode(String viewMode) { SharedPreferences.Editor editor = m_prefs.edit(); editor.putString("offline_view_mode", viewMode); editor.commit(); } public String getViewMode() { return m_prefs.getString("offline_view_mode", "adaptive"); } }