From 0b5d49463a295ba060b8bd6ad65046496068c9dc Mon Sep 17 00:00:00 2001 From: Andrew Dolgov Date: Wed, 21 May 2025 07:57:23 +0300 Subject: drop ArticleList class, replace with instances of ArrayList/List/Collections where appropriate --- .../src/main/java/org/fox/ttrss/Application.java | 5 +- .../src/main/java/org/fox/ttrss/ArticleModel.java | 38 ++++++---- .../src/main/java/org/fox/ttrss/ArticlePager.java | 5 +- .../main/java/org/fox/ttrss/HeadlinesFragment.java | 60 ++++------------ .../main/java/org/fox/ttrss/OnlineActivity.java | 80 +++++++++++----------- .../main/java/org/fox/ttrss/types/ArticleList.java | 53 -------------- org.fox.ttrss/src/main/res/values/strings.xml | 1 + 7 files changed, 88 insertions(+), 154 deletions(-) delete mode 100755 org.fox.ttrss/src/main/java/org/fox/ttrss/types/ArticleList.java (limited to 'org.fox.ttrss') diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/Application.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/Application.java index 56546a33..335815e8 100755 --- a/org.fox.ttrss/src/main/java/org/fox/ttrss/Application.java +++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/Application.java @@ -12,10 +12,11 @@ import org.acra.config.CoreConfigurationBuilder; import org.acra.config.DialogConfigurationBuilder; import org.acra.config.MailSenderConfigurationBuilder; import org.acra.data.StringFormat; -import org.fox.ttrss.types.ArticleList; +import org.fox.ttrss.types.Article; import java.util.HashMap; import java.util.LinkedHashMap; +import java.util.List; public class Application extends android.app.Application { @@ -31,7 +32,7 @@ public class Application extends android.app.Application { return m_singleton; } - public static ArticleList getArticles() { + public static List
getArticles() { return getInstance().m_articleModel.getArticles().getValue(); } diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/ArticleModel.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/ArticleModel.java index 366a8fa8..98d9e8ba 100644 --- a/org.fox.ttrss/src/main/java/org/fox/ttrss/ArticleModel.java +++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/ArticleModel.java @@ -19,18 +19,19 @@ import com.google.gson.JsonObject; import com.google.gson.reflect.TypeToken; import org.fox.ttrss.types.Article; -import org.fox.ttrss.types.ArticleList; import org.fox.ttrss.types.Feed; import java.lang.reflect.Type; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.stream.Collectors; public class ArticleModel extends AndroidViewModel implements ApiCommon.ApiCaller { private final String TAG = this.getClass().getSimpleName(); - @NonNull private final MutableLiveData m_articles = new MutableLiveData<>(new ArticleList()); + @NonNull private final MutableLiveData> m_articles = new MutableLiveData<>(new ArrayList
()); private SharedPreferences m_prefs; private final int m_responseCode = 0; protected String m_responseMessage; @@ -67,7 +68,7 @@ public class ArticleModel extends AndroidViewModel implements ApiCommon.ApiCalle return m_lastUpdate; } - public LiveData getArticles() { + public LiveData> getArticles() { return m_articles; } @@ -83,7 +84,7 @@ public class ArticleModel extends AndroidViewModel implements ApiCommon.ApiCalle m_articles.postValue(m_articles.getValue()); } - public void update(@NonNull ArticleList articles) { + public void update(@NonNull List
articles) { m_articles.postValue(articles); } @@ -91,13 +92,14 @@ public class ArticleModel extends AndroidViewModel implements ApiCommon.ApiCalle return m_activeArticle; } + /** returns null if there's none or it is invalid (missing in list) */ public Article getActiveArticle() { - ArticleList articles = m_articles.getValue(); + List
articles = m_articles.getValue(); try { - // always get uptodate item from model list + // always get uptodate item from model list if possible return articles.get(articles.indexOf(m_activeArticle.getValue())); - } catch (ArrayIndexOutOfBoundsException e) { + } catch (IndexOutOfBoundsException e) { return null; } } @@ -160,7 +162,7 @@ public class ArticleModel extends AndroidViewModel implements ApiCommon.ApiCalle public enum ArticlesSelection { ALL, NONE, UNREAD } public void setSelection(@NonNull ArticlesSelection select) { - ArticleList articles = m_articles.getValue(); + List
articles = m_articles.getValue(); for (int i = 0; i < articles.size(); i++) { Article articleClone = new Article(articles.get(i)); @@ -178,7 +180,7 @@ public class ArticleModel extends AndroidViewModel implements ApiCommon.ApiCalle private void loadInBackground() { Log.d(TAG, this + " loadInBackground append=" + m_append + " offset=" + m_offset + " lazyLoadEnabled=" + m_lazyLoadEnabled); - final ArticleList articlesWork = new ArticleList(m_articles.getValue()); + final List
articlesWork = new ArrayList<>(m_articles.getValue()); m_isLoading.postValue(true); @@ -270,11 +272,13 @@ public class ArticleModel extends AndroidViewModel implements ApiCommon.ApiCalle m_amountLoaded = articlesJson.size(); for (Article article : articlesJson) - if (!articlesWork.containsId(article.id)) { + if (!articlesWork.contains(article)) { article.collectMediaInfo(); article.cleanupExcerpt(); article.fixNullFields(); articlesWork.add(article); + } else { + Log.d(TAG, "duplicate:" + article); } if (m_firstIdChanged) { @@ -308,14 +312,14 @@ public class ArticleModel extends AndroidViewModel implements ApiCommon.ApiCalle }); } - private int getSkip(boolean append, @NonNull ArticleList articles) { + private int getSkip(boolean append, @NonNull List
articles) { int skip = 0; if (append) { // adaptive, all_articles, marked, published, unread String viewMode = m_prefs.getString("view_mode", "adaptive"); - int numUnread = Math.toIntExact(articles.getUnreadCount()); + int numUnread = Math.toIntExact(getUnread(articles).size()); int numAll = Math.toIntExact(articles.size()); if ("marked".equals(viewMode)) { @@ -408,4 +412,14 @@ public class ArticleModel extends AndroidViewModel implements ApiCommon.ApiCalle startLoading(false, m_feed, m_resizeWidth); } } + + + public List
getUnread(List
articles) { + return articles.stream().filter(a -> { return a.unread; }).collect(Collectors.toList()); + } + + public List
getSelected() { + return m_articles.getValue().stream().filter(a -> { return a.selected; }).collect(Collectors.toList()); + } + } diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/ArticlePager.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/ArticlePager.java index f411b7e0..7632ea19 100755 --- a/org.fox.ttrss/src/main/java/org/fox/ttrss/ArticlePager.java +++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/ArticlePager.java @@ -13,11 +13,12 @@ import androidx.fragment.app.Fragment; import androidx.viewpager2.widget.ViewPager2; import org.fox.ttrss.types.Article; -import org.fox.ttrss.types.ArticleList; import org.fox.ttrss.types.Feed; import org.fox.ttrss.util.ArticleDiffItemCallback; import org.fox.ttrss.util.DiffFragmentStateAdapter; +import java.util.ArrayList; + public class ArticlePager extends androidx.fragment.app.Fragment { private final String TAG = this.getClass().getSimpleName(); @@ -34,7 +35,7 @@ public class ArticlePager extends androidx.fragment.app.Fragment { } private void syncToSharedArticles() { - submitList(new ArticleList(Application.getArticles())); + submitList(new ArrayList<>(Application.getArticles())); } @Override diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/HeadlinesFragment.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/HeadlinesFragment.java index fb4452db..6621a37c 100755 --- a/org.fox.ttrss/src/main/java/org/fox/ttrss/HeadlinesFragment.java +++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/HeadlinesFragment.java @@ -75,7 +75,6 @@ import com.google.android.material.snackbar.Snackbar; import org.fox.ttrss.glide.ProgressTarget; import org.fox.ttrss.types.Article; -import org.fox.ttrss.types.ArticleList; import org.fox.ttrss.types.Attachment; import org.fox.ttrss.types.Feed; import org.fox.ttrss.util.ArticleDiffItemCallback; @@ -84,6 +83,7 @@ import org.jsoup.nodes.Element; import java.text.DateFormat; import java.text.SimpleDateFormat; +import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.TimeZone; @@ -108,7 +108,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { private SharedPreferences m_prefs; private ArticleListAdapter m_adapter; - private final ArticleList m_readArticles = new ArticleList(); + private final List
m_readArticles = new ArrayList<>(); private HeadlinesEventListener m_listener; private OnlineActivity m_activity; private SwipeRefreshLayout m_swipeLayout; @@ -164,45 +164,13 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { m_activity.shareArticle(article); return true; } else if (itemId == R.id.catchup_above) { - final Article fa = article; - - MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(getContext()) - .setMessage(R.string.confirm_catchup_above) - .setPositiveButton(R.string.dialog_ok, - (dialog, which) -> catchupAbove(fa)) - .setNegativeButton(R.string.dialog_cancel, - (dialog, which) -> { }); - - Dialog dialog = builder.create(); - dialog.show(); + m_activity.confirmCatchupAbove(article); return true; } Log.d(TAG, "onArticleMenuItemSelected, unhandled id=" + item.getItemId()); return false; } - private void catchupAbove(Article article) { - - ArticleList tmp = new ArticleList(); - ArticleList articles = Application.getArticles(); - - for (Article a : articles) { - if (article.equalsById(a)) - break; - - if (a.unread) { - Article articleClone = new Article(a); - articleClone.unread = false; - - tmp.add(articleClone); - } - } - - if (!tmp.isEmpty()) { - m_activity.setArticlesUnread(tmp, Article.UPDATE_SET_FALSE); - } - } - // all onContextItemSelected are invoked in sequence so we might get a context menu for headlines, etc public boolean onContextItemSelected(MenuItem item) { AdapterContextMenuInfo info = (AdapterContextMenuInfo) item @@ -359,7 +327,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { wasUnread = false; } - ArticleList tmpRemove = new ArticleList(Application.getArticles()); + List
tmpRemove = new ArrayList<>(Application.getArticles()); tmpRemove.remove(adapterPosition); Application.getArticlesModel().update(tmpRemove); @@ -372,7 +340,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { m_activity.saveArticleUnread(article); } - ArticleList tmpInsert = new ArticleList(Application.getArticles()); + List
tmpInsert = new ArrayList<>(Application.getArticles()); tmpInsert.add(adapterPosition, article); Application.getArticlesModel().update(tmpInsert); @@ -392,7 +360,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { m_list.setOnScrollListener(new RecyclerView.OnScrollListener() { @Override - public void onScrollStateChanged(RecyclerView recyclerView, int newState) { + public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); ArticleModel model = Application.getArticlesModel(); @@ -401,7 +369,9 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { if (!m_readArticles.isEmpty() && !m_isLazyLoading && !model.isLoading() && m_prefs.getBoolean("headlines_mark_read_scroll", false)) { Log.d(TAG, "marking articles as read, count=" + m_readArticles.size()); - m_activity.setArticlesUnread(m_readArticles, Article.UPDATE_SET_FALSE); + // since we clear the list after we send the batch to mark as read, we need to pass a cloned arraylist here, + // otherwise nothing would get marked as read when async operation completes + m_activity.setArticlesUnread(new ArrayList<>(m_readArticles), Article.UPDATE_SET_FALSE); m_readArticles.clear(); @@ -411,7 +381,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { } @Override - public void onScrolled(RecyclerView recyclerView, int dx, int dy) { + public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); int firstVisibleItem = m_layoutManager.findFirstVisibleItemPosition(); @@ -425,7 +395,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { Article article = Application.getArticles().get(i); if (article.unread && !m_readArticles.contains(article)) - m_readArticles.add(article); + m_readArticles.add(new Article(article)); } catch (IndexOutOfBoundsException e) { e.printStackTrace(); @@ -478,7 +448,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { // this gets notified on network update model.getUpdatesData().observe(getActivity(), lastUpdate -> { if (lastUpdate > 0) { - ArticleList tmp = new ArticleList(model.getArticles().getValue()); + List
tmp = new ArrayList<>(model.getArticles().getValue()); Log.d(TAG, "observed headlines last update=" + lastUpdate + " article count=" + tmp.size()); @@ -524,7 +494,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { model.getArticles().observe(getActivity(), articles -> { Log.d(TAG, "observed headlines article list size=" + articles.size()); - ArticleList tmp = new ArticleList(articles); + List
tmp = new ArrayList<>(articles); if (m_prefs.getBoolean("headlines_mark_read_scroll", false)) tmp.add(new Article(Article.TYPE_AMR_FOOTER)); @@ -1597,9 +1567,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { } private void syncToSharedArticles() { - ArticleList tmp = new ArticleList(); - - tmp.addAll(Application.getArticles()); + List
tmp = new ArrayList<>(Application.getArticles()); if (m_prefs.getBoolean("headlines_mark_read_scroll", false)) tmp.add(new Article(Article.TYPE_AMR_FOOTER)); diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/OnlineActivity.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/OnlineActivity.java index 1cdd972f..50f9d533 100755 --- a/org.fox.ttrss/src/main/java/org/fox/ttrss/OnlineActivity.java +++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/OnlineActivity.java @@ -38,12 +38,13 @@ import com.google.gson.reflect.TypeToken; import org.fox.ttrss.share.SubscribeActivity; import org.fox.ttrss.types.Article; -import org.fox.ttrss.types.ArticleList; 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; @@ -121,7 +122,19 @@ public class OnlineActivity extends CommonActivity { } } - //protected PullToRefreshAttacher m_pullToRefreshAttacher; + 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(); @@ -550,7 +563,7 @@ public class OnlineActivity extends CommonActivity { } return true; } else if (itemId == R.id.selection_toggle_unread) { - ArticleList selected = Application.getArticles().getSelected(); + List
selected = Application.getArticlesModel().getSelected(); if (!selected.isEmpty()) { for (Article a : selected) { @@ -562,7 +575,7 @@ public class OnlineActivity extends CommonActivity { } return true; } else if (itemId == R.id.selection_toggle_marked) { - ArticleList selected = Application.getArticles().getSelected(); + List
selected = Application.getArticlesModel().getSelected(); if (!selected.isEmpty()) { for (Article a : selected) { @@ -574,7 +587,7 @@ public class OnlineActivity extends CommonActivity { } return true; } else if (itemId == R.id.selection_toggle_published) { - ArticleList selected = Application.getArticles().getSelected(); + List
selected = Application.getArticlesModel().getSelected(); if (!selected.isEmpty()) { for (Article a : selected) { @@ -594,18 +607,9 @@ public class OnlineActivity extends CommonActivity { } return true; } else if (itemId == R.id.catchup_above) { - MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this) - .setMessage(R.string.confirm_catchup_above) - .setPositiveButton(R.string.dialog_ok, - (dialog, which) -> catchupAbove()) - .setNegativeButton(R.string.dialog_cancel, - (dialog, which) -> { - - }); - - Dialog dialog = builder.create(); - dialog.show(); - + if (activeArticle != null) { + confirmCatchupAbove(activeArticle); + } return true; } else if (itemId == R.id.article_set_labels) { if (getApiLevel() != 7) { @@ -621,14 +625,12 @@ public class OnlineActivity extends CommonActivity { return super.onOptionsItemSelected(item); } - private void catchupAbove() { - Article activeArticle = Application.getArticlesModel().getActiveArticle(); - - if (activeArticle != null) { - ArticleList tmp = new ArticleList(); + protected void catchupAbove(final Article startingArticle) { + if (startingArticle != null) { + List
tmp = new ArrayList<>(); for (Article a : Application.getArticles()) { - if (a.id == activeArticle.id) + if (a.id == startingArticle.id) break; Article articleClone = new Article(a); @@ -637,8 +639,6 @@ public class OnlineActivity extends CommonActivity { articleClone.unread = false; tmp.add(articleClone); - - Application.getArticlesModel().update(articleClone); } } @@ -919,53 +919,53 @@ public class OnlineActivity extends CommonActivity { } public void saveArticleUnread(final Article article) { - setArticlesField(new ArticleList(article), Article.UPDATE_FIELD_UNREAD, + setArticlesField(Collections.singletonList(article), Article.UPDATE_FIELD_UNREAD, article.unread ? Article.UPDATE_SET_TRUE : Article.UPDATE_SET_FALSE); } public void saveArticleScore(final Article article) { - setArticlesField(new ArticleList(article), Article.UPDATE_FIELD_SCORE, Article.UPDATE_SET_TRUE); + setArticlesField(Collections.singletonList(article), Article.UPDATE_FIELD_SCORE, Article.UPDATE_SET_TRUE); } public void saveArticleMarked(final Article article) { - setArticlesField(new ArticleList(article), Article.UPDATE_FIELD_MARKED, + setArticlesField(Collections.singletonList(article), Article.UPDATE_FIELD_MARKED, article.marked ? Article.UPDATE_SET_TRUE : Article.UPDATE_SET_FALSE); } public void saveArticlePublished(final Article article) { - setArticlesField(new ArticleList(article), Article.UPDATE_FIELD_PUBLISHED, + setArticlesField(Collections.singletonList(article), Article.UPDATE_FIELD_PUBLISHED, article.published ? Article.UPDATE_SET_TRUE : Article.UPDATE_SET_FALSE); } public void saveArticleNote(final Article article, final String note) { - setArticlesField(new ArticleList(article), Article.UPDATE_FIELD_NOTE, Article.UPDATE_SET_TRUE); + setArticlesField(Collections.singletonList(article), Article.UPDATE_FIELD_NOTE, Article.UPDATE_SET_TRUE); } - public void toggleArticlesMarked(final ArticleList articles) { + public void toggleArticlesMarked(final List
articles) { setArticlesMarked(articles, Article.UPDATE_TOGGLE); } - public void setArticlesMarked(final ArticleList articles, int mode) { + public void setArticlesMarked(final List
articles, int mode) { setArticlesField(articles, Article.UPDATE_FIELD_MARKED, mode); } - public void toggleArticlesUnread(final ArticleList articles) { + public void toggleArticlesUnread(final List
articles) { setArticlesUnread(articles, Article.UPDATE_FIELD_UNREAD); } - public void setArticlesUnread(final ArticleList articles, int mode) { + public void setArticlesUnread(final List
articles, int mode) { setArticlesField(articles, Article.UPDATE_FIELD_UNREAD, mode); } - public void toggleArticlesPublished(final ArticleList articles) { + public void toggleArticlesPublished(final List
articles) { setArticlesPublished(articles, Article.UPDATE_TOGGLE); } - public void setArticlesPublished(final ArticleList articles, int mode) { + public void setArticlesPublished(final List
articles, int mode) { setArticlesField(articles, Article.UPDATE_FIELD_PUBLISHED, mode); } - public void setArticlesField(final ArticleList articles, int field, int mode) { + public void setArticlesField(final List
articles, int field, int mode) { ApiRequest req = new ApiRequest(getApplicationContext()) { protected void onPostExecute(JsonElement result) { if (m_lastError == ApiCommon.ApiError.SUCCESS) { @@ -1319,8 +1319,10 @@ public class OnlineActivity extends CommonActivity { } public void enableActionModeObserver() { - Application.getArticlesModel().getArticles().observe(this, (articles -> { - int selectedCount = articles.getSelectedCount(); + ArticleModel model = Application.getArticlesModel(); + + model.getArticles().observe(this, (articles -> { + int selectedCount = model.getSelected().size(); Log.d(TAG, "observed selected articles=" + selectedCount); diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/types/ArticleList.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/types/ArticleList.java deleted file mode 100755 index 89530231..00000000 --- a/org.fox.ttrss/src/main/java/org/fox/ttrss/types/ArticleList.java +++ /dev/null @@ -1,53 +0,0 @@ -package org.fox.ttrss.types; - -import java.util.Arrays; -import java.util.Collection; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.stream.Collectors; - -public class ArticleList extends CopyOnWriteArrayList
{ - public boolean containsId(int id) { - return findById(id) != null; - } - - public boolean contains(Article article) { - return containsId(article.id); - } - - public Article findById(int id) { - for (Article a : this) { - if (a.id == id) - return a; - } - return null; - } - - public ArticleList(Article... articles) { - super(); - - addAll(Arrays.asList(articles)); - } - - public ArticleList() { } - - public ArticleList(ArticleList clone) { - this.addAll(clone); - } - - public int getUnreadCount() { - return getUnread().size(); - } - - public ArticleList getUnread() { - return this.stream().filter(a -> { return a.unread; }).collect(Collectors.toCollection(ArticleList::new)); - } - - public ArticleList getSelected() { - return this.stream().filter(a -> { return a.selected; }).collect(Collectors.toCollection(ArticleList::new)); - } - - public int getSelectedCount() { - return getSelected().size(); - } - -} \ No newline at end of file diff --git a/org.fox.ttrss/src/main/res/values/strings.xml b/org.fox.ttrss/src/main/res/values/strings.xml index e3111027..46001904 100755 --- a/org.fox.ttrss/src/main/res/values/strings.xml +++ b/org.fox.ttrss/src/main/res/values/strings.xml @@ -250,6 +250,7 @@ Opening links Open with… Mark articles as read? + Mark all articles above \"%1s\" as read? OK UNDO Marked as read -- cgit v1.2.3-54-g00ecf