summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew Dolgov <fox@fakecake.org>2025-05-18 14:28:33 +0300
committerAndrew Dolgov <fox@fakecake.org>2025-05-18 14:28:33 +0300
commitf6264c066c8ec3646f38593e948b09abf344d06f (patch)
tree3162f91f00029ae9fa12262e6bd87132f8707d3e
parent78e5c82809a122070cbc4326b56483df6de963fa (diff)
fix score not handled in areContentsTheSame, add selective updates to headline recycler view via getChangePayload
-rwxr-xr-xorg.fox.ttrss/src/main/java/org/fox/ttrss/HeadlinesFragment.java315
-rw-r--r--org.fox.ttrss/src/main/java/org/fox/ttrss/util/ArticleDiffItemCallback.java34
2 files changed, 210 insertions, 139 deletions
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 63ecbdb8..2a9b71b2 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
@@ -8,7 +8,6 @@ import android.content.SharedPreferences;
import android.content.res.ColorStateList;
import android.graphics.Paint;
import android.graphics.Point;
-import android.graphics.SurfaceTexture;
import android.graphics.drawable.Drawable;
import android.media.MediaPlayer;
import android.net.ConnectivityManager;
@@ -30,7 +29,6 @@ import android.view.Display;
import android.view.LayoutInflater;
import android.view.MenuInflater;
import android.view.MenuItem;
-import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import android.view.ViewGroup;
@@ -39,7 +37,6 @@ import android.widget.AdapterView;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.CheckBox;
import android.widget.EditText;
-import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.PopupMenu;
@@ -87,7 +84,7 @@ import org.jsoup.nodes.Element;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
-import java.util.HashMap;
+import java.util.List;
import java.util.TimeZone;
import java.util.stream.Collectors;
@@ -705,15 +702,17 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment {
private final ColorGenerator m_colorGenerator = ColorGenerator.DEFAULT;
private final TextDrawable.IBuilder m_drawableBuilder = TextDrawable.builder().round();
+ private final ColorStateList m_colorTertiary;
+ private final ColorStateList m_colorPrimary;
- boolean flavorImageEnabled;
+ boolean m_flavorImageEnabled;
private final int m_screenWidth;
private final int m_screenHeight;
private final ConnectivityManager m_cmgr;
private boolean canShowFlavorImage() {
- if (flavorImageEnabled) {
+ if (m_flavorImageEnabled) {
if (m_prefs.getBoolean("headline_images_wifi_only", false)) {
// why do i have to get this service every time instead of using a member variable :(
NetworkInfo wifi = m_cmgr.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
@@ -739,7 +738,17 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment {
m_screenWidth = size.x;
String headlineMode = m_prefs.getString("headline_mode", "HL_DEFAULT");
- flavorImageEnabled = "HL_DEFAULT".equals(headlineMode) || "HL_COMPACT".equals(headlineMode);
+ m_flavorImageEnabled = "HL_DEFAULT".equals(headlineMode) || "HL_COMPACT".equals(headlineMode);
+
+ TypedValue tvTertiary = new TypedValue();
+ m_activity.getTheme().resolveAttribute(R.attr.colorTertiary, tvTertiary, true);
+
+ m_colorTertiary = ColorStateList.valueOf(ContextCompat.getColor(m_activity, tvTertiary.resourceId));
+
+ TypedValue tvPrimary = new TypedValue();
+ m_activity.getTheme().resolveAttribute(R.attr.colorPrimary, tvPrimary, true);
+
+ m_colorPrimary = ColorStateList.valueOf(ContextCompat.getColor(m_activity, tvPrimary.resourceId));
m_cmgr = (ConnectivityManager) m_activity.getSystemService(Context.CONNECTIVITY_SERVICE);
}
@@ -777,6 +786,39 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment {
}
@Override
+ // https://stackoverflow.com/questions/33176336/need-an-example-about-recyclerview-adapter-notifyitemchangedint-position-objec/50085835#50085835
+ public void onBindViewHolder(final ArticleViewHolder holder, final int position, final List<Object> payloads) {
+ if (!payloads.isEmpty()) {
+ Log.d(TAG, "onBindViewHolder, payloads: " + payloads);
+
+ final Article article = getItem(position);
+
+ for (final Object pobject : payloads) {
+ ArticleDiffItemCallback.ChangePayload payload = (ArticleDiffItemCallback.ChangePayload) pobject;
+
+ switch (payload) {
+ case UNREAD:
+ break;
+ case MARKED:
+ updateMarkedView(article, holder, position);
+ break;
+ case SELECTED:
+ updateSelectedView(article, holder, position);
+ updateTextImage(article, holder, position);
+ break;
+ case PUBLISHED:
+ updatePublishedView(article, holder, position);
+ break;
+ case SCORE:
+ updateScoreView(article, holder);
+ break;
+ }
+ }
+ } else {
+ super.onBindViewHolder(holder, position, payloads);
+ }
+ }
+ @Override
public void onBindViewHolder(final ArticleViewHolder holder, int position) {
int headlineFontSize = m_prefs.getInt("headlines_font_size_sp_int", 13);
int headlineSmallFontSize = Math.max(10, Math.min(18, headlineFontSize - 2));
@@ -819,33 +861,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment {
});
}
- if (holder.textImage != null) {
- updateTextCheckedState(holder, position);
-
- holder.textImage.setOnClickListener(view -> {
- Article selectedArticle = getItem(position);
-
- Log.d(TAG, "textImage onClick pos=" + position + " article=" + article);
-
- selectedArticle.selected = !selectedArticle.selected;
-
- updateTextCheckedState(holder, position);
-
- m_listener.onArticleListSelectionChange();
- });
- ViewCompat.setTransitionName(holder.textImage, "gallery:" + article.flavorImageUri);
-
- if (article.flavorImage != null) {
-
- holder.textImage.setOnLongClickListener(v -> {
-
- openGalleryForType(article, holder, holder.textImage);
-
- return true;
- });
-
- }
- }
+ updateTextImage(article, holder, position);
if (holder.titleView != null) {
holder.titleView.setText(Html.fromHtml(article.title));
@@ -873,89 +889,9 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment {
}
}
- TypedValue tvTertiary = new TypedValue();
- m_activity.getTheme().resolveAttribute(R.attr.colorTertiary, tvTertiary, true);
-
- ColorStateList colorTertiary = ColorStateList.valueOf(ContextCompat.getColor(m_activity, tvTertiary.resourceId));
-
- TypedValue tvPrimary = new TypedValue();
- m_activity.getTheme().resolveAttribute(R.attr.colorPrimary, tvPrimary, true);
-
- ColorStateList colorPrimary = ColorStateList.valueOf(ContextCompat.getColor(m_activity, tvPrimary.resourceId));
-
- if (holder.markedView != null) {
- holder.markedView.setIconResource(article.marked ? R.drawable.baseline_star_24 : R.drawable.baseline_star_outline_24);
- holder.markedView.setIconTint(article.marked ? colorTertiary : colorPrimary);
-
- holder.markedView.setOnClickListener(v -> {
- Article selectedArticle = new Article(getItem(position));
- selectedArticle.marked = !selectedArticle.marked;
-
- m_activity.saveArticleMarked(selectedArticle);
- Application.getArticlesModel().update(position, selectedArticle);
- });
- }
-
- if (holder.scoreView != null) {
- int scoreDrawable = R.drawable.baseline_trending_flat_24;
-
- if (article.score > 0)
- scoreDrawable = R.drawable.baseline_trending_up_24;
- else if (article.score < 0)
- scoreDrawable = R.drawable.baseline_trending_down_24;
-
- holder.scoreView.setIconResource(scoreDrawable);
-
- if (article.score > Article.SCORE_HIGH)
- holder.scoreView.setIconTint(colorTertiary);
- else
- holder.scoreView.setIconTint(colorPrimary);
-
- if (m_activity.getApiLevel() >= 16) {
- holder.scoreView.setOnClickListener(v -> {
- final EditText edit = new EditText(getActivity());
- edit.setText(String.valueOf(article.score));
-
- MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(getContext())
- .setTitle(R.string.score_for_this_article)
- .setPositiveButton(R.string.set_score,
- (dialog, which) -> {
- try {
- article.score = Integer.parseInt(edit.getText().toString());
- m_activity.saveArticleScore(article);
- m_adapter.notifyItemChanged(m_list.getChildAdapterPosition(holder.view));
- } catch (NumberFormatException e) {
- m_activity.toast(R.string.score_invalid);
- e.printStackTrace();
- }
- })
- .setNegativeButton(getString(R.string.cancel),
- (dialog, which) -> { }).setView(edit);
-
- Dialog dialog = builder.create();
- dialog.show();
- });
- }
- }
-
- if (holder.publishedView != null) {
-
- // otherwise we just use tinting in actionbar
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
- holder.publishedView.setIconResource(article.published ? R.drawable.rss_box : R.drawable.rss);
- }
-
- holder.publishedView.setIconTint(article.published ? colorTertiary : colorPrimary);
-
- holder.publishedView.setOnClickListener(v -> {
- Article selectedArticle = new Article(getItem(position));
- selectedArticle.published = !selectedArticle.published;
-
- m_activity.saveArticlePublished(selectedArticle);
-
- Application.getArticlesModel().update(position, selectedArticle);
- });
- }
+ updateMarkedView(article, holder, position);
+ updateScoreView(article, holder);
+ updatePublishedView(article, holder, position);
if (holder.attachmentsView != null) {
if (article.attachments != null && !article.attachments.isEmpty()) {
@@ -1217,21 +1153,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment {
holder.dateView.setText(df.format(d));
}
-
- if (holder.selectionBoxView != null) {
- holder.selectionBoxView.setChecked(article.selected);
- holder.selectionBoxView.setOnClickListener(view -> {
- Article currentArticle = getItem(position);
-
- Log.d(TAG, "selectionCb onClick pos=" + position + " article=" + article);
-
- CheckBox cb = (CheckBox)view;
-
- currentArticle.selected = cb.isChecked();
-
- m_listener.onArticleListSelectionChange();
- });
- }
+ updateSelectedView(article, holder, position);
if (holder.menuButtonView != null) {
holder.menuButtonView.setOnClickListener(v -> {
@@ -1252,7 +1174,134 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment {
}
}
- private void loadFlavorImage(Article article, ArticleViewHolder holder, int maxImageHeight) {
+ private void updateMarkedView(Article article, ArticleViewHolder holder, int position) {
+ if (holder.markedView != null) {
+ holder.markedView.setIconResource(article.marked ? R.drawable.baseline_star_24 : R.drawable.baseline_star_outline_24);
+ holder.markedView.setIconTint(article.marked ? m_colorTertiary : m_colorPrimary);
+
+ holder.markedView.setOnClickListener(v -> {
+ Article selectedArticle = new Article(getItem(position));
+ selectedArticle.marked = !selectedArticle.marked;
+
+ m_activity.saveArticleMarked(selectedArticle);
+ Application.getArticlesModel().update(position, selectedArticle);
+ });
+ }
+ }
+
+ private void updateTextImage(Article article, ArticleViewHolder holder, int position) {
+ if (holder.textImage != null) {
+ updateTextCheckedState(holder, position);
+
+ holder.textImage.setOnClickListener(view -> {
+ Article selectedArticle = getItem(position);
+
+ Log.d(TAG, "textImage onClick pos=" + position + " article=" + article);
+
+ selectedArticle.selected = !selectedArticle.selected;
+
+ updateTextCheckedState(holder, position);
+
+ m_listener.onArticleListSelectionChange();
+ });
+
+ ViewCompat.setTransitionName(holder.textImage, "gallery:" + article.flavorImageUri);
+
+ if (article.flavorImage != null) {
+
+ holder.textImage.setOnLongClickListener(v -> {
+
+ openGalleryForType(article, holder, holder.textImage);
+
+ return true;
+ });
+
+ }
+ }
+ }
+
+ private void updateSelectedView(Article article, ArticleViewHolder holder, int position) {
+ if (holder.selectionBoxView != null) {
+ holder.selectionBoxView.setChecked(article.selected);
+ holder.selectionBoxView.setOnClickListener(view -> {
+ Article currentArticle = getItem(position);
+
+ Log.d(TAG, "selectionCb onClick pos=" + position + " article=" + article);
+
+ CheckBox cb = (CheckBox)view;
+
+ currentArticle.selected = cb.isChecked();
+
+ m_listener.onArticleListSelectionChange();
+ });
+ }
+ }
+
+ private void updateScoreView(Article article, ArticleViewHolder holder) {
+ if (holder.scoreView != null) {
+ int scoreDrawable = R.drawable.baseline_trending_flat_24;
+
+ if (article.score > 0)
+ scoreDrawable = R.drawable.baseline_trending_up_24;
+ else if (article.score < 0)
+ scoreDrawable = R.drawable.baseline_trending_down_24;
+
+ holder.scoreView.setIconResource(scoreDrawable);
+
+ if (article.score > Article.SCORE_HIGH)
+ holder.scoreView.setIconTint(m_colorTertiary);
+ else
+ holder.scoreView.setIconTint(m_colorPrimary);
+
+ if (m_activity.getApiLevel() >= 16) {
+ holder.scoreView.setOnClickListener(v -> {
+ final EditText edit = new EditText(getActivity());
+ edit.setText(String.valueOf(article.score));
+
+ MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(getContext())
+ .setTitle(R.string.score_for_this_article)
+ .setPositiveButton(R.string.set_score,
+ (dialog, which) -> {
+ try {
+ article.score = Integer.parseInt(edit.getText().toString());
+ m_activity.saveArticleScore(article);
+ m_adapter.notifyItemChanged(m_list.getChildAdapterPosition(holder.view));
+ } catch (NumberFormatException e) {
+ m_activity.toast(R.string.score_invalid);
+ e.printStackTrace();
+ }
+ })
+ .setNegativeButton(getString(R.string.cancel),
+ (dialog, which) -> { }).setView(edit);
+
+ Dialog dialog = builder.create();
+ dialog.show();
+ });
+ }
+ }
+ }
+
+ private void updatePublishedView(final Article article, ArticleViewHolder holder, int position) {
+ if (holder.publishedView != null) {
+ // otherwise we just use tinting in actionbar
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
+ holder.publishedView.setIconResource(article.published ? R.drawable.rss_box : R.drawable.rss);
+ }
+
+ holder.publishedView.setIconTint(article.published ? m_colorTertiary : m_colorPrimary);
+
+ holder.publishedView.setOnClickListener(v -> {
+ Article selectedArticle = new Article(getItem(position));
+ selectedArticle.published = !selectedArticle.published;
+
+ m_activity.saveArticlePublished(selectedArticle);
+
+ Application.getArticlesModel().update(position, selectedArticle);
+ });
+ }
+ }
+
+ private void loadFlavorImage(final Article article, ArticleViewHolder holder, int maxImageHeight) {
Glide.with(HeadlinesFragment.this)
.load(article.flavorImageUri)
.transition(DrawableTransitionOptions.withCrossFade())
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
index b037eea0..576048b3 100644
--- 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
@@ -7,15 +7,37 @@ import org.fox.ttrss.types.Article;
public class ArticleDiffItemCallback extends DiffUtil.ItemCallback<Article> {
private final String TAG = this.getClass().getSimpleName();
+
+ public enum ChangePayload { UNREAD, MARKED, SELECTED, PUBLISHED, NOTE, SCORE };
+
@Override
- public boolean areItemsTheSame(@NonNull Article a1, @NonNull Article a2) {
- return a1.id == a2.id;
+ public boolean areItemsTheSame(@NonNull Article oldItem, @NonNull Article newItem) {
+ return oldItem.id == newItem.id;
+ }
+
+ @Override
+ public Object getChangePayload(@NonNull Article oldItem, @NonNull Article newItem) {
+
+ if (oldItem.unread != newItem.unread)
+ return ChangePayload.UNREAD;
+ else if (oldItem.marked != newItem.marked)
+ return ChangePayload.MARKED;
+ else if (oldItem.selected != newItem.selected)
+ return ChangePayload.SELECTED;
+ else if (oldItem.published != newItem.published)
+ return ChangePayload.PUBLISHED;
+ else if (!oldItem.note.equals(newItem.note))
+ return ChangePayload.NOTE;
+ else if (oldItem.score != newItem.score)
+ return ChangePayload.SCORE;
+
+ return null;
}
@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);
+ public boolean areContentsTheSame(@NonNull Article oldItem, @NonNull Article newItem) {
+ return oldItem.id == newItem.id && oldItem.unread == newItem.unread && oldItem.marked == newItem.marked
+ && oldItem.selected == newItem.selected && oldItem.published == newItem.published
+ && oldItem.score == newItem.score && oldItem.note.equals(newItem.note);
}
}