package org.fox.ttrss; import android.annotation.SuppressLint; import android.app.Dialog; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.res.ColorStateList; import android.graphics.Point; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.util.Log; import android.util.TypedValue; import android.view.Display; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.widget.EditText; import android.widget.ListView; import android.widget.TextView; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.view.ActionMode; import androidx.appcompat.widget.Toolbar; import androidx.core.content.ContextCompat; import androidx.fragment.app.FragmentTransaction; import androidx.preference.PreferenceManager; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.progressindicator.LinearProgressIndicator; import com.google.gson.Gson; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.reflect.TypeToken; import org.fox.ttrss.share.SubscribeActivity; import org.fox.ttrss.types.Article; import org.fox.ttrss.types.Feed; import org.fox.ttrss.types.Label; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @SuppressLint("StaticFieldLeak") public class OnlineActivity extends CommonActivity { private final String TAG = this.getClass().getSimpleName(); protected SharedPreferences m_prefs; protected Menu m_menu; private Feed m_activeFeed; private ActionMode m_headlinesActionMode; private HeadlinesActionModeCallback m_headlinesActionModeCallback; private String m_lastImageHitTestUrl; protected LinearProgressIndicator m_loadingProgress; public void catchupDialog(final Feed feed) { if (getApiLevel() >= 15) { int selectedIndex = 0; final String searchQuery = Application.getArticlesModel().getSearchQuery(); int titleStringId = !searchQuery.isEmpty() ? R.string.catchup_dialog_title_search : R.string.catchup_dialog_title; MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this) .setTitle(getString(titleStringId, feed.title)) .setSingleChoiceItems( new String[]{ getString(R.string.catchup_dialog_all_articles), getString(R.string.catchup_dialog_1day), getString(R.string.catchup_dialog_1week), getString(R.string.catchup_dialog_2week) }, selectedIndex, (dialog, which) -> { }) .setPositiveButton(R.string.catchup, (dialog, which) -> { ListView list = ((AlertDialog) dialog).getListView(); if (list.getCheckedItemCount() > 0) { int position = list.getCheckedItemPosition(); String[] catchupModes = {"all", "1day", "1week", "2week"}; String mode = catchupModes[position]; catchupFeed(feed, mode, true, searchQuery); } }) .setNegativeButton(R.string.dialog_cancel, (dialog, which) -> { }); Dialog dialog = builder.create(); dialog.show(); } else { MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this) .setMessage(getString(R.string.catchup_dialog_title, feed.title)) .setPositiveButton(R.string.catchup, (dialog, which) -> catchupFeed(feed, "all", true, "")) .setNegativeButton(R.string.dialog_cancel, (dialog, which) -> { }); Dialog dialog = builder.create(); dialog.show(); } } public void confirmCatchupAbove(final Article article) { MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this) .setMessage(getString(R.string.confirm_catchup_above_title, article.title)) .setPositiveButton(R.string.dialog_ok, (dialog, which) -> catchupAbove(article)) .setNegativeButton(R.string.dialog_cancel, (dialog, which) -> { }); Dialog dialog = builder.create(); dialog.show(); } //protected PullToRefreshAttacher m_pullToRefreshAttacher; protected static abstract class OnLoginFinishedListener { public abstract void OnLoginSuccess(); public abstract void OnLoginFailed(); } 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; Application.getArticlesModel().setSelection(ArticleModel.ArticlesSelection.NONE); invalidateOptionsMenu(); } @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.action_mode_headlines, menu); return true; } @Override public boolean onActionItemClicked(ActionMode mode, MenuItem item) { onOptionsItemSelected(item); return false; } } protected String getSessionId() { return Application.getInstance().getSessionId(); } protected void setSessionId(String sessionId) { Application.getInstance().setSessionId(sessionId); } @Override public void onCreate(Bundle savedInstanceState) { // we use that before parent onCreate so let's init locally m_prefs = PreferenceManager .getDefaultSharedPreferences(getApplicationContext()); setAppTheme(m_prefs); super.onCreate(savedInstanceState); setContentView(R.layout.activity_login); Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); if (savedInstanceState != null) { m_activeFeed = savedInstanceState.getParcelable("m_activeFeed"); } m_headlinesActionModeCallback = new HeadlinesActionModeCallback(); ArticleModel model = Application.getArticlesModel(); model.getActive().observe(this, (articles) -> { invalidateOptionsMenu(); }); } public void login() { login(false, null); } public void login(boolean refresh) { login(refresh, null); } public void login(boolean refresh, OnLoginFinishedListener listener) { if (m_prefs.getString("ttrss_url", "").trim().isEmpty()) { setLoadingStatus(R.string.login_need_configure); MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this) .setMessage(R.string.dialog_need_configure_prompt) .setCancelable(false) .setPositiveButton(R.string.dialog_open_preferences, (dialog, id) -> { // launch preferences Intent intent = new Intent(OnlineActivity.this, PreferencesActivity.class); startActivityForResult(intent, 0); }) .setNegativeButton(R.string.cancel, (dialog, id) -> dialog.cancel()); Dialog alert = builder.create(); alert.show(); } else { setLoadingStatus(R.string.login_in_progress); LoginRequest ar = new LoginRequest(getApplicationContext(), refresh, listener); HashMap map = new HashMap<>(); map.put("op", "login"); map.put("user", m_prefs.getString("login", "").trim()); map.put("password", m_prefs.getString("password", "").trim()); ar.execute(map); setLoadingStatus(R.string.login_in_progress); } } protected void loginSuccess(boolean refresh) { setLoadingStatus(R.string.blank); initMenu(); Intent intent = new Intent(OnlineActivity.this, MasterActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); startActivityForResult(intent, 0); overridePendingTransition(0, 0); finish(); } @Override public boolean onContextItemSelected(android.view.MenuItem item) { int itemId = item.getItemId(); if (itemId == R.id.article_img_open) { if (getLastContentImageHitTestUrl() != null) { try { openUri(Uri.parse(getLastContentImageHitTestUrl())); } catch (Exception e) { e.printStackTrace(); toast(R.string.error_other_error); } } return true; } else if (itemId == R.id.article_img_copy) { if (getLastContentImageHitTestUrl() != null) { copyToClipboard(getLastContentImageHitTestUrl()); } return true; } else if (itemId == R.id.article_img_share) { if (getLastContentImageHitTestUrl() != null) { shareImageFromUri(getLastContentImageHitTestUrl()); } return true; } else if (itemId == R.id.article_img_share_url) { if (getLastContentImageHitTestUrl() != null) { shareText(getLastContentImageHitTestUrl()); } return true; } else if (itemId == R.id.article_img_view_caption) { if (getLastContentImageHitTestUrl() != null) { Article selectedArticle = Application.getArticlesModel().getActiveArticle(); if (selectedArticle != null) displayImageCaption(getLastContentImageHitTestUrl(), selectedArticle.content); } return true; } else if (itemId == R.id.article_link_share) { Article selectedArticle = Application.getArticlesModel().getActiveArticle(); if (selectedArticle != null) shareArticle(selectedArticle); return true; } else if (itemId == R.id.article_link_copy) { Article selectedArticle = Application.getArticlesModel().getActiveArticle(); if (selectedArticle != null) copyToClipboard(selectedArticle.link); return true; } Log.d(TAG, "onContextItemSelected, unhandled id=" + item.getItemId()); return super.onContextItemSelected(item); } public void displayAttachments(Article article) { if (article != null && article.attachments != null && !article.attachments.isEmpty()) { CharSequence[] items = new CharSequence[article.attachments.size()]; final CharSequence[] itemUrls = new CharSequence[article.attachments.size()]; for (int i = 0; i < article.attachments.size(); i++) { items[i] = article.attachments.get(i).title != null && !article.attachments.get(i).title.isEmpty() ? article.attachments.get(i).title : article.attachments.get(i).content_url; itemUrls[i] = article.attachments.get(i).content_url; } MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this) .setTitle(R.string.attachments_prompt) .setCancelable(true) .setSingleChoiceItems(items, 0, (dialog, which) -> { // }).setNeutralButton(R.string.attachment_copy, (dialog, which) -> { int selectedPosition = ((AlertDialog) dialog).getListView().getCheckedItemPosition(); copyToClipboard((String) itemUrls[selectedPosition]); }).setPositiveButton(R.string.attachment_view, (dialog, id) -> { int selectedPosition = ((AlertDialog) dialog).getListView().getCheckedItemPosition(); openUri(Uri.parse((String) itemUrls[selectedPosition])); dialog.cancel(); }).setNegativeButton(R.string.dialog_cancel, (dialog, id) -> dialog.cancel()); Dialog dialog = builder.create(); dialog.show(); } } @Override public boolean onOptionsItemSelected(MenuItem item) { final HeadlinesFragment hf = (HeadlinesFragment) getSupportFragmentManager().findFragmentByTag(FRAG_HEADLINES); Article activeArticle = Application.getArticlesModel().getActiveArticle(); Log.d(TAG, "onOptionsItemSelected, active article=" + activeArticle); int itemId = item.getItemId(); if (itemId == R.id.subscribe_to_feed) { Intent subscribe = new Intent(OnlineActivity.this, SubscribeActivity.class); startActivityForResult(subscribe, 0); return true; } else if (itemId == R.id.toggle_attachments) { if (activeArticle != null) displayAttachments(activeArticle); return true; } else if (itemId == R.id.login) { login(); return true; } else if (itemId == R.id.article_edit_note) { if (activeArticle != null) editArticleNote(activeArticle); return true; } else if (itemId == R.id.preferences) { Intent intent = new Intent(OnlineActivity.this, PreferencesActivity.class); startActivityForResult(intent, 0); return true; } else if (itemId == R.id.search) { final EditText edit = new EditText(this); edit.setText(Application.getArticlesModel().getSearchQuery()); MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this) .setTitle(R.string.search) .setPositiveButton(getString(R.string.search), (dialog4, which) -> { String query = edit.getText().toString().trim(); Application.getArticlesModel().setSearchQuery(query); }) .setNegativeButton(getString(R.string.cancel), (dialog3, which) -> { // }).setView(edit); Dialog dialog = builder.create(); dialog.show(); return true; } else if (itemId == R.id.headlines_mark_as_read) { if (hf != null) { Feed feed = hf.getFeed(); if (feed != null) { catchupDialog(hf.getFeed()); } } return true; } else if (itemId == R.id.headlines_display_mode) { if (hf != null) { String headlineMode = m_prefs.getString("headline_mode", "HL_DEFAULT"); String[] headlineModeNames = getResources().getStringArray(R.array.headline_mode_names); final String[] headlineModeValues = getResources().getStringArray(R.array.headline_mode_values); int selectedIndex = Arrays.asList(headlineModeValues).indexOf(headlineMode); MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this) .setTitle(R.string.headlines_set_display_mode) .setSingleChoiceItems(headlineModeNames, selectedIndex, (dialog2, which) -> { dialog2.cancel(); SharedPreferences.Editor editor = m_prefs.edit(); editor.putString("headline_mode", headlineModeValues[which]); editor.apply(); FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); HeadlinesFragment hfnew = new HeadlinesFragment(); hfnew.initialize(hf.getFeed()); ft.replace(R.id.headlines_fragment, hfnew, FRAG_HEADLINES); ft.commit(); }); Dialog dialog = builder.create(); dialog.show(); } return true; } else if (itemId == R.id.headlines_view_mode) { if (hf != null) { String viewMode = getViewMode(); int selectedIndex = 0; switch (viewMode) { case "all_articles": selectedIndex = 1; break; case "marked": selectedIndex = 2; break; case "published": selectedIndex = 3; break; case "unread": selectedIndex = 4; break; } MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(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, (dialog1, which) -> { switch (which) { case 0: setViewMode("adaptive"); break; case 1: setViewMode("all_articles"); break; case 2: setViewMode("marked"); break; case 3: setViewMode("published"); break; case 4: setViewMode("unread"); break; } dialog1.cancel(); refresh(); }); Dialog dialog = builder.create(); dialog.show(); } return true; } else if (itemId == R.id.headlines_select) { MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this) .setTitle(R.string.headlines_select_dialog) .setSingleChoiceItems( new String[]{ getString(R.string.headlines_select_all), getString(R.string.headlines_select_unread), getString(R.string.headlines_select_none)}, 0, (dialog, which) -> { ArticleModel model = Application.getArticlesModel(); switch (which) { case 0: model.setSelection(ArticleModel.ArticlesSelection.ALL); break; case 1: model.setSelection(ArticleModel.ArticlesSelection.UNREAD); break; case 2: model.setSelection(ArticleModel.ArticlesSelection.NONE); break; } dialog.cancel(); }); Dialog dialog = builder.create(); dialog.show(); return true; } else if (itemId == R.id.share_article) { if (activeArticle != null) shareArticle(activeArticle); return true; } else if (itemId == R.id.article_set_score) { if (activeArticle != null) { setArticleScore(activeArticle); } return true; } else if (itemId == R.id.toggle_marked) { if (activeArticle != null) { Article articleClone = new Article(activeArticle); articleClone.marked = !articleClone.marked; saveArticleMarked(articleClone); } return true; } else if (itemId == R.id.toggle_unread) { if (activeArticle != null) { Article articleClone = new Article(activeArticle); articleClone.unread = !articleClone.unread; saveArticleUnread(articleClone); } return true; } else if (itemId == R.id.selection_toggle_unread) { List
selected = Application.getArticlesModel().getSelected(); if (!selected.isEmpty()) { for (Article a : selected) { Article articleClone = new Article(a); articleClone.unread = !articleClone.unread; } toggleArticlesUnread(selected); } return true; } else if (itemId == R.id.selection_toggle_marked) { List
selected = Application.getArticlesModel().getSelected(); if (!selected.isEmpty()) { for (Article a : selected) { Article articleClone = new Article(a); articleClone.marked = !articleClone.marked; } toggleArticlesMarked(selected); } return true; } else if (itemId == R.id.selection_toggle_published) { List
selected = Application.getArticlesModel().getSelected(); if (!selected.isEmpty()) { for (Article a : selected) { Article articleClone = new Article(a); articleClone.published = !articleClone.published; } toggleArticlesPublished(selected); } return true; } else if (itemId == R.id.toggle_published) { if (activeArticle != null) { Article articleClone = new Article(activeArticle); articleClone.published = !articleClone.published; saveArticlePublished(articleClone); } return true; } else if (itemId == R.id.catchup_above) { if (activeArticle != null) { confirmCatchupAbove(activeArticle); } return true; } else if (itemId == R.id.article_set_labels) { if (getApiLevel() != 7) { if (activeArticle != null) editArticleLabels(activeArticle); } else { toast(R.string.server_function_not_available); } return true; } Log.d(TAG, "onOptionsItemSelected, unhandled id=" + item.getItemId()); return super.onOptionsItemSelected(item); } protected void catchupAbove(final Article startingArticle) { if (startingArticle != null) { List
tmp = new ArrayList<>(); for (Article a : Application.getArticles()) { if (a.id == startingArticle.id) break; Article articleClone = new Article(a); if (articleClone.unread) { articleClone.unread = false; tmp.add(articleClone); } } if (!tmp.isEmpty()) { setArticlesUnread(tmp, Article.UPDATE_SET_FALSE); } } } public void editArticleNote(final Article article) { MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this) .setTitle(article.title); final EditText topicEdit = new EditText(this); topicEdit.setText(article.note); builder.setView(topicEdit); builder.setPositiveButton(R.string.article_edit_note, (dialog, which) -> { String note = topicEdit.getText().toString().trim(); saveArticleNote(article, note); }); builder.setNegativeButton(R.string.dialog_cancel, (dialog, which) -> { // }); Dialog dialog = builder.create(); dialog.show(); } public void editArticleLabels(Article article) { final int articleId = article.id; ApiRequest req = new ApiRequest(getApplicationContext()) { @Override protected void onPostExecute(JsonElement result) { if (result != null) { Type listType = new TypeToken>() { }.getType(); final List