From 895b0d9c2473fcd3351c5368176b79e63b1d6079 Mon Sep 17 00:00:00 2001 From: Andrew Dolgov Date: Wed, 14 May 2025 18:43:19 +0300 Subject: refactor to more consistent naming, allow observing articlelist changes and update requests separately --- .../src/main/java/org/fox/ttrss/Application.java | 6 +- .../src/main/java/org/fox/ttrss/ArticlePager.java | 8 +- .../src/main/java/org/fox/ttrss/ArticlesModel.java | 324 +++++++++++++++++++++ .../main/java/org/fox/ttrss/HeadlinesFragment.java | 54 ++-- .../main/java/org/fox/ttrss/HeadlinesModel.java | 319 -------------------- .../main/java/org/fox/ttrss/OnlineActivity.java | 17 +- .../src/main/java/org/fox/ttrss/types/Article.java | 39 ++- .../main/java/org/fox/ttrss/types/ArticleList.java | 4 + .../fox/ttrss/util/ArticleDiffItemCallback.java | 21 ++ .../fox/ttrss/util/HeadlinesDiffItemCallback.java | 22 -- 10 files changed, 437 insertions(+), 377 deletions(-) create mode 100644 org.fox.ttrss/src/main/java/org/fox/ttrss/ArticlesModel.java delete mode 100644 org.fox.ttrss/src/main/java/org/fox/ttrss/HeadlinesModel.java create mode 100644 org.fox.ttrss/src/main/java/org/fox/ttrss/util/ArticleDiffItemCallback.java delete mode 100644 org.fox.ttrss/src/main/java/org/fox/ttrss/util/HeadlinesDiffItemCallback.java (limited to 'org.fox.ttrss/src/main/java') 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 08beaa99..441929f4 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 @@ -25,7 +25,7 @@ public class Application extends android.app.Application { private int m_apiLevel; public LinkedHashMap m_customSortModes = new LinkedHashMap<>(); ConnectivityManager m_cmgr; - HeadlinesModel m_headlinesModel; + ArticlesModel m_headlinesModel; public static Application getInstance(){ return m_singleton; @@ -35,7 +35,7 @@ public class Application extends android.app.Application { return getInstance().m_headlinesModel.getArticles(); } - public HeadlinesModel getHeadlinesModel() { + public static ArticlesModel getHeadlinesModel() { return getInstance().m_headlinesModel; } @@ -45,7 +45,7 @@ public class Application extends android.app.Application { m_singleton = this; m_cmgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); - m_headlinesModel = new HeadlinesModel(this); + m_headlinesModel = new ArticlesModel(this); } public String getSessionId() { 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 89043cbc..ce3b114e 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 @@ -16,7 +16,7 @@ import org.fox.ttrss.types.Article; import org.fox.ttrss.types.ArticleList; import org.fox.ttrss.types.Feed; import org.fox.ttrss.util.DiffFragmentStateAdapter; -import org.fox.ttrss.util.HeadlinesDiffItemCallback; +import org.fox.ttrss.util.ArticleDiffItemCallback; public class ArticlePager extends androidx.fragment.app.Fragment { @@ -31,7 +31,7 @@ public class ArticlePager extends androidx.fragment.app.Fragment { private static class PagerAdapter extends DiffFragmentStateAdapter
{ public PagerAdapter(@NonNull Fragment fragment) { - super(fragment, new HeadlinesDiffItemCallback()); + super(fragment, new ArticleDiffItemCallback()); } private void syncToSharedArticles() { @@ -85,10 +85,10 @@ public class ArticlePager extends androidx.fragment.app.Fragment { m_adapter = new PagerAdapter(this); m_adapter.submitList(Application.getArticles()); - HeadlinesModel model = Application.getInstance().getHeadlinesModel(); + ArticlesModel model = Application.getInstance().getHeadlinesModel(); // deal with further updates - model.getLiveData().observe(getActivity(), articles -> { + model.getArticlesData().observe(getActivity(), articles -> { Log.d(TAG, "observed article list size=" + articles.size()); m_adapter.submitList(articles); }); diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/ArticlesModel.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/ArticlesModel.java new file mode 100644 index 00000000..af535580 --- /dev/null +++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/ArticlesModel.java @@ -0,0 +1,324 @@ +package org.fox.ttrss; + +import android.app.Application; +import android.content.SharedPreferences; +import android.os.Handler; +import android.os.Looper; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.lifecycle.AndroidViewModel; +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; +import androidx.preference.PreferenceManager; + +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +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.HashMap; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class ArticlesModel extends AndroidViewModel implements ApiCommon.ApiCaller { + private final String TAG = this.getClass().getSimpleName(); + private final MutableLiveData m_articles = new MutableLiveData<>(new ArticleList()); + private SharedPreferences m_prefs; + private final int m_responseCode = 0; + protected String m_responseMessage; + private int m_apiStatusCode = 0; + + private String m_lastErrorMessage; + private ApiCommon.ApiError m_lastError; + private Feed m_feed; + private int m_firstId; + private String m_searchQuery = ""; + private boolean m_firstIdChanged; + private int m_offset; + private int m_amountLoaded; + private int m_resizeWidth; + private boolean m_append; + private boolean m_lazyLoadEnabled = true; + private boolean m_loadingInProgress; + private ExecutorService m_executor; + private Handler m_mainHandler = new Handler(Looper.getMainLooper()); + private MutableLiveData m_lastUpdate = new MutableLiveData<>(new Long(0)); + + public ArticlesModel(@NonNull Application application) { + super(application); + + m_prefs = PreferenceManager.getDefaultSharedPreferences(application); + + // do we need concurrency or not? + m_executor = Executors.newSingleThreadExecutor(); + } + + public LiveData getUpdatesData() { + return m_lastUpdate; + } + + public LiveData getArticlesData() { + return m_articles; + } + + public ArticleList getArticles() { + return m_articles.getValue(); + } + + public void update(int position, Article article) { + m_articles.getValue().set(position, article); + m_articles.postValue(m_articles.getValue()); + } + + public void update(ArticleList articles) { + m_articles.postValue(articles); + } + + public void startLoading(boolean append, @NonNull Feed feed, int resizeWidth) { + Log.d(TAG, "startLoading append=" + append); + + m_resizeWidth = resizeWidth; + + if (!append) { + m_append = false; + m_lazyLoadEnabled = true; + m_feed = feed; + + forceLoad(); + } else if (feed != m_feed || m_lazyLoadEnabled && !m_loadingInProgress) { + m_append = true; + m_feed = feed; + forceLoad(); + } else { + m_articles.postValue(m_articles.getValue()); + } + } + + private void forceLoad() { + Log.d(TAG, "forceLoad"); + loadInBackground(); + } + + private ArticleList loadInBackground() { + Log.d(TAG, this + " loadInBackground append=" + m_append + " offset=" + m_offset); + + ArticleList articlesWork = new ArticleList(); + articlesWork.addAll(m_articles.getValue()); + + m_loadingInProgress = true; + + final int skip = getSkip(m_append, articlesWork); + final boolean allowForceUpdate = org.fox.ttrss.Application.getInstance().getApiLevel() >= 9 && + !m_feed.is_cat && m_feed.id > 0 && !m_append && skip == 0; + + HashMap params = new HashMap<>(); + + params.put("op", "getHeadlines"); + params.put("sid", org.fox.ttrss.Application.getInstance().getSessionId()); + params.put("feed_id", String.valueOf(m_feed.id)); + params.put("show_excerpt", "true"); + params.put("excerpt_length", String.valueOf(CommonActivity.EXCERPT_MAX_LENGTH)); + params.put("show_content", "true"); + params.put("include_attachments", "true"); + params.put("view_mode", m_prefs.getString("view_mode", "adaptive")); + params.put("limit", m_prefs.getString("headlines_request_size", "15")); + params.put("skip", String.valueOf(skip)); + params.put("include_nested", "true"); + params.put("has_sandbox", "true"); + params.put("order_by", m_prefs.getString("headlines_sort_mode", "default")); + + if (m_prefs.getBoolean("enable_image_downsampling", false)) { + if (m_prefs.getBoolean("always_downsample_images", false) || !org.fox.ttrss.Application.getInstance().isWifiConnected()) { + params.put("resize_width", String.valueOf(m_resizeWidth)); + } + } + + if (m_feed.is_cat) + params.put("is_cat", "true"); + + if (allowForceUpdate) { + params.put("force_update", "true"); + } + + if (m_searchQuery != null && !m_searchQuery.isEmpty()) { + params.put("search", m_searchQuery); + params.put("search_mode", ""); + params.put("match_on", "both"); + } + + if (m_firstId > 0) + params.put("check_first_id", String.valueOf(m_firstId)); + + if (org.fox.ttrss.Application.getInstance().getApiLevel() >= 12) { + params.put("include_header", "true"); + } + + Log.d(TAG, "firstId=" + m_firstId + " append=" + m_append + " skip=" + skip + " localSize=" + articlesWork.size()); + + m_executor.execute(() -> { + JsonElement result = ApiCommon.performRequest(getApplication(), params, this); + + Log.d(TAG, "got result=" + result); + + if (result != null) { + try { + JsonArray content = result.getAsJsonArray(); + if (content != null) { + final List
articlesJson; + final JsonObject header; + + if (org.fox.ttrss.Application.getInstance().getApiLevel() >= 12) { + header = content.get(0).getAsJsonObject(); + + m_firstIdChanged = header.get("first_id_changed") != null; + + try { + m_firstId = header.get("first_id").getAsInt(); + } catch (NumberFormatException e) { + m_firstId = 0; + } + + Log.d(TAG, this + " firstID=" + m_firstId + " firstIdChanged=" + m_firstIdChanged); + + Type listType = new TypeToken>() {}.getType(); + articlesJson = new Gson().fromJson(content.get(1), listType); + } else { + Type listType = new TypeToken>() {}.getType(); + articlesJson = new Gson().fromJson(content, listType); + } + + if (!m_append) + articlesWork.clear(); + + m_amountLoaded = articlesJson.size(); + + for (Article article : articlesJson) + if (!articlesWork.containsId(article.id)) { + article.collectMediaInfo(); + article.cleanupExcerpt(); + article.fixNullFields(); + articlesWork.add(article); + } + + if (m_firstIdChanged) { + Log.d(TAG, "first id changed, disabling lazy load"); + m_lazyLoadEnabled = false; + } + + if (m_amountLoaded < Integer.parseInt(m_prefs.getString("headlines_request_size", "15"))) { + Log.d(TAG, this + " amount loaded "+m_amountLoaded+" < request size, disabling lazy load"); + m_lazyLoadEnabled = false; + } + + m_offset += m_amountLoaded; + m_loadingInProgress = false; + + Log.d(TAG, this + " loaded headlines=" + m_amountLoaded + " resultingLocalSize=" + articlesWork.size()); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + m_mainHandler.post(() -> { + m_articles.setValue(articlesWork); + m_lastUpdate.setValue(System.currentTimeMillis()); + }); + }); + + m_loadingInProgress = false; + + return articlesWork; + } + + private int getSkip(boolean append, ArticleList 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 numAll = Math.toIntExact(articles.size()); + + if ("marked".equals(viewMode)) { + skip = numAll; + } else if ("published".equals(viewMode)) { + skip = numAll; + } else if ("unread".equals(viewMode)) { + skip = numUnread; + } else if (m_searchQuery != null && !m_searchQuery.isEmpty()) { + skip = numAll; + } else if ("adaptive".equals(viewMode)) { + skip = numUnread > 0 ? numUnread : numAll; + } else { + skip = numAll; + } + } + + return skip; + } + + @Override + public void setStatusCode(int statusCode) { + m_apiStatusCode = statusCode; + } + + @Override + public void setLastError(ApiCommon.ApiError lastError) { + m_lastError = lastError; + } + + @Override + public void setLastErrorMessage(String message) { + m_lastErrorMessage = message; + } + + public boolean getFirstIdChanged() { + return m_firstIdChanged; + } + + public boolean getAppend() { + return m_append; + } + + public void setSearchQuery(String searchQuery) { + m_searchQuery = searchQuery; + } + + public String getSearchQuery() { + return m_searchQuery; + } + + public int getOffset() { + return m_offset; + } + + public boolean lazyLoadEnabled() { + return m_lazyLoadEnabled; + } + + public int getErrorMessage() { + return ApiCommon.getErrorMessage(m_lastError); + } + + ApiCommon.ApiError getLastError() { + return m_lastError; + } + + String getLastErrorMessage() { + return m_lastErrorMessage; + } + + public boolean isLoading() { + return m_loadingInProgress; + } +} 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 2e6a4323..62f32cfa 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 @@ -43,12 +43,9 @@ import android.widget.PopupMenu; import android.widget.ProgressBar; import android.widget.TextView; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import androidx.core.app.ActivityCompat; import androidx.core.app.ActivityOptionsCompat; import androidx.core.view.ViewCompat; -import androidx.loader.content.Loader; import androidx.preference.PreferenceManager; import androidx.recyclerview.widget.DefaultItemAnimator; import androidx.recyclerview.widget.ItemTouchHelper; @@ -74,13 +71,12 @@ 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.HeadlinesDiffItemCallback; +import org.fox.ttrss.util.ArticleDiffItemCallback; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import java.text.DateFormat; import java.text.SimpleDateFormat; -import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.TimeZone; @@ -395,7 +391,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { public void onScrollStateChanged(RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); - HeadlinesModel model = Application.getInstance().getHeadlinesModel(); + ArticlesModel model = Application.getInstance().getHeadlinesModel(); if (newState == RecyclerView.SCROLL_STATE_IDLE) { if (!m_readArticles.isEmpty() && !m_isLazyLoading && !model.isLoading() && m_prefs.getBoolean("headlines_mark_read_scroll", false)) { @@ -444,7 +440,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { // Log.d(TAG, "pending to auto mark as read count=" + m_readArticles.size()); } - HeadlinesModel model = Application.getInstance().getHeadlinesModel(); + ArticlesModel model = Application.getInstance().getHeadlinesModel(); if (dy > 0 && !m_isLazyLoading && !model.isLoading() && model.lazyLoadEnabled() && lastVisibleItem >= Application.getArticles().size() - 5) { @@ -463,14 +459,13 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { m_activity.setTitle(m_feed.title); } - HeadlinesModel model = Application.getInstance().getHeadlinesModel(); + ArticlesModel model = Application.getHeadlinesModel(); - model.getLiveData().observe(getActivity(), articles -> { - Log.d(TAG, "observed article list size=" + articles.size() + " append=" + model.getAppend()); + // this gets notified on network update + model.getUpdatesData().observe(getActivity(), lastUpdate -> { + ArticleList tmp = new ArticleList(model.getArticles()); - ArticleList tmp = new ArticleList(); - - tmp.addAll(articles); + Log.d(TAG, "observed last update=" + lastUpdate + " article count=" + tmp.size()); if (m_prefs.getBoolean("headlines_mark_read_scroll", false)) tmp.add(new Article(Article.TYPE_AMR_FOOTER)); @@ -503,6 +498,19 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { m_activity.toast(model.getErrorMessage()); } } + + }); + + // loaded articles might get modified for all sorts of reasons + model.getArticlesData().observe(getActivity(), articles -> { + Log.d(TAG, "observed article list size=" + articles.size()); + + ArticleList tmp = new ArticleList(model.getArticles()); + + if (m_prefs.getBoolean("headlines_mark_read_scroll", false)) + tmp.add(new Article(Article.TYPE_AMR_FOOTER)); + + m_adapter.submitList(tmp); }); return view; @@ -536,7 +544,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { } public void refresh(final boolean append) { - HeadlinesModel model = Application.getInstance().getHeadlinesModel(); + ArticlesModel model = Application.getInstance().getHeadlinesModel(); if (!append) m_activeArticleId = -1; @@ -688,7 +696,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { } public ArticleListAdapter() { - super(new HeadlinesDiffItemCallback()); + super(new ArticleDiffItemCallback()); Display display = m_activity.getWindowManager().getDefaultDisplay(); Point size = new Point(); @@ -836,11 +844,11 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { holder.markedView.setIconTint(ColorStateList.valueOf(tvPrimary.data)); holder.markedView.setOnClickListener(v -> { - article.marked = !article.marked; + Article selectedArticle = new Article(getItem(position)); + selectedArticle.marked = !selectedArticle.marked; - m_adapter.notifyItemChanged(m_list.getChildAdapterPosition(holder.view)); - - m_activity.saveArticleMarked(article); + m_activity.saveArticleMarked(selectedArticle); + Application.getHeadlinesModel().update(position, selectedArticle); }); } @@ -901,10 +909,12 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { holder.publishedView.setIconTint(ColorStateList.valueOf(tvPrimary.data)); holder.publishedView.setOnClickListener(v -> { - article.published = !article.published; - m_adapter.notifyItemChanged(m_list.getChildAdapterPosition(holder.view)); + Article selectedArticle = new Article(getItem(position)); + selectedArticle.published = !selectedArticle.published; + + m_activity.saveArticlePublished(selectedArticle); - m_activity.saveArticlePublished(article); + Application.getHeadlinesModel().update(position, selectedArticle); }); } diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/HeadlinesModel.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/HeadlinesModel.java deleted file mode 100644 index cc8a9d3d..00000000 --- a/org.fox.ttrss/src/main/java/org/fox/ttrss/HeadlinesModel.java +++ /dev/null @@ -1,319 +0,0 @@ -package org.fox.ttrss; - -import android.app.Application; -import android.content.Context; -import android.content.SharedPreferences; -import android.os.Handler; -import android.os.Looper; -import android.util.Log; - -import androidx.annotation.NonNull; -import androidx.lifecycle.AndroidViewModel; -import androidx.lifecycle.LiveData; -import androidx.lifecycle.MutableLiveData; -import androidx.loader.app.LoaderManager; -import androidx.preference.PreferenceManager; - -import com.google.gson.Gson; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -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.HashMap; -import java.util.List; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -public class HeadlinesModel extends AndroidViewModel implements ApiCommon.ApiCaller { - private final String TAG = this.getClass().getSimpleName(); - private final MutableLiveData m_articles = new MutableLiveData<>(new ArticleList()); - private SharedPreferences m_prefs; - private final int m_responseCode = 0; - protected String m_responseMessage; - private int m_apiStatusCode = 0; - - private String m_lastErrorMessage; - private ApiCommon.ApiError m_lastError; - private Feed m_feed; - private int m_firstId; - private String m_searchQuery = ""; - private boolean m_firstIdChanged; - private int m_offset; - private int m_amountLoaded; - private int m_resizeWidth; - private boolean m_append; - private boolean m_lazyLoadEnabled = true; - private boolean m_loadingInProgress; - private ExecutorService m_executor; - private Handler m_mainHandler = new Handler(Looper.getMainLooper()); - - public HeadlinesModel(@NonNull Application application) { - super(application); - - m_prefs = PreferenceManager.getDefaultSharedPreferences(application); - - // do we need concurrency or not? - m_executor = Executors.newSingleThreadExecutor(); - } - - public LiveData getLiveData() { - return m_articles; - } - - public ArticleList getArticles() { - return m_articles.getValue(); - } - - public void update(ArticleList articles) { - m_articles.postValue(articles); - } - - public void startLoading(boolean append, @NonNull Feed feed, int resizeWidth) { - Log.d(TAG, "startLoading append=" + append); - - m_resizeWidth = resizeWidth; - - if (!append) { - m_append = false; - m_lazyLoadEnabled = true; - m_feed = feed; - - forceLoad(); - } else if (feed != m_feed || m_lazyLoadEnabled && !m_loadingInProgress) { - m_append = true; - m_feed = feed; - forceLoad(); - } else { - ArticleList tmp = new ArticleList(); - tmp.addAll(m_articles.getValue()); - - m_articles.postValue(tmp); - } - } - - private void forceLoad() { - Log.d(TAG, "forceLoad"); - - m_articles.postValue(loadInBackground()); - } - - private ArticleList loadInBackground() { - Log.d(TAG, this + " loadInBackground append=" + m_append + " offset=" + m_offset); - - ArticleList articlesWork = new ArticleList(); - articlesWork.addAll(m_articles.getValue()); - - m_loadingInProgress = true; - - final int skip = getSkip(m_append, articlesWork); - final boolean allowForceUpdate = org.fox.ttrss.Application.getInstance().getApiLevel() >= 9 && - !m_feed.is_cat && m_feed.id > 0 && !m_append && skip == 0; - - HashMap params = new HashMap<>(); - - params.put("op", "getHeadlines"); - params.put("sid", org.fox.ttrss.Application.getInstance().getSessionId()); - params.put("feed_id", String.valueOf(m_feed.id)); - params.put("show_excerpt", "true"); - params.put("excerpt_length", String.valueOf(CommonActivity.EXCERPT_MAX_LENGTH)); - params.put("show_content", "true"); - params.put("include_attachments", "true"); - params.put("view_mode", m_prefs.getString("view_mode", "adaptive")); - params.put("limit", m_prefs.getString("headlines_request_size", "15")); - params.put("skip", String.valueOf(skip)); - params.put("include_nested", "true"); - params.put("has_sandbox", "true"); - params.put("order_by", m_prefs.getString("headlines_sort_mode", "default")); - - if (m_prefs.getBoolean("enable_image_downsampling", false)) { - if (m_prefs.getBoolean("always_downsample_images", false) || !org.fox.ttrss.Application.getInstance().isWifiConnected()) { - params.put("resize_width", String.valueOf(m_resizeWidth)); - } - } - - if (m_feed.is_cat) - params.put("is_cat", "true"); - - if (allowForceUpdate) { - params.put("force_update", "true"); - } - - if (m_searchQuery != null && !m_searchQuery.isEmpty()) { - params.put("search", m_searchQuery); - params.put("search_mode", ""); - params.put("match_on", "both"); - } - - if (m_firstId > 0) - params.put("check_first_id", String.valueOf(m_firstId)); - - if (org.fox.ttrss.Application.getInstance().getApiLevel() >= 12) { - params.put("include_header", "true"); - } - - Log.d(TAG, "firstId=" + m_firstId + " append=" + m_append + " skip=" + skip + " localSize=" + articlesWork.size()); - - m_executor.execute(() -> { - JsonElement result = ApiCommon.performRequest(getApplication(), params, this); - - Log.d(TAG, "got result=" + result); - - if (result != null) { - try { - JsonArray content = result.getAsJsonArray(); - if (content != null) { - final List
articlesJson; - final JsonObject header; - - if (org.fox.ttrss.Application.getInstance().getApiLevel() >= 12) { - header = content.get(0).getAsJsonObject(); - - m_firstIdChanged = header.get("first_id_changed") != null; - - try { - m_firstId = header.get("first_id").getAsInt(); - } catch (NumberFormatException e) { - m_firstId = 0; - } - - Log.d(TAG, this + " firstID=" + m_firstId + " firstIdChanged=" + m_firstIdChanged); - - Type listType = new TypeToken>() {}.getType(); - articlesJson = new Gson().fromJson(content.get(1), listType); - } else { - Type listType = new TypeToken>() {}.getType(); - articlesJson = new Gson().fromJson(content, listType); - } - - if (!m_append) - articlesWork.clear(); - - m_amountLoaded = articlesJson.size(); - - for (Article article : articlesJson) - if (!articlesWork.containsId(article.id)) { - article.collectMediaInfo(); - article.cleanupExcerpt(); - article.fixNullFields(); - articlesWork.add(article); - } - - if (m_firstIdChanged) { - Log.d(TAG, "first id changed, disabling lazy load"); - m_lazyLoadEnabled = false; - } - - if (m_amountLoaded < Integer.parseInt(m_prefs.getString("headlines_request_size", "15"))) { - Log.d(TAG, this + " amount loaded "+m_amountLoaded+" < request size, disabling lazy load"); - m_lazyLoadEnabled = false; - } - - m_offset += m_amountLoaded; - m_loadingInProgress = false; - - Log.d(TAG, this + " loaded headlines=" + m_amountLoaded + " resultingLocalSize=" + articlesWork.size()); - } - } catch (Exception e) { - e.printStackTrace(); - } - } - - m_mainHandler.post(() -> { - m_articles.postValue(articlesWork); - }); - }); - - m_loadingInProgress = false; - - return articlesWork; - } - - private int getSkip(boolean append, ArticleList 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 numAll = Math.toIntExact(articles.size()); - - if ("marked".equals(viewMode)) { - skip = numAll; - } else if ("published".equals(viewMode)) { - skip = numAll; - } else if ("unread".equals(viewMode)) { - skip = numUnread; - } else if (m_searchQuery != null && !m_searchQuery.isEmpty()) { - skip = numAll; - } else if ("adaptive".equals(viewMode)) { - skip = numUnread > 0 ? numUnread : numAll; - } else { - skip = numAll; - } - } - - return skip; - } - - @Override - public void setStatusCode(int statusCode) { - m_apiStatusCode = statusCode; - } - - @Override - public void setLastError(ApiCommon.ApiError lastError) { - m_lastError = lastError; - } - - @Override - public void setLastErrorMessage(String message) { - m_lastErrorMessage = message; - } - - public boolean getFirstIdChanged() { - return m_firstIdChanged; - } - - public boolean getAppend() { - return m_append; - } - - public void setSearchQuery(String searchQuery) { - m_searchQuery = searchQuery; - } - - public String getSearchQuery() { - return m_searchQuery; - } - - public int getOffset() { - return m_offset; - } - - public boolean lazyLoadEnabled() { - return m_lazyLoadEnabled; - } - - public int getErrorMessage() { - return ApiCommon.getErrorMessage(m_lastError); - } - - ApiCommon.ApiError getLastError() { - return m_lastError; - } - - String getLastErrorMessage() { - return m_lastErrorMessage; - } - - public boolean isLoading() { - return m_loadingInProgress; - } -} 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 db06415f..4faff8af 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 @@ -864,7 +864,8 @@ public class OnlineActivity extends CommonActivity { protected void setApiLevel(int apiLevel) { Application.getInstance().setApiLevel(apiLevel); } - + + // TODO switch to setArticleField() public void saveArticleUnread(final Article article) { ApiRequest req = new ApiRequest(getApplicationContext()) { protected void onPostExecute(JsonElement result) { @@ -878,11 +879,12 @@ public class OnlineActivity extends CommonActivity { map.put("op", "updateArticle"); map.put("article_ids", String.valueOf(article.id)); map.put("mode", article.unread ? "1" : "0"); - map.put("field", "2"); + map.put("field", String.valueOf(Article.UPDATE_FIELD_UNREAD)); req.execute(map); } + // TODO switch to setArticleField() public void saveArticleScore(final Article article) { ApiRequest req = new ApiRequest(getApplicationContext()) { protected void onPostExecute(JsonElement result) { @@ -896,11 +898,12 @@ public class OnlineActivity extends CommonActivity { map.put("op", "updateArticle"); map.put("article_ids", String.valueOf(article.id)); map.put("data", String.valueOf(article.score)); - map.put("field", "4"); + map.put("field", String.valueOf(Article.UPDATE_FIELD_SCORE)); req.execute(map); } + // TODO switch to setArticleField() public void saveArticleMarked(final Article article) { ApiRequest req = new ApiRequest(getApplicationContext()) { protected void onPostExecute(JsonElement result) { @@ -914,11 +917,12 @@ public class OnlineActivity extends CommonActivity { map.put("op", "updateArticle"); map.put("article_ids", String.valueOf(article.id)); map.put("mode", article.marked ? "1" : "0"); - map.put("field", "0"); + map.put("field", String.valueOf(Article.UPDATE_FIELD_MARKED)); req.execute(map); } + // TODO switch to setArticleField() public void saveArticlePublished(final Article article) { ApiRequest req = new ApiRequest(getApplicationContext()) { @@ -933,11 +937,12 @@ public class OnlineActivity extends CommonActivity { map.put("op", "updateArticle"); map.put("article_ids", String.valueOf(article.id)); map.put("mode", article.published ? "1" : "0"); - map.put("field", "1"); + map.put("field", String.valueOf(Article.UPDATE_FIELD_PUBLISHED)); req.execute(map); } + // TODO switch to setArticleField() public void saveArticleNote(final Article article, final String note) { ApiRequest req = new ApiRequest(getApplicationContext()) { protected void onPostExecute(JsonElement result) { @@ -951,7 +956,7 @@ public class OnlineActivity extends CommonActivity { map.put("article_ids", String.valueOf(article.id)); map.put("mode", "1"); map.put("data", note); - map.put("field", "3"); + map.put("field", String.valueOf(Article.UPDATE_FIELD_NOTE)); req.execute(map); } diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/types/Article.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/types/Article.java index 624e54e4..c0f63937 100755 --- a/org.fox.ttrss/src/main/java/org/fox/ttrss/types/Article.java +++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/types/Article.java @@ -69,7 +69,7 @@ public class Article implements Parcelable { transient public String youtubeVid; transient public List mediaList = new ArrayList<>(); - public Article(Parcel in) { + public Article(Parcel in) { readFromParcel(in); } @@ -195,6 +195,43 @@ public class Article implements Parcelable { fixNullFields(); } + public Article(Article clone) { + id = clone.id; + unread = clone.unread; + marked = clone.marked; + published = clone.published; + score = clone.score; + updated = clone.updated; + is_updated = clone.is_updated; + title = clone.title; + link = clone.link; + feed_id = clone.feed_id; + tags = clone.tags; + attachments = clone.attachments; + content = clone.content; + excerpt = clone.excerpt; + labels = clone.labels; + feed_title = clone.feed_title; + comments_count = clone.comments_count; + comments_link = clone.comments_link; + always_display_attachments = clone.always_display_attachments; + author = clone.author; + note = clone.note; + selected = clone.selected; + flavor_image = clone.flavor_image; + flavor_stream = clone.flavor_stream; + flavor_kind = clone.flavor_kind; + site_url = clone.site_url; + + articleDoc = clone.articleDoc; + flavorImage = clone.flavorImage; + + flavorImageUri = clone.flavorImageUri; + flavorStreamUri = clone.flavorStreamUri; + youtubeVid = clone.youtubeVid; + mediaList = new ArrayList<>(clone.mediaList); + } + @Override public int describeContents() { return 0; 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 index 01af780c..74df2c33 100755 --- 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 @@ -23,6 +23,10 @@ public class ArticleList extends CopyOnWriteArrayList
{ public ArticleList() { } + public ArticleList(ArticleList clone) { + this.addAll(clone); + } + public ArticleList getWithoutFooters() { return this.stream().filter(a -> { return a.id > 0; }).collect(Collectors.toCollection(ArticleList::new)); } diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/util/ArticleDiffItemCallback.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/util/ArticleDiffItemCallback.java new file mode 100644 index 00000000..b037eea0 --- /dev/null +++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/util/ArticleDiffItemCallback.java @@ -0,0 +1,21 @@ +package org.fox.ttrss.util; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.DiffUtil; + +import org.fox.ttrss.types.Article; + +public class ArticleDiffItemCallback extends DiffUtil.ItemCallback
{ + private final String TAG = this.getClass().getSimpleName(); + @Override + public boolean areItemsTheSame(@NonNull Article a1, @NonNull Article a2) { + return a1.id == a2.id; + } + + @Override + public boolean areContentsTheSame(@NonNull Article a1, @NonNull Article a2) { + return a1.id == a2.id && a1.unread == a2.unread && a1.marked == a2.marked + && a1.selected == a2.selected && a1.published == a2.published + && a1.note.equals(a2.note); + } +} diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/util/HeadlinesDiffItemCallback.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/util/HeadlinesDiffItemCallback.java deleted file mode 100644 index c236610c..00000000 --- a/org.fox.ttrss/src/main/java/org/fox/ttrss/util/HeadlinesDiffItemCallback.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.fox.ttrss.util; - -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.DiffUtil; - -import org.fox.ttrss.types.Article; - -public class HeadlinesDiffItemCallback extends DiffUtil.ItemCallback
{ - @Override - public boolean areItemsTheSame(@NonNull Article a1, @NonNull Article a2) { - // Log.d(TAG, "[DIFF] areItemsTheSame a1=" + a1.title + " a2=" + a2.title); - - return a1.id == a2.id; - } - - @Override - public boolean areContentsTheSame(@NonNull Article a1, @NonNull Article a2) { - return a1.id == a2.id && a1.unread == a2.unread && a1.marked == a2.marked - && a1.selected == a2.selected && a1.published == a2.published - && a1.note.equals(a2.note); - } -} -- cgit v1.2.3-54-g00ecf