From 5314dbf812938099210cd153bcd160643d5378a6 Mon Sep 17 00:00:00 2001 From: Andrew Dolgov Date: Sat, 17 May 2025 14:06:30 +0300 Subject: wip switch gallery stuff to a viewmodel --- .../main/java/org/fox/ttrss/GalleryActivity.java | 111 +++++++++------- .../src/main/java/org/fox/ttrss/GalleryModel.java | 139 +++++++++++++++++++++ .../src/main/res/layout/activity_gallery.xml | 2 +- 3 files changed, 203 insertions(+), 49 deletions(-) create mode 100644 org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryModel.java diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryActivity.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryActivity.java index c527730b..538e1c8f 100755 --- a/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryActivity.java +++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryActivity.java @@ -12,22 +12,28 @@ import android.view.Window; import android.widget.PopupMenu; import android.widget.ProgressBar; +import androidx.annotation.NonNull; import androidx.appcompat.widget.Toolbar; import androidx.core.app.ActivityCompat; import androidx.core.view.WindowCompat; import androidx.core.view.WindowInsetsCompat; import androidx.core.view.WindowInsetsControllerCompat; import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentStatePagerAdapter; +import androidx.lifecycle.ViewModelProvider; import androidx.preference.PreferenceManager; +import androidx.recyclerview.widget.DiffUtil; import androidx.viewpager.widget.ViewPager; +import androidx.viewpager2.widget.ViewPager2; import com.ToxicBakery.viewpager.transforms.DepthPageTransformer; import com.bumptech.glide.Glide; import com.bumptech.glide.load.engine.DiskCacheStrategy; import org.fox.ttrss.types.GalleryEntry; +import org.fox.ttrss.util.DiffFragmentStateAdapter; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; @@ -42,32 +48,36 @@ import me.relex.circleindicator.CircleIndicator; public class GalleryActivity extends CommonActivity { private final String TAG = this.getClass().getSimpleName(); - protected ArrayList m_items = new ArrayList<>(); protected String m_title; private ArticleImagesPagerAdapter m_adapter; public String m_content; - private ViewPager m_pager; // TODO replace with viewpager2 + private ViewPager2 m_pager; // TODO replace with viewpager2 private ProgressBar m_checkProgress; - private static class ArticleImagesPagerAdapter extends FragmentStatePagerAdapter { - private final List m_items; + private static class GalleryEntryDiffItemCallback extends DiffUtil.ItemCallback { - public ArticleImagesPagerAdapter(FragmentManager fm, List items) { - super(fm); - m_items = items; + @Override + public boolean areItemsTheSame(@NonNull GalleryEntry oldItem, @NonNull GalleryEntry newItem) { + return oldItem.url.equals(newItem.url); } @Override - public int getCount() { - return m_items.size(); + public boolean areContentsTheSame(@NonNull GalleryEntry oldItem, @NonNull GalleryEntry newItem) { + return oldItem.url.equals(newItem.url) && oldItem.type.equals(newItem.type); + } + } + + private static class ArticleImagesPagerAdapter extends DiffFragmentStateAdapter { + protected ArticleImagesPagerAdapter(FragmentActivity fragmentActivity, DiffUtil.ItemCallback diffCallback) { + super(fragmentActivity, diffCallback); } @Override - public Fragment getItem(int position) { + public Fragment createFragment(int position) { //Log.d(TAG, "getItem: " + position + " " + m_urls.get(position)); - GalleryEntry item = m_items.get(position); + GalleryEntry item = getItem(position); switch (item.type) { case TYPE_IMAGE: { @@ -150,6 +160,7 @@ public class GalleryActivity extends CommonActivity { } } + /* boolean collectGalleryContents(String imgSrcFirst, Document doc, List uncheckedItems ) { Elements elems = doc.select("img,video"); @@ -229,19 +240,18 @@ public class GalleryActivity extends CommonActivity { } return firstFound; - } + } */ public void onSaveInstanceState(Bundle out) { super.onSaveInstanceState(out); - out.putParcelableArrayList("m_items", m_items); out.putString("m_title", m_title); out.putString("m_content", m_content); } @Override public void onCreate(Bundle savedInstanceState) { - ActivityCompat.postponeEnterTransition(this); + // ActivityCompat.postponeEnterTransition(this); // we use that before parent onCreate so let's init locally m_prefs = PreferenceManager @@ -263,62 +273,68 @@ public class GalleryActivity extends CommonActivity { getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().hide(); - ArrayList uncheckedItems = new ArrayList<>(); - if (savedInstanceState == null) { m_title = getIntent().getStringExtra("title"); m_content = getIntent().getStringExtra("content"); - String imgSrcFirst = getIntent().getStringExtra("firstSrc"); + // this should be returned first so that transition completes properly + String firstSrc = getIntent().getStringExtra("firstSrc"); - Document doc = Jsoup.parse(m_content); + // Document doc = Jsoup.parse(m_content); // if we were unable to find first image, try again for all media content so that // gallery doesn't lock up because of a pending shared transition - if (!collectGalleryContents(imgSrcFirst, doc, uncheckedItems)) + /* if (!collectGalleryContents(imgSrcFirst, doc, uncheckedItems)) if (!collectGalleryContents("", doc, uncheckedItems)) - m_items.add(new GalleryEntry(imgSrcFirst, GalleryEntry.GalleryEntryType.TYPE_IMAGE, null)); - } else { - ArrayList list = savedInstanceState.getParcelableArrayList("m_items"); + m_items.add(new GalleryEntry(imgSrcFirst, GalleryEntry.GalleryEntryType.TYPE_IMAGE, null)); */ + + GalleryModel model = new ViewModelProvider(this).get(GalleryModel.class); + model.collectItems(m_content, firstSrc); + + model.getItems().observe(this, galleryEntries -> { + Log.d(TAG, "observed gallery entries=" + galleryEntries + " firstSrc=" + firstSrc); - m_items.clear(); - m_items.addAll(list); + m_adapter.submitList(galleryEntries, () -> { + Log.d(TAG, "selecting first src=" + firstSrc); + }); + }); + } else { + // ArrayList list = savedInstanceState.getParcelableArrayList("m_items"); m_title = savedInstanceState.getString("m_title"); m_content = savedInstanceState.getString("m_content"); } findViewById(R.id.gallery_overflow).setOnClickListener(v -> { - PopupMenu popup = new PopupMenu(GalleryActivity.this, v); - MenuInflater inflater = popup.getMenuInflater(); - inflater.inflate(R.menu.content_gallery_entry, popup.getMenu()); + try { + GalleryEntry entry = m_adapter.getCurrentList().get(m_pager.getCurrentItem()); - final GalleryEntry entry = m_items.get(m_pager.getCurrentItem()); + PopupMenu popup = new PopupMenu(GalleryActivity.this, v); + MenuInflater inflater = popup.getMenuInflater(); + inflater.inflate(R.menu.content_gallery_entry, popup.getMenu()); - popup.getMenu().findItem(R.id.article_img_share) - .setVisible(entry.type == GalleryEntry.GalleryEntryType.TYPE_IMAGE); + popup.getMenu().findItem(R.id.article_img_share) + .setVisible(entry.type == GalleryEntry.GalleryEntryType.TYPE_IMAGE); - popup.setOnMenuItemClickListener(item -> onImageMenuItemSelected(item, entry)); + popup.setOnMenuItemClickListener(item -> onImageMenuItemSelected(item, entry)); - popup.show(); + popup.show(); + } catch (IndexOutOfBoundsException e) { + e.printStackTrace(); + } }); setTitle(m_title); - m_adapter = new ArticleImagesPagerAdapter(getSupportFragmentManager(), m_items); + m_adapter = new ArticleImagesPagerAdapter(this, new GalleryEntryDiffItemCallback()); m_pager = findViewById(R.id.gallery_pager); m_pager.setAdapter(m_adapter); - m_pager.setPageTransformer(true, new DepthPageTransformer()); - - CircleIndicator indicator = findViewById(R.id.gallery_pager_indicator); - indicator.setViewPager(m_pager); - m_adapter.registerDataSetObserver(indicator.getDataSetObserver()); m_checkProgress = findViewById(R.id.gallery_check_progress); - Log.d(TAG, "items to check:" + uncheckedItems.size()); + /* Log.d(TAG, "items to check:" + uncheckedItems.size()); MediaCheckTask mct = new MediaCheckTask() { @Override @@ -339,26 +355,25 @@ public class GalleryActivity extends CommonActivity { @Override protected void onPostExecute(List result) { m_items.addAll(result); - m_adapter.notifyDataSetChanged(); } }; - mct.execute(uncheckedItems); - + mct.execute(uncheckedItems); */ } @Override public boolean onContextItemSelected(MenuItem item) { int position = m_pager.getCurrentItem(); - GalleryEntry entry = m_items.get(position); + try { + GalleryEntry entry = m_adapter.getCurrentList().get(position); - //String url = m_items.get(position).url; + if (onImageMenuItemSelected(item, entry)) + return true; - - - if (onImageMenuItemSelected(item, entry)) - return true; + } catch (IndexOutOfBoundsException e) { + e.printStackTrace(); + } return super.onContextItemSelected(item); } diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryModel.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryModel.java new file mode 100644 index 00000000..25117bb2 --- /dev/null +++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryModel.java @@ -0,0 +1,139 @@ +package org.fox.ttrss; + +import android.app.Application; +import android.net.Uri; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.lifecycle.AndroidViewModel; +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; + +import org.fox.ttrss.types.GalleryEntry; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; + +import java.util.ArrayList; +import java.util.List; + +public class GalleryModel extends AndroidViewModel { + private final String TAG = this.getClass().getSimpleName(); + + private MutableLiveData> m_items = new MutableLiveData<>(new ArrayList<>()); + + public GalleryModel(@NonNull Application application) { + super(application); + } + + public LiveData> getItems() { + return m_items; + } + + public void collectItems(String articleText, String srcFirst) { + Document doc = Jsoup.parse(articleText); + + /* look for srcFirst and post an update */ + + Elements elems = doc.select("img,video"); + + for (Element elem : elems) { + GalleryEntry item = new GalleryEntry(); + + if ("video".equalsIgnoreCase(elem.tagName())) { + + + } else { + String src = elem.attr("abs:src"); + + + } + } + } + + List collectGalleryContents(String imgSrcFirst, String articleText, List uncheckedItems ) { + List items = new ArrayList<>(); + + Document doc = Jsoup.parse(articleText); + + Elements elems = doc.select("img,video"); + + boolean firstFound = false; + + for (Element elem : elems) { + + GalleryEntry item = new GalleryEntry(); + + if ("video".equalsIgnoreCase(elem.tagName())) { + String cover = elem.attr("poster"); + + Element source = elem.select("source").first(); + + if (source != null) { + String src = source.attr("src"); + + if (!src.isEmpty()) { + //Log.d(TAG, "vid/src=" + src); + + if (src.startsWith("//")) { + src = "https:" + src; + } + + if (imgSrcFirst.equals(src)) + firstFound = true; + + try { + Uri checkUri = Uri.parse(src); + + if (!"data".equalsIgnoreCase(checkUri.getScheme())) { + item.url = src; + item.coverUrl = cover; + item.type = GalleryEntry.GalleryEntryType.TYPE_VIDEO; + } + + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + } else { + String src = elem.attr("src"); + + if (!src.isEmpty()) { + if (src.startsWith("//")) { + src = "https:" + src; + } + + if (imgSrcFirst.equals(src)) + firstFound = true; + + Log.d(TAG, "img/fir=" + imgSrcFirst + ";"); + Log.d(TAG, "img/src=" + src + "; ff=" + firstFound); + + try { + Uri checkUri = Uri.parse(src); + + if (!"data".equalsIgnoreCase(checkUri.getScheme())) { + item.url = src; + item.type = GalleryEntry.GalleryEntryType.TYPE_IMAGE; + } + + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + if ((firstFound || imgSrcFirst.isEmpty()) && item.url != null) { + if (m_items.isEmpty()) + m_items.add(item); + else + uncheckedItems.add(item); + } + } + + return firstFound; + } +} diff --git a/org.fox.ttrss/src/main/res/layout/activity_gallery.xml b/org.fox.ttrss/src/main/res/layout/activity_gallery.xml index b0f67761..b38f0408 100644 --- a/org.fox.ttrss/src/main/res/layout/activity_gallery.xml +++ b/org.fox.ttrss/src/main/res/layout/activity_gallery.xml @@ -5,7 +5,7 @@ android:animateLayoutChanges="true" android:layout_height="fill_parent"> - Date: Sat, 17 May 2025 14:40:15 +0300 Subject: more gallery model stuff --- .../src/main/java/org/fox/ttrss/GalleryModel.java | 121 +++++++++++---------- 1 file changed, 61 insertions(+), 60 deletions(-) diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryModel.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryModel.java index 25117bb2..1e059e0f 100644 --- a/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryModel.java +++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryModel.java @@ -1,7 +1,10 @@ package org.fox.ttrss; import android.app.Application; +import android.graphics.Bitmap; import android.net.Uri; +import android.os.Handler; +import android.os.Looper; import android.util.Log; import androidx.annotation.NonNull; @@ -9,14 +12,21 @@ import androidx.lifecycle.AndroidViewModel; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.engine.DiskCacheStrategy; + import org.fox.ttrss.types.GalleryEntry; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; +import java.net.MalformedURLException; +import java.net.URISyntaxException; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; public class GalleryModel extends AndroidViewModel { private final String TAG = this.getClass().getSimpleName(); @@ -31,67 +41,47 @@ public class GalleryModel extends AndroidViewModel { return m_items; } + private ExecutorService m_executor = Executors.newSingleThreadExecutor(); + private Handler m_mainHandler = new Handler(Looper.getMainLooper()); + public void collectItems(String articleText, String srcFirst) { Document doc = Jsoup.parse(articleText); - /* look for srcFirst and post an update */ - - Elements elems = doc.select("img,video"); - - for (Element elem : elems) { - GalleryEntry item = new GalleryEntry(); - - if ("video".equalsIgnoreCase(elem.tagName())) { - - - } else { - String src = elem.attr("abs:src"); - - - } - } - } + List checkList = new ArrayList<>(); - List collectGalleryContents(String imgSrcFirst, String articleText, List uncheckedItems ) { - List items = new ArrayList<>(); + /* look for srcFirst quickly and post an update */ - Document doc = Jsoup.parse(articleText); + Log.d(TAG, "looking for srcFirst=" + srcFirst); Elements elems = doc.select("img,video"); - boolean firstFound = false; - for (Element elem : elems) { - - GalleryEntry item = new GalleryEntry(); - if ("video".equalsIgnoreCase(elem.tagName())) { - String cover = elem.attr("poster"); - Element source = elem.select("source").first(); + String poster = elem.attr("abs:poster"); if (source != null) { - String src = source.attr("src"); + String src = source.attr("abs:src"); - if (!src.isEmpty()) { - //Log.d(TAG, "vid/src=" + src); + Log.d(TAG, "checking vid src=" + src + " poster=" + poster); - if (src.startsWith("//")) { - src = "https:" + src; - } + if (poster != null && poster.equals(srcFirst) || src != null && src.equals(srcFirst)) { + Log.d(TAG, "first item found, vid=" + src); + + GalleryEntry item = new GalleryEntry(src, GalleryEntry.GalleryEntryType.TYPE_VIDEO, poster); - if (imgSrcFirst.equals(src)) - firstFound = true; + checkList.add(item); + m_items.postValue(checkList); + } else { try { Uri checkUri = Uri.parse(src); if (!"data".equalsIgnoreCase(checkUri.getScheme())) { - item.url = src; - item.coverUrl = cover; - item.type = GalleryEntry.GalleryEntryType.TYPE_VIDEO; - } + checkList.add(new GalleryEntry(src, GalleryEntry.GalleryEntryType.TYPE_VIDEO, poster)); + m_items.postValue(checkList); + } } catch (Exception e) { e.printStackTrace(); } @@ -99,41 +89,52 @@ public class GalleryModel extends AndroidViewModel { } } else { - String src = elem.attr("src"); + String src = elem.attr("abs:src"); - if (!src.isEmpty()) { - if (src.startsWith("//")) { - src = "https:" + src; - } + Log.d(TAG, "checking img src=" + src); - if (imgSrcFirst.equals(src)) - firstFound = true; + if (src != null && src.equals(srcFirst)) { + Log.d(TAG, "first item found, img=" + src); - Log.d(TAG, "img/fir=" + imgSrcFirst + ";"); - Log.d(TAG, "img/src=" + src + "; ff=" + firstFound); + GalleryEntry item = new GalleryEntry(src, GalleryEntry.GalleryEntryType.TYPE_IMAGE, null); + checkList.add(item); + + m_items.postValue(checkList); + } else { try { Uri checkUri = Uri.parse(src); if (!"data".equalsIgnoreCase(checkUri.getScheme())) { - item.url = src; - item.type = GalleryEntry.GalleryEntryType.TYPE_IMAGE; - } + m_executor.execute(() -> { + Log.d(TAG, "checking image with glide: " + src); + + try { + Bitmap bmp = Glide.with(getApplication().getApplicationContext()) + .load(src) + .asBitmap() + .skipMemoryCache(false) + .diskCacheStrategy(DiskCacheStrategy.ALL) + .into(HeadlinesFragment.FLAVOR_IMG_MIN_SIZE, HeadlinesFragment.FLAVOR_IMG_MIN_SIZE) + .get(); + + if (bmp != null && bmp.getWidth() >= HeadlinesFragment.FLAVOR_IMG_MIN_SIZE && bmp.getHeight() >= HeadlinesFragment.FLAVOR_IMG_MIN_SIZE) { + Log.d(TAG, "image matches gallery criteria, adding..."); + + checkList.add(new GalleryEntry(src, GalleryEntry.GalleryEntryType.TYPE_IMAGE, null)); + m_items.postValue(checkList); + } + } catch (Exception e) { + e.printStackTrace(); + } + }); + } } catch (Exception e) { e.printStackTrace(); } } } - - if ((firstFound || imgSrcFirst.isEmpty()) && item.url != null) { - if (m_items.isEmpty()) - m_items.add(item); - else - uncheckedItems.add(item); - } } - - return firstFound; } } -- cgit v1.2.3-54-g00ecf From c6eb66e86742e2aaa6876cecc47ce366f4703567 Mon Sep 17 00:00:00 2001 From: Andrew Dolgov Date: Sat, 17 May 2025 14:46:29 +0300 Subject: even more gallery stuff --- .../main/java/org/fox/ttrss/GalleryActivity.java | 23 ++++++++++--------- .../src/main/java/org/fox/ttrss/GalleryModel.java | 2 +- .../java/org/fox/ttrss/GalleryVideoFragment.java | 26 ---------------------- 3 files changed, 13 insertions(+), 38 deletions(-) diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryActivity.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryActivity.java index 538e1c8f..97e66ffb 100755 --- a/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryActivity.java +++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryActivity.java @@ -251,7 +251,7 @@ public class GalleryActivity extends CommonActivity { @Override public void onCreate(Bundle savedInstanceState) { - // ActivityCompat.postponeEnterTransition(this); + ActivityCompat.postponeEnterTransition(this); // we use that before parent onCreate so let's init locally m_prefs = PreferenceManager @@ -277,17 +277,9 @@ public class GalleryActivity extends CommonActivity { m_title = getIntent().getStringExtra("title"); m_content = getIntent().getStringExtra("content"); - // this should be returned first so that transition completes properly + // this should be dealt with first so that transition completes properly String firstSrc = getIntent().getStringExtra("firstSrc"); - // Document doc = Jsoup.parse(m_content); - - // if we were unable to find first image, try again for all media content so that - // gallery doesn't lock up because of a pending shared transition - /* if (!collectGalleryContents(imgSrcFirst, doc, uncheckedItems)) - if (!collectGalleryContents("", doc, uncheckedItems)) - m_items.add(new GalleryEntry(imgSrcFirst, GalleryEntry.GalleryEntryType.TYPE_IMAGE, null)); */ - GalleryModel model = new ViewModelProvider(this).get(GalleryModel.class); model.collectItems(m_content, firstSrc); @@ -295,7 +287,16 @@ public class GalleryActivity extends CommonActivity { Log.d(TAG, "observed gallery entries=" + galleryEntries + " firstSrc=" + firstSrc); m_adapter.submitList(galleryEntries, () -> { - Log.d(TAG, "selecting first src=" + firstSrc); + for (GalleryEntry entry : galleryEntries) { + if (entry.url.equals(firstSrc)) { + int position = galleryEntries.indexOf(entry); + + Log.d(TAG, "selecting first src=" + firstSrc + " pos=" + position); + m_pager.setCurrentItem(position); + + break; + } + } }); }); diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryModel.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryModel.java index 1e059e0f..16c38ed4 100644 --- a/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryModel.java +++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryModel.java @@ -65,7 +65,7 @@ public class GalleryModel extends AndroidViewModel { Log.d(TAG, "checking vid src=" + src + " poster=" + poster); - if (poster != null && poster.equals(srcFirst) || src != null && src.equals(srcFirst)) { + if (src != null && src.equals(srcFirst)) { Log.d(TAG, "first item found, vid=" + src); GalleryEntry item = new GalleryEntry(src, GalleryEntry.GalleryEntryType.TYPE_VIDEO, poster); diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryVideoFragment.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryVideoFragment.java index a96e59b5..28d6cf7d 100755 --- a/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryVideoFragment.java +++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryVideoFragment.java @@ -57,32 +57,6 @@ public class GalleryVideoFragment extends GalleryBaseFragment { registerForContextMenu(imgView); - /*final GlideDrawableImageViewTarget glideImage = new GlideDrawableImageViewTarget(imgView); - - Glide.with(this) - .load(m_coverUrl) - //.dontAnimate() - .diskCacheStrategy(DiskCacheStrategy.ALL) - .skipMemoryCache(false) - .listener(new RequestListener() { - @Override - public boolean onException(Exception e, String model, Target target, boolean isFirstResource) { - ActivityCompat.startPostponedEnterTransition(m_activity); - - initializeVideoPlayer(view); - return false; - } - - @Override - public boolean onResourceReady(GlideDrawable resource, String model, Target target, boolean isFromMemoryCache, boolean isFirstResource) { - ActivityCompat.startPostponedEnterTransition(m_activity); - - initializeVideoPlayer(view); - return false; - } - }) - .into(glideImage); */ - ActivityCompat.startPostponedEnterTransition(m_activity); initializeVideoPlayer(view); -- cgit v1.2.3-54-g00ecf From afab430a0f51bd009895f8f930b1a2f2c98cdc47 Mon Sep 17 00:00:00 2001 From: Andrew Dolgov Date: Sat, 17 May 2025 14:48:30 +0300 Subject: only try to select first item once --- .../src/main/java/org/fox/ttrss/GalleryActivity.java | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryActivity.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryActivity.java index 97e66ffb..89fcd59c 100755 --- a/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryActivity.java +++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryActivity.java @@ -53,6 +53,7 @@ public class GalleryActivity extends CommonActivity { public String m_content; private ViewPager2 m_pager; // TODO replace with viewpager2 private ProgressBar m_checkProgress; + private boolean m_firstWasSelected; private static class GalleryEntryDiffItemCallback extends DiffUtil.ItemCallback { @@ -287,14 +288,17 @@ public class GalleryActivity extends CommonActivity { Log.d(TAG, "observed gallery entries=" + galleryEntries + " firstSrc=" + firstSrc); m_adapter.submitList(galleryEntries, () -> { - for (GalleryEntry entry : galleryEntries) { - if (entry.url.equals(firstSrc)) { - int position = galleryEntries.indexOf(entry); + if (!m_firstWasSelected) { + for (GalleryEntry entry : galleryEntries) { + if (entry.url.equals(firstSrc)) { + int position = galleryEntries.indexOf(entry); - Log.d(TAG, "selecting first src=" + firstSrc + " pos=" + position); - m_pager.setCurrentItem(position); + Log.d(TAG, "selecting first src=" + firstSrc + " pos=" + position); + m_pager.setCurrentItem(position); - break; + m_firstWasSelected = true; + break; + } } } }); -- cgit v1.2.3-54-g00ecf From 6964d2cbce2e23e8d8767cc4192a3f46f93ccd42 Mon Sep 17 00:00:00 2001 From: Andrew Dolgov Date: Sat, 17 May 2025 15:13:49 +0300 Subject: add very suboptimal progress reporting for gallery --- .../main/java/org/fox/ttrss/GalleryActivity.java | 25 ++++++++++++-------- .../src/main/java/org/fox/ttrss/GalleryModel.java | 27 ++++++++++++++++++---- 2 files changed, 37 insertions(+), 15 deletions(-) diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryActivity.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryActivity.java index 89fcd59c..afd70b08 100755 --- a/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryActivity.java +++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryActivity.java @@ -20,31 +20,21 @@ import androidx.core.view.WindowInsetsCompat; import androidx.core.view.WindowInsetsControllerCompat; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentActivity; -import androidx.fragment.app.FragmentManager; -import androidx.fragment.app.FragmentStatePagerAdapter; import androidx.lifecycle.ViewModelProvider; import androidx.preference.PreferenceManager; import androidx.recyclerview.widget.DiffUtil; -import androidx.viewpager.widget.ViewPager; import androidx.viewpager2.widget.ViewPager2; -import com.ToxicBakery.viewpager.transforms.DepthPageTransformer; import com.bumptech.glide.Glide; import com.bumptech.glide.load.engine.DiskCacheStrategy; import org.fox.ttrss.types.GalleryEntry; import org.fox.ttrss.util.DiffFragmentStateAdapter; -import org.jsoup.Jsoup; -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; -import org.jsoup.select.Elements; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutionException; -import me.relex.circleindicator.CircleIndicator; - public class GalleryActivity extends CommonActivity { private final String TAG = this.getClass().getSimpleName(); @@ -284,6 +274,21 @@ public class GalleryActivity extends CommonActivity { GalleryModel model = new ViewModelProvider(this).get(GalleryModel.class); model.collectItems(m_content, firstSrc); + model.getItemsToCheck().observe(this, itemsToCheck -> { + Log.d(TAG, "observed items to check=" + itemsToCheck); + + m_checkProgress.setMax(itemsToCheck); + m_checkProgress.setVisibility(View.VISIBLE); + m_checkProgress.setProgress(0); + }); + + model.getCheckProgress().observe(this, progress -> { + Log.d(TAG, "observed item check progress=" + progress); + + m_checkProgress.setProgress(progress); + m_checkProgress.setVisibility(progress < m_checkProgress.getMax() ? View.VISIBLE : View.GONE); + }); + model.getItems().observe(this, galleryEntries -> { Log.d(TAG, "observed gallery entries=" + galleryEntries + " firstSrc=" + firstSrc); diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryModel.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryModel.java index 16c38ed4..9486b7ed 100644 --- a/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryModel.java +++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryModel.java @@ -21,8 +21,6 @@ import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; -import java.net.MalformedURLException; -import java.net.URISyntaxException; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutorService; @@ -32,6 +30,8 @@ public class GalleryModel extends AndroidViewModel { private final String TAG = this.getClass().getSimpleName(); private MutableLiveData> m_items = new MutableLiveData<>(new ArrayList<>()); + private MutableLiveData m_checkProgress = new MutableLiveData<>(Integer.valueOf(0)); + private MutableLiveData m_itemsToCheck = new MutableLiveData(Integer.valueOf(0)); public GalleryModel(@NonNull Application application) { super(application); @@ -44,6 +44,14 @@ public class GalleryModel extends AndroidViewModel { private ExecutorService m_executor = Executors.newSingleThreadExecutor(); private Handler m_mainHandler = new Handler(Looper.getMainLooper()); + public LiveData getItemsToCheck() { + return m_itemsToCheck; + } + + public LiveData getCheckProgress() { + return m_checkProgress; + } + public void collectItems(String articleText, String srcFirst) { Document doc = Jsoup.parse(articleText); @@ -55,7 +63,13 @@ public class GalleryModel extends AndroidViewModel { Elements elems = doc.select("img,video"); + m_itemsToCheck.postValue(elems.size()); + + int currentItem = 0; + for (Element elem : elems) { + ++currentItem; + if ("video".equalsIgnoreCase(elem.tagName())) { Element source = elem.select("source").first(); String poster = elem.attr("abs:poster"); @@ -87,7 +101,6 @@ public class GalleryModel extends AndroidViewModel { } } } - } else { String src = elem.attr("abs:src"); @@ -122,8 +135,10 @@ public class GalleryModel extends AndroidViewModel { if (bmp != null && bmp.getWidth() >= HeadlinesFragment.FLAVOR_IMG_MIN_SIZE && bmp.getHeight() >= HeadlinesFragment.FLAVOR_IMG_MIN_SIZE) { Log.d(TAG, "image matches gallery criteria, adding..."); - checkList.add(new GalleryEntry(src, GalleryEntry.GalleryEntryType.TYPE_IMAGE, null)); - m_items.postValue(checkList); + m_mainHandler.post(() -> { + checkList.add(new GalleryEntry(src, GalleryEntry.GalleryEntryType.TYPE_IMAGE, null)); + m_items.postValue(checkList); + }); } } catch (Exception e) { e.printStackTrace(); @@ -135,6 +150,8 @@ public class GalleryModel extends AndroidViewModel { } } } + + m_checkProgress.postValue(currentItem); } } } -- cgit v1.2.3-54-g00ecf From 361abacbe113eda94617d5629d1f20dc5b7da05a Mon Sep 17 00:00:00 2001 From: Andrew Dolgov Date: Sat, 17 May 2025 16:40:17 +0300 Subject: cleanup video fragment a bit, do all gallery model processing in a single background job --- .../src/main/java/org/fox/ttrss/GalleryModel.java | 140 ++++++++++----------- .../java/org/fox/ttrss/GalleryVideoFragment.java | 56 ++------- 2 files changed, 81 insertions(+), 115 deletions(-) diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryModel.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryModel.java index 9486b7ed..5b550015 100644 --- a/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryModel.java +++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryModel.java @@ -31,7 +31,7 @@ public class GalleryModel extends AndroidViewModel { private MutableLiveData> m_items = new MutableLiveData<>(new ArrayList<>()); private MutableLiveData m_checkProgress = new MutableLiveData<>(Integer.valueOf(0)); - private MutableLiveData m_itemsToCheck = new MutableLiveData(Integer.valueOf(0)); + private MutableLiveData m_itemsToCheck = new MutableLiveData<>(Integer.valueOf(0)); public GalleryModel(@NonNull Application application) { super(application); @@ -52,106 +52,102 @@ public class GalleryModel extends AndroidViewModel { return m_checkProgress; } - public void collectItems(String articleText, String srcFirst) { - Document doc = Jsoup.parse(articleText); + private boolean isDataUri(String src) { + try { + Uri uri = Uri.parse(src); - List checkList = new ArrayList<>(); + return "data".equalsIgnoreCase(uri.getScheme()); - /* look for srcFirst quickly and post an update */ + } catch (Exception e) { + e.printStackTrace(); + } - Log.d(TAG, "looking for srcFirst=" + srcFirst); + return false; + } - Elements elems = doc.select("img,video"); + public void collectItems(String articleText, String srcFirst) { + m_executor.execute(() -> { - m_itemsToCheck.postValue(elems.size()); + Document doc = Jsoup.parse(articleText); - int currentItem = 0; + List checkList = new ArrayList<>(); - for (Element elem : elems) { - ++currentItem; + Log.d(TAG, "looking for srcFirst=" + srcFirst); - if ("video".equalsIgnoreCase(elem.tagName())) { - Element source = elem.select("source").first(); - String poster = elem.attr("abs:poster"); + Elements elems = doc.select("img,video"); - if (source != null) { - String src = source.attr("abs:src"); + m_itemsToCheck.postValue(elems.size()); - Log.d(TAG, "checking vid src=" + src + " poster=" + poster); + int currentItem = 0; - if (src != null && src.equals(srcFirst)) { - Log.d(TAG, "first item found, vid=" + src); + for (Element elem : elems) { + ++currentItem; - GalleryEntry item = new GalleryEntry(src, GalleryEntry.GalleryEntryType.TYPE_VIDEO, poster); + if ("video".equalsIgnoreCase(elem.tagName())) { + Element source = elem.select("source").first(); + String poster = elem.attr("abs:poster"); - checkList.add(item); + if (source != null) { + String src = source.attr("abs:src"); - m_items.postValue(checkList); - } else { - try { - Uri checkUri = Uri.parse(src); + Log.d(TAG, "checking vid src=" + src + " poster=" + poster); - if (!"data".equalsIgnoreCase(checkUri.getScheme())) { - checkList.add(new GalleryEntry(src, GalleryEntry.GalleryEntryType.TYPE_VIDEO, poster)); + if (src != null && src.equals(srcFirst)) { + Log.d(TAG, "first item found, vid=" + src); + GalleryEntry item = new GalleryEntry(src, GalleryEntry.GalleryEntryType.TYPE_VIDEO, poster); + + checkList.add(item); + + m_items.postValue(checkList); + } else { + if (!isDataUri(src)) { + checkList.add(new GalleryEntry(src, GalleryEntry.GalleryEntryType.TYPE_VIDEO, poster)); m_items.postValue(checkList); } - } catch (Exception e) { - e.printStackTrace(); } } - } - } else { - String src = elem.attr("abs:src"); + } else { + String src = elem.attr("abs:src"); - Log.d(TAG, "checking img src=" + src); + Log.d(TAG, "checking img src=" + src); - if (src != null && src.equals(srcFirst)) { - Log.d(TAG, "first item found, img=" + src); + if (src != null && src.equals(srcFirst)) { + Log.d(TAG, "first item found, img=" + src); - GalleryEntry item = new GalleryEntry(src, GalleryEntry.GalleryEntryType.TYPE_IMAGE, null); + GalleryEntry item = new GalleryEntry(src, GalleryEntry.GalleryEntryType.TYPE_IMAGE, null); - checkList.add(item); + checkList.add(item); - m_items.postValue(checkList); - } else { - try { - Uri checkUri = Uri.parse(src); - - if (!"data".equalsIgnoreCase(checkUri.getScheme())) { - - m_executor.execute(() -> { - Log.d(TAG, "checking image with glide: " + src); - - try { - Bitmap bmp = Glide.with(getApplication().getApplicationContext()) - .load(src) - .asBitmap() - .skipMemoryCache(false) - .diskCacheStrategy(DiskCacheStrategy.ALL) - .into(HeadlinesFragment.FLAVOR_IMG_MIN_SIZE, HeadlinesFragment.FLAVOR_IMG_MIN_SIZE) - .get(); - - if (bmp != null && bmp.getWidth() >= HeadlinesFragment.FLAVOR_IMG_MIN_SIZE && bmp.getHeight() >= HeadlinesFragment.FLAVOR_IMG_MIN_SIZE) { - Log.d(TAG, "image matches gallery criteria, adding..."); - - m_mainHandler.post(() -> { - checkList.add(new GalleryEntry(src, GalleryEntry.GalleryEntryType.TYPE_IMAGE, null)); - m_items.postValue(checkList); - }); - } - } catch (Exception e) { - e.printStackTrace(); + m_items.postValue(checkList); + } else { + if (!isDataUri(src)) { + Log.d(TAG, "checking image with glide: " + src); + + try { + Bitmap bmp = Glide.with(getApplication().getApplicationContext()) + .load(src) + .asBitmap() + .skipMemoryCache(false) + .diskCacheStrategy(DiskCacheStrategy.ALL) + .into(HeadlinesFragment.FLAVOR_IMG_MIN_SIZE, HeadlinesFragment.FLAVOR_IMG_MIN_SIZE) + .get(); + + if (bmp != null && bmp.getWidth() >= HeadlinesFragment.FLAVOR_IMG_MIN_SIZE && bmp.getHeight() >= HeadlinesFragment.FLAVOR_IMG_MIN_SIZE) { + Log.d(TAG, "image matches gallery criteria, adding..."); + + checkList.add(new GalleryEntry(src, GalleryEntry.GalleryEntryType.TYPE_IMAGE, null)); + m_items.postValue(checkList); } - }); + } catch (Exception e) { + e.printStackTrace(); + } } - } catch (Exception e) { - e.printStackTrace(); } } - } - m_checkProgress.postValue(currentItem); - } + m_checkProgress.postValue(currentItem); + } + }); } } diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryVideoFragment.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryVideoFragment.java index 28d6cf7d..18d08e4f 100755 --- a/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryVideoFragment.java +++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryVideoFragment.java @@ -26,7 +26,6 @@ public class GalleryVideoFragment extends GalleryBaseFragment { String m_url; String m_coverUrl; MediaPlayer m_mediaPlayer; - private boolean m_userVisibleHint = false; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -58,60 +57,34 @@ public class GalleryVideoFragment extends GalleryBaseFragment { registerForContextMenu(imgView); ActivityCompat.startPostponedEnterTransition(m_activity); - initializeVideoPlayer(view); - - return view; - } - - @Override - public void setUserVisibleHint(boolean isVisibleToUser) { - super.setUserVisibleHint(isVisibleToUser); - m_userVisibleHint = isVisibleToUser; - Log.d(TAG, "setUserVisibleHint: " + isVisibleToUser); - - if (getView() == null) return; - - try { - - if (isVisibleToUser) { - if (m_mediaPlayer != null && !m_mediaPlayer.isPlaying()) { - m_mediaPlayer.start(); - } + view.findViewById(R.id.flavor_image_progress).setVisibility(View.VISIBLE); - } else { - if (m_mediaPlayer != null && m_mediaPlayer.isPlaying()) { - m_mediaPlayer.pause(); - } - } - } catch (IllegalStateException e) { - e.printStackTrace(); - } + initializeVideoPlayer(view); + return view; } private void initializeVideoPlayer(final View view) { - - //Log.d(TAG, "initializeVideoPlayer: " + m_activity + " " + view); - - - final MediaController m_mediaController = new MediaController(m_activity); + final MediaController mediaController = new MediaController(m_activity); final TextureView textureView = view.findViewById(R.id.flavor_video); registerForContextMenu(textureView); textureView.setOnClickListener(v -> { try { - if (!m_mediaController.isShowing()) - m_mediaController.show(5000); + + if (mediaController.isShowing()) + mediaController.hide(); else - m_mediaController.hide(); + mediaController.show(); + } catch (Exception e) { e.printStackTrace(); } }); - m_mediaController.setAnchorView(textureView); + mediaController.setAnchorView(textureView); textureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() { @Override @@ -120,7 +93,7 @@ public class GalleryVideoFragment extends GalleryBaseFragment { m_mediaPlayer = new MediaPlayer(); - m_mediaController.setMediaPlayer(new MediaController.MediaPlayerControl() { + mediaController.setMediaPlayer(new MediaController.MediaPlayerControl() { @Override public void start() { m_mediaPlayer.start(); @@ -187,16 +160,13 @@ public class GalleryVideoFragment extends GalleryBaseFragment { } m_mediaPlayer.setOnPreparedListener(mp -> { - getView().findViewById(R.id.flavor_image).setVisibility(View.GONE); - getView().findViewById(R.id.flavor_image_progress).setVisibility(View.GONE); + view.findViewById(R.id.flavor_image_progress).setVisibility(View.GONE); try { resizeSurface(textureView); mp.setLooping(true); - if (m_userVisibleHint) { - mp.start(); - } + mp.start(); } catch (IllegalStateException e) { e.printStackTrace(); } -- cgit v1.2.3-54-g00ecf From 4be33b1faf3ae819c7323af36bbebfe4d72e66c1 Mon Sep 17 00:00:00 2001 From: Andrew Dolgov Date: Sat, 17 May 2025 16:54:49 +0300 Subject: drop unused dependencies --- org.fox.ttrss/build.gradle | 2 -- 1 file changed, 2 deletions(-) diff --git a/org.fox.ttrss/build.gradle b/org.fox.ttrss/build.gradle index 7ffaf523..f042c029 100755 --- a/org.fox.ttrss/build.gradle +++ b/org.fox.ttrss/build.gradle @@ -153,9 +153,7 @@ dependencies { implementation 'com.github.natario1:NestedScrollCoordinatorLayout:5a33a7dbd8' implementation 'com.google.android.material:material:1.12.0' implementation 'com.google.code.gson:gson:2.10.1' - implementation 'com.ToxicBakery.viewpager.transforms:view-pager-transforms:1.2.32@aar' implementation 'me.relex:circleindicator:1.2.2@aar' - implementation 'com.nineoldandroids:library:2.4.0' implementation 'com.github.amulyakhare:TextDrawable:558677ea31' implementation 'com.telefonica:nestedscrollwebview:0.1.6' implementation 'androidx.preference:preference:1.2.1' -- cgit v1.2.3-54-g00ecf From 10ae5ad31910949dd771ea28a7448454f2e3d64c Mon Sep 17 00:00:00 2001 From: Andrew Dolgov Date: Sat, 17 May 2025 17:22:45 +0300 Subject: experimental - switch to glide 4 --- org.fox.ttrss/build.gradle | 7 +- org.fox.ttrss/src/main/AndroidManifest.xml | 3 - .../src/main/java/org/fox/ttrss/ApiCommon.java | 9 +- .../main/java/org/fox/ttrss/CommonActivity.java | 8 +- .../main/java/org/fox/ttrss/GalleryActivity.java | 149 +----------------- .../java/org/fox/ttrss/GalleryImageFragment.java | 22 +-- .../src/main/java/org/fox/ttrss/GalleryModel.java | 2 +- .../main/java/org/fox/ttrss/HeadlinesFragment.java | 44 +++--- .../fox/ttrss/glide/OkHttpProgressGlideModule.java | 168 --------------------- .../java/org/fox/ttrss/glide/ProgressTarget.java | 110 -------------- .../java/org/fox/ttrss/glide/WrappingTarget.java | 54 ------- .../src/main/res/layout/headlines_row.xml | 2 +- .../src/main/res/layout/headlines_row_unread.xml | 2 +- 13 files changed, 48 insertions(+), 532 deletions(-) delete mode 100644 org.fox.ttrss/src/main/java/org/fox/ttrss/glide/OkHttpProgressGlideModule.java delete mode 100644 org.fox.ttrss/src/main/java/org/fox/ttrss/glide/ProgressTarget.java delete mode 100755 org.fox.ttrss/src/main/java/org/fox/ttrss/glide/WrappingTarget.java diff --git a/org.fox.ttrss/build.gradle b/org.fox.ttrss/build.gradle index f042c029..6712a1c3 100755 --- a/org.fox.ttrss/build.gradle +++ b/org.fox.ttrss/build.gradle @@ -136,13 +136,10 @@ def getVersion() { dependencies { implementation 'com.squareup.okhttp3:okhttp:3.12.5' - implementation('com.github.bumptech.glide:okhttp3-integration:1.5.0') { - exclude group: 'glide-parent' - } implementation 'org.jsoup:jsoup:1.11.3' implementation 'com.bogdwellers:pinchtozoom:0.1' - implementation 'com.github.bumptech.glide:glide:3.8.0' - implementation files('libs/glide-transformations-2.0.2.jar') + implementation 'com.github.bumptech.glide:glide:4.11.0' + implementation 'jp.wasabeef:glide-transformations:4.3.0' implementation 'androidx.recyclerview:recyclerview:1.4.0' implementation 'androidx.activity:activity:1.10.1' implementation 'androidx.legacy:legacy-support-v4:1.0.0' diff --git a/org.fox.ttrss/src/main/AndroidManifest.xml b/org.fox.ttrss/src/main/AndroidManifest.xml index 3388d515..f2a209f9 100755 --- a/org.fox.ttrss/src/main/AndroidManifest.xml +++ b/org.fox.ttrss/src/main/AndroidManifest.xml @@ -21,9 +21,6 @@ android:label="@string/app_name" android:networkSecurityConfig="@xml/network_security_config" > - - 0) caller.notifyProgress((int) (bytesRead * 100f / contentLength)); } - }; + }; */ /* lets shamelessly hijack OkHttpProgressGlideModule */ @@ -162,7 +159,7 @@ public class ApiCommon { .connectTimeout(10, TimeUnit.SECONDS) .writeTimeout(10, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS) - .addNetworkInterceptor(createInterceptor(listener)) +// .addNetworkInterceptor(createInterceptor(listener)) .build(); Response response = client.newCall(request).execute(); diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/CommonActivity.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/CommonActivity.java index 97e356c4..749d1131 100755 --- a/org.fox.ttrss/src/main/java/org/fox/ttrss/CommonActivity.java +++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/CommonActivity.java @@ -27,6 +27,8 @@ import android.view.View; import android.widget.CheckBox; import androidx.activity.EdgeToEdge; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatDelegate; import androidx.browser.customtabs.CustomTabsCallback; @@ -40,8 +42,8 @@ import androidx.preference.PreferenceManager; import com.bumptech.glide.Glide; import com.bumptech.glide.load.engine.DiskCacheStrategy; -import com.bumptech.glide.request.animation.GlideAnimation; import com.bumptech.glide.request.target.SimpleTarget; +import com.bumptech.glide.request.transition.Transition; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.snackbar.Snackbar; @@ -348,13 +350,13 @@ public class CommonActivity extends AppCompatActivity implements SharedPreferenc protected void shareImageFromUri(String url) { Glide.with(this) - .load(url) .asBitmap() + .load(url) .skipMemoryCache(false) .diskCacheStrategy(DiskCacheStrategy.ALL) .into(new SimpleTarget() { @Override - public void onResourceReady(Bitmap resource, GlideAnimation glideAnimation) { + public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition transition) { Log.d(TAG, "image resource ready: " + resource); if (resource != null) { diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryActivity.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryActivity.java index afd70b08..eaa997f4 100755 --- a/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryActivity.java +++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryActivity.java @@ -66,8 +66,6 @@ public class GalleryActivity extends CommonActivity { @Override public Fragment createFragment(int position) { - //Log.d(TAG, "getItem: " + position + " " + m_urls.get(position)); - GalleryEntry item = getItem(position); switch (item.type) { @@ -88,151 +86,6 @@ public class GalleryActivity extends CommonActivity { } } - private static class MediaProgressResult { - GalleryEntry item; - int position; - int count; - - public MediaProgressResult(GalleryEntry item, int position, int count) { - this.item = item; - this.position = position; - this.count = count; - } - } - - private class MediaCheckTask extends AsyncTask, MediaProgressResult, List> { - - private final List m_checkedItems = new ArrayList<>(); - - @Override - protected List doInBackground(List... params) { - - ArrayList items = new ArrayList<>(params[0]); - int position = 0; - - for (GalleryEntry item : items) { - if (!isCancelled()) { - ++position; - - Log.d(TAG, "checking: " + item.url + " " + item.coverUrl); - - if (item.type == GalleryEntry.GalleryEntryType.TYPE_IMAGE) { - try { - Bitmap bmp = Glide.with(GalleryActivity.this) - .load(item.url) - .asBitmap() - .skipMemoryCache(false) - .diskCacheStrategy(DiskCacheStrategy.ALL) - //.dontTransform() - .into(HeadlinesFragment.FLAVOR_IMG_MIN_SIZE, HeadlinesFragment.FLAVOR_IMG_MIN_SIZE) - .get(); - - if (bmp.getWidth() >= HeadlinesFragment.FLAVOR_IMG_MIN_SIZE && bmp.getHeight() >= HeadlinesFragment.FLAVOR_IMG_MIN_SIZE) { - m_checkedItems.add(item); - publishProgress(new MediaProgressResult(item, position, items.size())); - } - - } catch (InterruptedException e) { - e.printStackTrace(); - } catch (ExecutionException e) { - e.printStackTrace(); - } catch (OutOfMemoryError e) { - e.printStackTrace(); - } - - } else { - m_checkedItems.add(item); - publishProgress(new MediaProgressResult(item, position, items.size())); - } - } - } - - return m_checkedItems; - } - } - - /* - boolean collectGalleryContents(String imgSrcFirst, Document doc, List uncheckedItems ) { - Elements elems = doc.select("img,video"); - - boolean firstFound = false; - - for (Element elem : elems) { - - GalleryEntry item = new GalleryEntry(); - - if ("video".equalsIgnoreCase(elem.tagName())) { - String cover = elem.attr("poster"); - - Element source = elem.select("source").first(); - - if (source != null) { - String src = source.attr("src"); - - if (!src.isEmpty()) { - //Log.d(TAG, "vid/src=" + src); - - if (src.startsWith("//")) { - src = "https:" + src; - } - - if (imgSrcFirst.equals(src)) - firstFound = true; - - try { - Uri checkUri = Uri.parse(src); - - if (!"data".equalsIgnoreCase(checkUri.getScheme())) { - item.url = src; - item.coverUrl = cover; - item.type = GalleryEntry.GalleryEntryType.TYPE_VIDEO; - } - - } catch (Exception e) { - e.printStackTrace(); - } - } - } - - } else { - String src = elem.attr("src"); - - if (!src.isEmpty()) { - if (src.startsWith("//")) { - src = "https:" + src; - } - - if (imgSrcFirst.equals(src)) - firstFound = true; - - Log.d(TAG, "img/fir=" + imgSrcFirst + ";"); - Log.d(TAG, "img/src=" + src + "; ff=" + firstFound); - - try { - Uri checkUri = Uri.parse(src); - - if (!"data".equalsIgnoreCase(checkUri.getScheme())) { - item.url = src; - item.type = GalleryEntry.GalleryEntryType.TYPE_IMAGE; - } - - } catch (Exception e) { - e.printStackTrace(); - } - } - } - - if ((firstFound || imgSrcFirst.isEmpty()) && item.url != null) { - if (m_items.isEmpty()) - m_items.add(item); - else - uncheckedItems.add(item); - } - } - - return firstFound; - } */ - public void onSaveInstanceState(Bundle out) { super.onSaveInstanceState(out); @@ -242,7 +95,7 @@ public class GalleryActivity extends CommonActivity { @Override public void onCreate(Bundle savedInstanceState) { - ActivityCompat.postponeEnterTransition(this); + // ActivityCompat.postponeEnterTransition(this); // we use that before parent onCreate so let's init locally m_prefs = PreferenceManager diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryImageFragment.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryImageFragment.java index 8f9d5783..d5206518 100755 --- a/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryImageFragment.java +++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryImageFragment.java @@ -1,5 +1,6 @@ package org.fox.ttrss; +import android.graphics.drawable.Drawable; import android.os.Bundle; import android.util.Log; import android.view.LayoutInflater; @@ -8,15 +9,17 @@ import android.view.ViewGroup; import android.widget.ImageView; import android.widget.ProgressBar; +import androidx.annotation.Nullable; import androidx.core.app.ActivityCompat; import androidx.core.view.ViewCompat; import com.bogdwellers.pinchtozoom.ImageMatrixTouchHandler; import com.bumptech.glide.Glide; +import com.bumptech.glide.load.DataSource; import com.bumptech.glide.load.engine.DiskCacheStrategy; -import com.bumptech.glide.load.resource.drawable.GlideDrawable; +import com.bumptech.glide.load.engine.GlideException; import com.bumptech.glide.request.RequestListener; -import com.bumptech.glide.request.target.GlideDrawableImageViewTarget; +import com.bumptech.glide.request.target.DrawableImageViewTarget; import com.bumptech.glide.request.target.Target; public class GalleryImageFragment extends GalleryBaseFragment { @@ -56,33 +59,34 @@ public class GalleryImageFragment extends GalleryBaseFragment { final ProgressBar progressBar = view.findViewById(R.id.flavor_image_progress); final View errorMessage = view.findViewById(R.id.flavor_image_error); - final GlideDrawableImageViewTarget glideImage = new GlideDrawableImageViewTarget(imgView); + // final GlideDrawableImageViewTarget glideImage = new GlideDrawableImageViewTarget(imgView); - Glide.with(getContext()) + Glide.with(m_activity) .load(m_url) - //.dontAnimate() .diskCacheStrategy(DiskCacheStrategy.ALL) .skipMemoryCache(false) - .listener(new RequestListener() { + .listener(new RequestListener() { @Override - public boolean onException(Exception e, String model, Target target, boolean isFirstResource) { + public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) { progressBar.setVisibility(View.GONE); errorMessage.setVisibility(View.VISIBLE); ActivityCompat.startPostponedEnterTransition(m_activity); + return false; } @Override - public boolean onResourceReady(GlideDrawable resource, String model, Target target, boolean isFromMemoryCache, boolean isFirstResource) { + public boolean onResourceReady(Drawable resource, Object model, Target target, DataSource dataSource, boolean isFirstResource) { progressBar.setVisibility(View.GONE); errorMessage.setVisibility(View.GONE); ActivityCompat.startPostponedEnterTransition(m_activity); + return false; } }) - .into(glideImage); + .into(new DrawableImageViewTarget(imgView)); return view; } diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryModel.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryModel.java index 5b550015..8458fc33 100644 --- a/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryModel.java +++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryModel.java @@ -126,8 +126,8 @@ public class GalleryModel extends AndroidViewModel { try { Bitmap bmp = Glide.with(getApplication().getApplicationContext()) - .load(src) .asBitmap() + .load(src) .skipMemoryCache(false) .diskCacheStrategy(DiskCacheStrategy.ALL) .into(HeadlinesFragment.FLAVOR_IMG_MIN_SIZE, HeadlinesFragment.FLAVOR_IMG_MIN_SIZE) 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 8c24a14a..a1ee92c7 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 @@ -45,6 +45,7 @@ import android.widget.PopupMenu; import android.widget.ProgressBar; import android.widget.TextView; +import androidx.annotation.Nullable; import androidx.core.app.ActivityCompat; import androidx.core.app.ActivityOptionsCompat; import androidx.core.content.ContextCompat; @@ -60,16 +61,16 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import com.amulyakhare.textdrawable.TextDrawable; import com.amulyakhare.textdrawable.util.ColorGenerator; import com.bumptech.glide.Glide; +import com.bumptech.glide.load.DataSource; import com.bumptech.glide.load.engine.DiskCacheStrategy; -import com.bumptech.glide.load.resource.drawable.GlideDrawable; +import com.bumptech.glide.load.engine.GlideException; import com.bumptech.glide.request.RequestListener; -import com.bumptech.glide.request.target.GlideDrawableImageViewTarget; +import com.bumptech.glide.request.target.DrawableImageViewTarget; import com.bumptech.glide.request.target.Target; import com.google.android.material.button.MaterialButton; import com.google.android.material.dialog.MaterialAlertDialogBuilder; 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; @@ -615,7 +616,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { public View flavorImageOverflow; public TextureView flavorVideoView; public MaterialButton attachmentsView; - public ProgressTarget flavorProgressTarget; + //public ProgressTarget flavorProgressTarget; int articleId; public TextView linkHost; @@ -657,10 +658,9 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { attachmentsView = v.findViewById(R.id.attachments); linkHost = v.findViewById(R.id.link_host); - if (flavorImageView != null && flavorImageLoadingBar != null) { + /* if (flavorImageView != null && flavorImageLoadingBar != null) { flavorProgressTarget = new FlavorProgressTarget<>(new GlideDrawableImageViewTarget(flavorImageView), flavorImageLoadingBar); - } - + } */ } public void clearAnimation() { @@ -668,7 +668,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { } } - private static class FlavorProgressTarget extends ProgressTarget { + /* private static class FlavorProgressTarget extends ProgressTarget { private final ProgressBar progress; public FlavorProgressTarget(Target target, ProgressBar progress) { super(target); @@ -693,7 +693,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { @Override protected void onDelivered() { progress.setVisibility(View.INVISIBLE); } - } + } */ private class ArticleListAdapter extends ListAdapter { public static final int VIEW_NORMAL = 0; @@ -1014,7 +1014,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { holder.flavorVideoView.setVisibility(View.GONE); holder.flavorImageHolder.setVisibility(View.GONE); - Glide.clear(holder.flavorImageView); + Glide.with(m_activity).clear(holder.flavorImageView); // this is needed if our flavor image goes behind base listview element holder.headlineHeader.setOnClickListener(v -> { @@ -1083,18 +1083,17 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { } } - holder.flavorProgressTarget.setModel(article.flavorImageUri); + // holder.flavorProgressTarget.setModel(article.flavorImageUri); try { - Glide.with(getContext()) + Glide.with(m_activity) .load(article.flavorImageUri) - //.dontTransform() .diskCacheStrategy(DiskCacheStrategy.ALL) .skipMemoryCache(false) - .listener(new RequestListener() { + .listener(new RequestListener() { @Override - public boolean onException(Exception e, String model, Target target, boolean isFirstResource) { + public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) { holder.flavorImageLoadingBar.setVisibility(View.GONE); holder.flavorImageView.setVisibility(View.GONE); @@ -1103,7 +1102,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { } @Override - public boolean onResourceReady(GlideDrawable resource, String model, Target target, boolean isFromMemoryCache, boolean isFirstResource) { + public boolean onResourceReady(Drawable resource, Object model, Target target, DataSource dataSource, boolean isFirstResource) { holder.flavorImageLoadingBar.setVisibility(View.GONE); @@ -1124,7 +1123,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { } } }) - .into(holder.flavorProgressTarget); + .into(new DrawableImageViewTarget(holder.flavorImageView)); } catch (OutOfMemoryError e) { e.printStackTrace(); } @@ -1338,22 +1337,21 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { holder.textImage.setImageDrawable(textDrawable); } else { - Glide.with(getContext()) + Glide.with(m_activity) .load(article.flavorImageUri) .placeholder(textDrawable) .thumbnail(0.5f) - .bitmapTransform(new CropCircleTransformation(getActivity())) + .transform(new CropCircleTransformation()) .diskCacheStrategy(DiskCacheStrategy.ALL) .skipMemoryCache(false) - .listener(new RequestListener() { + .listener(new RequestListener() { @Override - public boolean onException(Exception e, String model, Target target, boolean isFirstResource) { + public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) { return false; } @Override - public boolean onResourceReady(GlideDrawable resource, String model, Target target, boolean isFromMemoryCache, boolean isFirstResource) { - + public boolean onResourceReady(Drawable resource, Object model, Target target, DataSource dataSource, boolean isFirstResource) { return resource.getIntrinsicWidth() < THUMB_IMG_MIN_SIZE || resource.getIntrinsicHeight() < THUMB_IMG_MIN_SIZE; } }) diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/glide/OkHttpProgressGlideModule.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/glide/OkHttpProgressGlideModule.java deleted file mode 100644 index bb868f8e..00000000 --- a/org.fox.ttrss/src/main/java/org/fox/ttrss/glide/OkHttpProgressGlideModule.java +++ /dev/null @@ -1,168 +0,0 @@ -package org.fox.ttrss.glide; - -import android.content.Context; -import android.os.Handler; -import android.os.Looper; - -import com.bumptech.glide.Glide; -import com.bumptech.glide.GlideBuilder; -import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader; -import com.bumptech.glide.load.model.GlideUrl; -import com.bumptech.glide.module.GlideModule; - -import java.io.IOException; -import java.io.InputStream; -import java.util.HashMap; -import java.util.Map; - -import okhttp3.HttpUrl; -import okhttp3.Interceptor; -import okhttp3.MediaType; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; -import okhttp3.ResponseBody; -import okio.Buffer; -import okio.BufferedSource; -import okio.ForwardingSource; -import okio.Okio; -import okio.Source; - -public class OkHttpProgressGlideModule implements GlideModule { - @Override public void applyOptions(Context context, GlideBuilder builder) { - - } - @Override public void registerComponents(Context context, Glide glide) { - OkHttpClient client = new OkHttpClient.Builder() - .addNetworkInterceptor(createInterceptor(new DispatchingProgressListener())) - .build(); - glide.register(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory(client)); - } - - public static Interceptor createInterceptor(final ResponseProgressListener listener) { - return chain -> { - Request request = chain.request(); - Response response = chain.proceed(request); - return response.newBuilder() - .body(new OkHttpProgressResponseBody(request.url(), response.body(), listener)) - .build(); - }; - } - - public interface UIProgressListener { - void onProgress(long bytesRead, long expectedLength); - /** - * Control how often the listener needs an update. 0% and 100% will always be dispatched. - * @return in percentage (0.2 = call {@link #onProgress} around every 0.2 percent of progress) - */ - float getGranualityPercentage(); - } - - public static void forget(String url) { - DispatchingProgressListener.forget(url); - } - public static void expect(String url, UIProgressListener listener) { - DispatchingProgressListener.expect(url, listener); - } - - public interface ResponseProgressListener { - void update(HttpUrl url, long bytesRead, long contentLength); - } - - private static class DispatchingProgressListener implements ResponseProgressListener { - private static final Map LISTENERS = new HashMap<>(); - private static final Map PROGRESSES = new HashMap<>(); - - private final Handler handler; - DispatchingProgressListener() { - this.handler = new Handler(Looper.getMainLooper()); - } - - static void forget(String url) { - LISTENERS.remove(url); - PROGRESSES.remove(url); - } - static void expect(String url, UIProgressListener listener) { - LISTENERS.put(url, listener); - } - - @Override public void update(HttpUrl url, final long bytesRead, final long contentLength) { - //System.out.printf("%s: %d/%d = %.2f%%%n", url, bytesRead, contentLength, (100f * bytesRead) / contentLength); - - String key = url.toString(); - final UIProgressListener listener = LISTENERS.get(key); - - if (listener == null) { - return; - } - if (contentLength <= bytesRead) { - forget(key); - } - if (needsDispatch(key, bytesRead, contentLength, listener.getGranualityPercentage())) { - handler.post(() -> listener.onProgress(bytesRead, contentLength)); - } - } - - private boolean needsDispatch(String key, long current, long total, float granularity) { - if (granularity == 0 || current == 0 || total == current) { - return true; - } - float percent = 100f * current / total; - long currentProgress = (long)(percent / granularity); - Long lastProgress = PROGRESSES.get(key); - if (lastProgress == null || currentProgress != lastProgress) { - PROGRESSES.put(key, currentProgress); - return true; - } else { - return false; - } - } - } - - public static class OkHttpProgressResponseBody extends ResponseBody { - private final HttpUrl url; - private final ResponseBody responseBody; - private final ResponseProgressListener progressListener; - private BufferedSource bufferedSource; - - public OkHttpProgressResponseBody(HttpUrl url, ResponseBody responseBody, - ResponseProgressListener progressListener) { - - this.url = url; - this.responseBody = responseBody; - this.progressListener = progressListener; - } - - @Override public MediaType contentType() { - return responseBody.contentType(); - } - - @Override public long contentLength() { - return responseBody.contentLength(); - } - - @Override public BufferedSource source() { - if (bufferedSource == null) { - bufferedSource = Okio.buffer(source(responseBody.source())); - } - return bufferedSource; - } - - private Source source(Source source) { - return new ForwardingSource(source) { - long totalBytesRead = 0L; - @Override public long read(Buffer sink, long byteCount) throws IOException { - long bytesRead = super.read(sink, byteCount); - long fullLength = responseBody.contentLength(); - if (bytesRead == -1) { // this source is exhausted - totalBytesRead = fullLength; - } else { - totalBytesRead += bytesRead; - } - progressListener.update(url, totalBytesRead, fullLength); - return bytesRead; - } - }; - } - } -} \ No newline at end of file diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/glide/ProgressTarget.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/glide/ProgressTarget.java deleted file mode 100644 index 977d1954..00000000 --- a/org.fox.ttrss/src/main/java/org/fox/ttrss/glide/ProgressTarget.java +++ /dev/null @@ -1,110 +0,0 @@ -package org.fox.ttrss.glide; - - -import android.graphics.drawable.Drawable; - -import com.bumptech.glide.Glide; -import com.bumptech.glide.request.animation.GlideAnimation; -import com.bumptech.glide.request.target.Target; - -public abstract class ProgressTarget extends WrappingTarget implements OkHttpProgressGlideModule.UIProgressListener { - private T model; - private boolean ignoreProgress = true; - public ProgressTarget(Target target) { - this(null, target); - } - public ProgressTarget(T model, Target target) { - super(target); - this.model = model; - } - - public final T getModel() { - return model; - } - public final void setModel(T model) { - Glide.clear(this); // indirectly calls cleanup - this.model = model; - } - /** - * Convert a model into an Url string that is used to match up the OkHttp requests. For explicit - * {@link com.bumptech.glide.load.model.GlideUrl GlideUrl} loads this needs to return - * {@link com.bumptech.glide.load.model.GlideUrl#toStringUrl toStringUrl}. For custom models do the same as your - * {@link com.bumptech.glide.load.model.stream.BaseGlideUrlLoader BaseGlideUrlLoader} does. - * @param model return the representation of the given model, DO NOT use {@link #getModel()} inside this method. - * @return a stable Url representation of the model, otherwise the progress reporting won't work - */ - protected String toUrlString(T model) { - return String.valueOf(model); - } - - @Override public float getGranualityPercentage() { - return 1.0f; - } - - @Override public void onProgress(long bytesRead, long expectedLength) { - if (ignoreProgress) { - return; - } - if (expectedLength == Long.MAX_VALUE) { - onConnecting(); - } else if (bytesRead == expectedLength) { - onDownloaded(); - } else { - onDownloading(bytesRead, expectedLength); - } - } - - /** - * Called when the Glide load has started. - * At this time it is not known if the Glide will even go and use the network to fetch the image. - */ - protected abstract void onConnecting(); - /** - * Called when there's any progress on the download; not called when loading from cache. - * At this time we know how many bytes have been transferred through the wire. - */ - protected abstract void onDownloading(long bytesRead, long expectedLength); - /** - * Called when the bytes downloaded reach the length reported by the server; not called when loading from cache. - * At this time it is fairly certain, that Glide either finished reading the stream. - * This means that the image was either already decoded or saved the network stream to cache. - * In the latter case there's more work to do: decode the image from cache and transform. - * These cannot be listened to for progress so it's unsure how fast they'll be, best to show indeterminate progress. - */ - protected abstract void onDownloaded(); - /** - * Called when the Glide load has finished either by successfully loading the image or failing to load or cancelled. - * In any case the best is to hide/reset any progress displays. - */ - protected abstract void onDelivered(); - - private void start() { - OkHttpProgressGlideModule.expect(toUrlString(model), this); - ignoreProgress = false; - onProgress(0, Long.MAX_VALUE); - } - private void cleanup() { - ignoreProgress = true; - T model = this.model; // save in case it gets modified - onDelivered(); - OkHttpProgressGlideModule.forget(toUrlString(model)); - this.model = null; - } - - @Override public void onLoadStarted(Drawable placeholder) { - super.onLoadStarted(placeholder); - start(); - } - @Override public void onResourceReady(Z resource, GlideAnimation animation) { - cleanup(); - super.onResourceReady(resource, animation); - } - @Override public void onLoadFailed(Exception e, Drawable errorDrawable) { - cleanup(); - super.onLoadFailed(e, errorDrawable); - } - @Override public void onLoadCleared(Drawable placeholder) { - cleanup(); - super.onLoadCleared(placeholder); - } -} \ No newline at end of file diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/glide/WrappingTarget.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/glide/WrappingTarget.java deleted file mode 100755 index 235acc3c..00000000 --- a/org.fox.ttrss/src/main/java/org/fox/ttrss/glide/WrappingTarget.java +++ /dev/null @@ -1,54 +0,0 @@ -package org.fox.ttrss.glide; - -import android.graphics.drawable.Drawable; - -import androidx.annotation.NonNull; - -import com.bumptech.glide.request.Request; -import com.bumptech.glide.request.animation.GlideAnimation; -import com.bumptech.glide.request.target.SizeReadyCallback; -import com.bumptech.glide.request.target.Target; - -public class WrappingTarget implements Target { - protected final @NonNull Target target; - public WrappingTarget(@NonNull Target target) { - this.target = target; - } - public @NonNull Target getWrappedTarget() { - return target; - } - @Override public void getSize(SizeReadyCallback cb) { - target.getSize(cb); - } - - @Override public void onLoadStarted(Drawable placeholder) { - target.onLoadStarted(placeholder); - } - @Override public void onLoadFailed(Exception e, Drawable errorDrawable) { - target.onLoadFailed(e, errorDrawable); - } - @SuppressWarnings("unchecked") - @Override public void onResourceReady(Z resource, GlideAnimation glideAnimation) { - target.onResourceReady(resource, (GlideAnimation)glideAnimation); - } - @Override public void onLoadCleared(Drawable placeholder) { - target.onLoadCleared(placeholder); - } - - @Override public Request getRequest() { - return target.getRequest(); - } - @Override public void setRequest(Request request) { - target.setRequest(request); - } - - @Override public void onStart() { - target.onStart(); - } - @Override public void onStop() { - target.onStop(); - } - @Override public void onDestroy() { - target.onDestroy(); - } -} \ No newline at end of file diff --git a/org.fox.ttrss/src/main/res/layout/headlines_row.xml b/org.fox.ttrss/src/main/res/layout/headlines_row.xml index 3c0ecb20..d77ad6cc 100755 --- a/org.fox.ttrss/src/main/res/layout/headlines_row.xml +++ b/org.fox.ttrss/src/main/res/layout/headlines_row.xml @@ -123,7 +123,7 @@ android:adjustViewBounds="true" android:background="@android:color/transparent" android:cropToPadding="true" - android:scaleType="centerCrop" + android:scaleType="fitCenter" tools:src="@drawable/ic_launcher_background" android:visibility="visible" /> diff --git a/org.fox.ttrss/src/main/res/layout/headlines_row_unread.xml b/org.fox.ttrss/src/main/res/layout/headlines_row_unread.xml index 7cae0fb1..7e5f7b48 100755 --- a/org.fox.ttrss/src/main/res/layout/headlines_row_unread.xml +++ b/org.fox.ttrss/src/main/res/layout/headlines_row_unread.xml @@ -124,7 +124,7 @@ android:adjustViewBounds="true" android:background="@android:color/transparent" android:cropToPadding="true" - android:scaleType="centerCrop" + android:scaleType="fitCenter" tools:src="@drawable/ic_launcher_background" android:visibility="visible" /> -- cgit v1.2.3-54-g00ecf From 330ee25295c7c4de60b675e922a03cd3732e745a Mon Sep 17 00:00:00 2001 From: Andrew Dolgov Date: Sat, 17 May 2025 18:08:17 +0300 Subject: add fadeouts --- org.fox.ttrss/build.gradle | 1 + org.fox.ttrss/src/main/java/org/fox/ttrss/HeadlinesFragment.java | 3 +++ 2 files changed, 4 insertions(+) diff --git a/org.fox.ttrss/build.gradle b/org.fox.ttrss/build.gradle index 6712a1c3..055dd2ad 100755 --- a/org.fox.ttrss/build.gradle +++ b/org.fox.ttrss/build.gradle @@ -139,6 +139,7 @@ dependencies { implementation 'org.jsoup:jsoup:1.11.3' implementation 'com.bogdwellers:pinchtozoom:0.1' implementation 'com.github.bumptech.glide:glide:4.11.0' + implementation 'com.github.bumptech.glide:okhttp3-integration:4.11.0' implementation 'jp.wasabeef:glide-transformations:4.3.0' implementation 'androidx.recyclerview:recyclerview:1.4.0' implementation 'androidx.activity:activity:1.10.1' 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 a1ee92c7..96cd245c 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 @@ -64,6 +64,7 @@ import com.bumptech.glide.Glide; import com.bumptech.glide.load.DataSource; import com.bumptech.glide.load.engine.DiskCacheStrategy; import com.bumptech.glide.load.engine.GlideException; +import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions; import com.bumptech.glide.request.RequestListener; import com.bumptech.glide.request.target.DrawableImageViewTarget; import com.bumptech.glide.request.target.Target; @@ -1089,6 +1090,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { Glide.with(m_activity) .load(article.flavorImageUri) + .transition(DrawableTransitionOptions.withCrossFade()) .diskCacheStrategy(DiskCacheStrategy.ALL) .skipMemoryCache(false) .listener(new RequestListener() { @@ -1339,6 +1341,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { } else { Glide.with(m_activity) .load(article.flavorImageUri) + .transition(DrawableTransitionOptions.withCrossFade()) .placeholder(textDrawable) .thumbnail(0.5f) .transform(new CropCircleTransformation()) -- cgit v1.2.3-54-g00ecf From d87c2072677c3c265d6e4a30862cd717a9a2cf5f Mon Sep 17 00:00:00 2001 From: Andrew Dolgov Date: Sat, 17 May 2025 18:12:52 +0300 Subject: import okhttp progress interceptor properly --- .../src/main/java/org/fox/ttrss/ApiCommon.java | 74 +++++++++++++++++++++- 1 file changed, 71 insertions(+), 3 deletions(-) diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/ApiCommon.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/ApiCommon.java index 49dca6dd..e472a6b0 100644 --- a/org.fox.ttrss/src/main/java/org/fox/ttrss/ApiCommon.java +++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/ApiCommon.java @@ -24,11 +24,18 @@ import java.util.concurrent.TimeUnit; import okhttp3.Credentials; import okhttp3.HttpUrl; +import okhttp3.Interceptor; import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; +import okhttp3.ResponseBody; +import okio.Buffer; +import okio.BufferedSource; +import okio.ForwardingSource; +import okio.Okio; +import okio.Source; public class ApiCommon { public static final String TAG = "ApiCommon"; @@ -143,7 +150,7 @@ public class ApiCommon { Request request = requestBuilder.build(); - /* OkHttpProgressGlideModule.ResponseProgressListener listener = new OkHttpProgressGlideModule.ResponseProgressListener() { + ResponseProgressListener listener = new ResponseProgressListener() { @Override public void update(HttpUrl url, long bytesRead, long contentLength) { // Log.d(TAG, "[progress] " + url + " " + bytesRead + " of " + contentLength); @@ -151,7 +158,7 @@ public class ApiCommon { if (contentLength > 0) caller.notifyProgress((int) (bytesRead * 100f / contentLength)); } - }; */ + }; /* lets shamelessly hijack OkHttpProgressGlideModule */ @@ -159,7 +166,7 @@ public class ApiCommon { .connectTimeout(10, TimeUnit.SECONDS) .writeTimeout(10, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS) -// .addNetworkInterceptor(createInterceptor(listener)) + .addNetworkInterceptor(createInterceptor(listener)) .build(); Response response = client.newCall(request).execute(); @@ -263,6 +270,67 @@ public class ApiCommon { return null; } + private interface ResponseProgressListener { + void update(HttpUrl url, long bytesRead, long contentLength); + } + + private static Interceptor createInterceptor(final ResponseProgressListener listener) { + return chain -> { + Request request = chain.request(); + Response response = chain.proceed(request); + return response.newBuilder() + .body(new ProgressResponseBody(request.url(), response.body(), listener)) + .build(); + }; + } + + private static class ProgressResponseBody extends ResponseBody { + private final HttpUrl url; + private final ResponseBody responseBody; + private final ResponseProgressListener progressListener; + private BufferedSource bufferedSource; + + public ProgressResponseBody(HttpUrl url, ResponseBody responseBody, + ResponseProgressListener progressListener) { + + this.url = url; + this.responseBody = responseBody; + this.progressListener = progressListener; + } + + @Override public MediaType contentType() { + return responseBody.contentType(); + } + + @Override public long contentLength() { + return responseBody.contentLength(); + } + + @Override public BufferedSource source() { + if (bufferedSource == null) { + bufferedSource = Okio.buffer(source(responseBody.source())); + } + return bufferedSource; + } + + private Source source(Source source) { + return new ForwardingSource(source) { + long totalBytesRead = 0L; + @Override public long read(Buffer sink, long byteCount) throws IOException { + long bytesRead = super.read(sink, byteCount); + long fullLength = responseBody.contentLength(); + if (bytesRead == -1) { // this source is exhausted + totalBytesRead = fullLength; + } else { + totalBytesRead += bytesRead; + } + progressListener.update(url, totalBytesRead, fullLength); + return bytesRead; + } + }; + } + } + private static String getUserAgent(Context context) { try { PackageInfo packageInfo = context.getPackageManager(). -- cgit v1.2.3-54-g00ecf From ef97a4d716a14519a75accbe8e548afed7e86f8e Mon Sep 17 00:00:00 2001 From: Andrew Dolgov Date: Sat, 17 May 2025 23:31:47 +0300 Subject: adapt progress loading stuff to glide v4 --- org.fox.ttrss/build.gradle | 6 +- .../main/java/org/fox/ttrss/GalleryActivity.java | 2 +- .../java/org/fox/ttrss/GalleryImageFragment.java | 6 +- .../src/main/java/org/fox/ttrss/GalleryModel.java | 12 ++ .../java/org/fox/ttrss/GalleryVideoFragment.java | 2 +- .../main/java/org/fox/ttrss/HeadlinesFragment.java | 107 +++++++------ .../fox/ttrss/glide/OkHttpProgressGlideModule.java | 172 +++++++++++++++++++++ .../java/org/fox/ttrss/glide/ProgressTarget.java | 115 ++++++++++++++ .../java/org/fox/ttrss/glide/WrappingTarget.java | 63 ++++++++ 9 files changed, 426 insertions(+), 59 deletions(-) create mode 100644 org.fox.ttrss/src/main/java/org/fox/ttrss/glide/OkHttpProgressGlideModule.java create mode 100644 org.fox.ttrss/src/main/java/org/fox/ttrss/glide/ProgressTarget.java create mode 100755 org.fox.ttrss/src/main/java/org/fox/ttrss/glide/WrappingTarget.java diff --git a/org.fox.ttrss/build.gradle b/org.fox.ttrss/build.gradle index 055dd2ad..03be018f 100755 --- a/org.fox.ttrss/build.gradle +++ b/org.fox.ttrss/build.gradle @@ -138,9 +138,9 @@ dependencies { implementation 'com.squareup.okhttp3:okhttp:3.12.5' implementation 'org.jsoup:jsoup:1.11.3' implementation 'com.bogdwellers:pinchtozoom:0.1' - implementation 'com.github.bumptech.glide:glide:4.11.0' - implementation 'com.github.bumptech.glide:okhttp3-integration:4.11.0' - implementation 'jp.wasabeef:glide-transformations:4.3.0' + implementation 'com.github.bumptech.glide:glide:4.14.2' + implementation 'com.github.bumptech.glide:okhttp3-integration:4.14.2' + annotationProcessor 'com.github.bumptech.glide:compiler:4.14.2' implementation 'androidx.recyclerview:recyclerview:1.4.0' implementation 'androidx.activity:activity:1.10.1' implementation 'androidx.legacy:legacy-support-v4:1.0.0' diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryActivity.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryActivity.java index eaa997f4..ced956c2 100755 --- a/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryActivity.java +++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryActivity.java @@ -131,7 +131,7 @@ public class GalleryActivity extends CommonActivity { Log.d(TAG, "observed items to check=" + itemsToCheck); m_checkProgress.setMax(itemsToCheck); - m_checkProgress.setVisibility(View.VISIBLE); + m_checkProgress.setVisibility(itemsToCheck > 0 ? View.VISIBLE : View.GONE); m_checkProgress.setProgress(0); }); diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryImageFragment.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryImageFragment.java index d5206518..db141427 100755 --- a/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryImageFragment.java +++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryImageFragment.java @@ -61,7 +61,7 @@ public class GalleryImageFragment extends GalleryBaseFragment { // final GlideDrawableImageViewTarget glideImage = new GlideDrawableImageViewTarget(imgView); - Glide.with(m_activity) + Glide.with(this) .load(m_url) .diskCacheStrategy(DiskCacheStrategy.ALL) .skipMemoryCache(false) @@ -71,7 +71,7 @@ public class GalleryImageFragment extends GalleryBaseFragment { progressBar.setVisibility(View.GONE); errorMessage.setVisibility(View.VISIBLE); - ActivityCompat.startPostponedEnterTransition(m_activity); + // ActivityCompat.startPostponedEnterTransition(m_activity); return false; } @@ -81,7 +81,7 @@ public class GalleryImageFragment extends GalleryBaseFragment { progressBar.setVisibility(View.GONE); errorMessage.setVisibility(View.GONE); - ActivityCompat.startPostponedEnterTransition(m_activity); + // ActivityCompat.startPostponedEnterTransition(m_activity); return false; } diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryModel.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryModel.java index 8458fc33..2f0451fb 100644 --- a/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryModel.java +++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryModel.java @@ -79,6 +79,7 @@ public class GalleryModel extends AndroidViewModel { m_itemsToCheck.postValue(elems.size()); int currentItem = 0; + boolean firstFound = false; for (Element elem : elems) { ++currentItem; @@ -95,6 +96,8 @@ public class GalleryModel extends AndroidViewModel { if (src != null && src.equals(srcFirst)) { Log.d(TAG, "first item found, vid=" + src); + firstFound = true; + GalleryEntry item = new GalleryEntry(src, GalleryEntry.GalleryEntryType.TYPE_VIDEO, poster); checkList.add(item); @@ -115,6 +118,8 @@ public class GalleryModel extends AndroidViewModel { if (src != null && src.equals(srcFirst)) { Log.d(TAG, "first item found, img=" + src); + firstFound = true; + GalleryEntry item = new GalleryEntry(src, GalleryEntry.GalleryEntryType.TYPE_IMAGE, null); checkList.add(item); @@ -148,6 +153,13 @@ public class GalleryModel extends AndroidViewModel { m_checkProgress.postValue(currentItem); } + + // if we didn't find it in the document, let's add insert to the list anyway so shared transition + // would hopefully work + if (!firstFound) { + checkList.add(0, new GalleryEntry(srcFirst, GalleryEntry.GalleryEntryType.TYPE_IMAGE, null)); + m_items.postValue(checkList); + } }); } } diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryVideoFragment.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryVideoFragment.java index 18d08e4f..a171fb5b 100755 --- a/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryVideoFragment.java +++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryVideoFragment.java @@ -56,7 +56,7 @@ public class GalleryVideoFragment extends GalleryBaseFragment { registerForContextMenu(imgView); - ActivityCompat.startPostponedEnterTransition(m_activity); + // ActivityCompat.startPostponedEnterTransition(m_activity); view.findViewById(R.id.flavor_image_progress).setVisibility(View.VISIBLE); 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 96cd245c..d5da9d6e 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 @@ -64,14 +64,17 @@ import com.bumptech.glide.Glide; import com.bumptech.glide.load.DataSource; import com.bumptech.glide.load.engine.DiskCacheStrategy; import com.bumptech.glide.load.engine.GlideException; +import com.bumptech.glide.load.resource.bitmap.DownsampleStrategy; import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions; import com.bumptech.glide.request.RequestListener; +import com.bumptech.glide.request.RequestOptions; import com.bumptech.glide.request.target.DrawableImageViewTarget; import com.bumptech.glide.request.target.Target; import com.google.android.material.button.MaterialButton; import com.google.android.material.dialog.MaterialAlertDialogBuilder; 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; @@ -87,8 +90,6 @@ import java.util.HashMap; import java.util.TimeZone; import java.util.stream.Collectors; -import jp.wasabeef.glide.transformations.CropCircleTransformation; - public class HeadlinesFragment extends androidx.fragment.app.Fragment { private boolean m_isLazyLoading; @@ -617,7 +618,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { public View flavorImageOverflow; public TextureView flavorVideoView; public MaterialButton attachmentsView; - //public ProgressTarget flavorProgressTarget; + public ProgressTarget flavorProgressTarget; int articleId; public TextView linkHost; @@ -659,9 +660,10 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { attachmentsView = v.findViewById(R.id.attachments); linkHost = v.findViewById(R.id.link_host); - /* if (flavorImageView != null && flavorImageLoadingBar != null) { - flavorProgressTarget = new FlavorProgressTarget<>(new GlideDrawableImageViewTarget(flavorImageView), flavorImageLoadingBar); - } */ + if (flavorImageView != null && flavorImageLoadingBar != null) { + flavorProgressTarget = new FlavorProgressTarget<>(new DrawableImageViewTarget(flavorImageView), + flavorImageLoadingBar); + } } public void clearAnimation() { @@ -669,7 +671,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { } } - /* private static class FlavorProgressTarget extends ProgressTarget { + private static class FlavorProgressTarget extends ProgressTarget { private final ProgressBar progress; public FlavorProgressTarget(Target target, ProgressBar progress) { super(target); @@ -694,7 +696,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { @Override protected void onDelivered() { progress.setVisibility(View.INVISIBLE); } - } */ + } private class ArticleListAdapter extends ListAdapter { public static final int VIEW_NORMAL = 0; @@ -711,8 +713,8 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { private final TextDrawable.IBuilder m_drawableBuilder = TextDrawable.builder().round(); boolean flavorImageEnabled; + private final int m_screenWidth; private final int m_screenHeight; - private int m_lastAddedPosition; private final ConnectivityManager m_cmgr; @@ -740,6 +742,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { Point size = new Point(); display.getSize(size); m_screenHeight = size.y; + m_screenWidth = size.x; String headlineMode = m_prefs.getString("headline_mode", "HL_DEFAULT"); flavorImageEnabled = "HL_DEFAULT".equals(headlineMode) || "HL_COMPACT".equals(headlineMode); @@ -1015,7 +1018,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { holder.flavorVideoView.setVisibility(View.GONE); holder.flavorImageHolder.setVisibility(View.GONE); - Glide.with(m_activity).clear(holder.flavorImageView); + Glide.with(HeadlinesFragment.this).clear(holder.flavorImageView); // this is needed if our flavor image goes behind base listview element holder.headlineHeader.setOnClickListener(v -> { @@ -1069,66 +1072,66 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { } holder.flavorImageView.setVisibility(View.VISIBLE); - holder.flavorImageView.setMaxHeight((int)(m_screenHeight * 0.6f)); + + int maxImageSize = (int) (m_screenHeight * 0.5f); + + // we also downsample below using glide to save RAM + // holder.flavorImageView.setMaxHeight(maxImageSize); // only show holder if we're about to display a picture holder.flavorImageHolder.setVisibility(View.VISIBLE); // prevent lower listiew entries from jumping around if this row is modified - if (m_flavorHeightsCache.containsKey(article.id)) { + /* if (m_flavorHeightsCache.containsKey(article.id)) { int cachedHeight = m_flavorHeightsCache.get(article.id); if (cachedHeight > 0) { FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) holder.flavorImageView.getLayoutParams(); lp.height = cachedHeight; } - } + } */ - // holder.flavorProgressTarget.setModel(article.flavorImageUri); - - try { + holder.flavorProgressTarget.setModel(article.flavorImageUri); - Glide.with(m_activity) - .load(article.flavorImageUri) - .transition(DrawableTransitionOptions.withCrossFade()) - .diskCacheStrategy(DiskCacheStrategy.ALL) - .skipMemoryCache(false) - .listener(new RequestListener() { - @Override - public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) { + Glide.with(HeadlinesFragment.this) + .load(article.flavorImageUri) + .transition(DrawableTransitionOptions.withCrossFade()) + .diskCacheStrategy(DiskCacheStrategy.ALL) + .skipMemoryCache(false) + .listener(new RequestListener() { + @Override + public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) { - holder.flavorImageLoadingBar.setVisibility(View.GONE); - holder.flavorImageView.setVisibility(View.GONE); + holder.flavorImageLoadingBar.setVisibility(View.GONE); + holder.flavorImageView.setVisibility(View.GONE); - return false; - } + return false; + } - @Override - public boolean onResourceReady(Drawable resource, Object model, Target target, DataSource dataSource, boolean isFirstResource) { + @Override + public boolean onResourceReady(Drawable resource, Object model, Target target, DataSource dataSource, boolean isFirstResource) { - holder.flavorImageLoadingBar.setVisibility(View.GONE); + holder.flavorImageLoadingBar.setVisibility(View.GONE); - if (resource.getIntrinsicWidth() > FLAVOR_IMG_MIN_SIZE && resource.getIntrinsicHeight() > FLAVOR_IMG_MIN_SIZE) { + if (resource.getIntrinsicWidth() > FLAVOR_IMG_MIN_SIZE && resource.getIntrinsicHeight() > FLAVOR_IMG_MIN_SIZE) { - holder.flavorImageView.setVisibility(View.VISIBLE); - holder.flavorImageOverflow.setVisibility(View.VISIBLE); + holder.flavorImageView.setVisibility(View.VISIBLE); + holder.flavorImageOverflow.setVisibility(View.VISIBLE); - adjustVideoKindView(holder, article); + adjustVideoKindView(holder, article); - return false; - } else { + return false; + } else { - holder.flavorImageOverflow.setVisibility(View.GONE); - holder.flavorImageView.setVisibility(View.GONE); + holder.flavorImageOverflow.setVisibility(View.GONE); + holder.flavorImageView.setVisibility(View.GONE); - return true; - } + return true; } - }) - .into(new DrawableImageViewTarget(holder.flavorImageView)); - } catch (OutOfMemoryError e) { - e.printStackTrace(); - } + } + }) + .into(holder.flavorProgressTarget); + } if (m_prefs.getBoolean("inline_video_player", false) && article.flavorImage != null && @@ -1339,12 +1342,12 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { holder.textImage.setImageDrawable(textDrawable); } else { - Glide.with(m_activity) + Glide.with(HeadlinesFragment.this) .load(article.flavorImageUri) .transition(DrawableTransitionOptions.withCrossFade()) .placeholder(textDrawable) .thumbnail(0.5f) - .transform(new CropCircleTransformation()) + .apply(RequestOptions.circleCropTransform()) .diskCacheStrategy(DiskCacheStrategy.ALL) .skipMemoryCache(false) .listener(new RequestListener() { @@ -1400,12 +1403,14 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { intent.putExtra("content", tempContent); - ActivityOptionsCompat options = + /* ActivityOptionsCompat options = ActivityOptionsCompat.makeSceneTransitionAnimation(m_activity, transitionView != null ? transitionView : holder.flavorImageView, - "gallery:" + (article.flavorStreamUri != null ? article.flavorStreamUri : article.flavorImageUri)); + "gallery:" + (article.flavorStreamUri != null ? article.flavorStreamUri : article.flavorImageUri)); */ + + // ActivityCompat.startActivity(m_activity, intent, options.toBundle()) - ActivityCompat.startActivity(m_activity, intent, options.toBundle()); + startActivity(intent); } } diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/glide/OkHttpProgressGlideModule.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/glide/OkHttpProgressGlideModule.java new file mode 100644 index 00000000..561f9a86 --- /dev/null +++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/glide/OkHttpProgressGlideModule.java @@ -0,0 +1,172 @@ +package org.fox.ttrss.glide; + +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.util.Log; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.GlideBuilder; +import com.bumptech.glide.Registry; +import com.bumptech.glide.annotation.GlideModule; +import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader; +import com.bumptech.glide.load.model.GlideUrl; +import com.bumptech.glide.module.AppGlideModule; + +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; + +import okhttp3.HttpUrl; +import okhttp3.Interceptor; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.ResponseBody; +import okio.Buffer; +import okio.BufferedSource; +import okio.ForwardingSource; +import okio.Okio; +import okio.Source; + +@GlideModule +public class OkHttpProgressGlideModule extends AppGlideModule { + @Override public void registerComponents(Context context, Glide glide, Registry registry) { + OkHttpClient client = new OkHttpClient.Builder() + .addNetworkInterceptor(createInterceptor(new DispatchingProgressListener())) + .build(); + + // registry.append() doesn't work... + registry.prepend(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory(client)); + } + + public static Interceptor createInterceptor(final ResponseProgressListener listener) { + return chain -> { + Request request = chain.request(); + Response response = chain.proceed(request); + + return response.newBuilder() + .body(new OkHttpProgressResponseBody(request.url(), response.body(), listener)) + .build(); + }; + } + + public interface UIProgressListener { + void onProgress(long bytesRead, long expectedLength); + /** + * Control how often the listener needs an update. 0% and 100% will always be dispatched. + * @return in percentage (0.2 = call {@link #onProgress} around every 0.2 percent of progress) + */ + float getGranualityPercentage(); + } + + public static void forget(String url) { + DispatchingProgressListener.forget(url); + } + public static void expect(String url, UIProgressListener listener) { + DispatchingProgressListener.expect(url, listener); + } + + public interface ResponseProgressListener { + void update(HttpUrl url, long bytesRead, long contentLength); + } + + private static class DispatchingProgressListener implements ResponseProgressListener { + private static final Map LISTENERS = new HashMap<>(); + private static final Map PROGRESSES = new HashMap<>(); + + private final Handler handler; + DispatchingProgressListener() { + this.handler = new Handler(Looper.getMainLooper()); + } + + static void forget(String url) { + LISTENERS.remove(url); + PROGRESSES.remove(url); + } + static void expect(String url, UIProgressListener listener) { + LISTENERS.put(url, listener); + } + + @Override public void update(HttpUrl url, final long bytesRead, final long contentLength) { + //System.out.printf("%s: %d/%d = %.2f%%%n", url, bytesRead, contentLength, (100f * bytesRead) / contentLength); + + String key = url.toString(); + final UIProgressListener listener = LISTENERS.get(key); + + if (listener == null) { + return; + } + if (contentLength <= bytesRead) { + forget(key); + } + if (needsDispatch(key, bytesRead, contentLength, listener.getGranualityPercentage())) { + handler.post(() -> listener.onProgress(bytesRead, contentLength)); + } + } + + private boolean needsDispatch(String key, long current, long total, float granularity) { + if (granularity == 0 || current == 0 || total == current) { + return true; + } + float percent = 100f * current / total; + long currentProgress = (long)(percent / granularity); + Long lastProgress = PROGRESSES.get(key); + if (lastProgress == null || currentProgress != lastProgress) { + PROGRESSES.put(key, currentProgress); + return true; + } else { + return false; + } + } + } + + public static class OkHttpProgressResponseBody extends ResponseBody { + private final HttpUrl url; + private final ResponseBody responseBody; + private final ResponseProgressListener progressListener; + private BufferedSource bufferedSource; + + public OkHttpProgressResponseBody(HttpUrl url, ResponseBody responseBody, + ResponseProgressListener progressListener) { + + this.url = url; + this.responseBody = responseBody; + this.progressListener = progressListener; + } + + @Override public MediaType contentType() { + return responseBody.contentType(); + } + + @Override public long contentLength() { + return responseBody.contentLength(); + } + + @Override public BufferedSource source() { + if (bufferedSource == null) { + bufferedSource = Okio.buffer(source(responseBody.source())); + } + return bufferedSource; + } + + private Source source(Source source) { + return new ForwardingSource(source) { + long totalBytesRead = 0L; + @Override public long read(Buffer sink, long byteCount) throws IOException { + long bytesRead = super.read(sink, byteCount); + long fullLength = responseBody.contentLength(); + if (bytesRead == -1) { // this source is exhausted + totalBytesRead = fullLength; + } else { + totalBytesRead += bytesRead; + } + progressListener.update(url, totalBytesRead, fullLength); + return bytesRead; + } + }; + } + } +} \ No newline at end of file diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/glide/ProgressTarget.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/glide/ProgressTarget.java new file mode 100644 index 00000000..172dd28a --- /dev/null +++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/glide/ProgressTarget.java @@ -0,0 +1,115 @@ +package org.fox.ttrss.glide; + + +import android.graphics.drawable.Drawable; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.engine.Resource; +import com.bumptech.glide.request.target.Target; +import com.bumptech.glide.request.transition.Transition; + +public abstract class ProgressTarget extends WrappingTarget implements OkHttpProgressGlideModule.UIProgressListener { + private T model; + private boolean ignoreProgress = true; + public ProgressTarget(Target target) { + this(null, target); + } + public ProgressTarget(T model, Target target) { + super(target); + this.model = model; + } + + public final T getModel() { + return model; + } + public final void setModel(T model) { + this.model = model; + } + /** + * Convert a model into an Url string that is used to match up the OkHttp requests. For explicit + * {@link com.bumptech.glide.load.model.GlideUrl GlideUrl} loads this needs to return + * {@link com.bumptech.glide.load.model.GlideUrl#toStringUrl toStringUrl}. For custom models do the same as your + * {@link com.bumptech.glide.load.model.stream.BaseGlideUrlLoader BaseGlideUrlLoader} does. + * @param model return the representation of the given model, DO NOT use {@link #getModel()} inside this method. + * @return a stable Url representation of the model, otherwise the progress reporting won't work + */ + protected String toUrlString(T model) { + return String.valueOf(model); + } + + @Override public float getGranualityPercentage() { + return 1.0f; + } + + @Override public void onProgress(long bytesRead, long expectedLength) { + if (ignoreProgress) { + return; + } + if (expectedLength == Long.MAX_VALUE) { + onConnecting(); + } else if (bytesRead == expectedLength) { + onDownloaded(); + } else { + onDownloading(bytesRead, expectedLength); + } + } + + /** + * Called when the Glide load has started. + * At this time it is not known if the Glide will even go and use the network to fetch the image. + */ + protected abstract void onConnecting(); + /** + * Called when there's any progress on the download; not called when loading from cache. + * At this time we know how many bytes have been transferred through the wire. + */ + protected abstract void onDownloading(long bytesRead, long expectedLength); + /** + * Called when the bytes downloaded reach the length reported by the server; not called when loading from cache. + * At this time it is fairly certain, that Glide either finished reading the stream. + * This means that the image was either already decoded or saved the network stream to cache. + * In the latter case there's more work to do: decode the image from cache and transform. + * These cannot be listened to for progress so it's unsure how fast they'll be, best to show indeterminate progress. + */ + protected abstract void onDownloaded(); + /** + * Called when the Glide load has finished either by successfully loading the image or failing to load or cancelled. + * In any case the best is to hide/reset any progress displays. + */ + protected abstract void onDelivered(); + + private void start() { + OkHttpProgressGlideModule.expect(toUrlString(model), this); + ignoreProgress = false; + onProgress(0, Long.MAX_VALUE); + } + private void cleanup() { + ignoreProgress = true; + T model = this.model; // save in case it gets modified + onDelivered(); + OkHttpProgressGlideModule.forget(toUrlString(model)); + this.model = null; + } + + @Override public void onLoadStarted(Drawable placeholder) { + super.onLoadStarted(placeholder); + start(); + } + /** @noinspection unchecked*/ + public void onResourceReady(@NonNull Z resource, @Nullable Transition transition) { + cleanup(); + super.onResourceReady(resource, (Transition)transition); + } + @Override public void onLoadFailed(Drawable errorDrawable) { + cleanup(); + super.onLoadFailed(errorDrawable); + } + @Override public void onLoadCleared(Drawable placeholder) { + cleanup(); + super.onLoadCleared(placeholder); + } +} \ No newline at end of file diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/glide/WrappingTarget.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/glide/WrappingTarget.java new file mode 100755 index 00000000..314800d9 --- /dev/null +++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/glide/WrappingTarget.java @@ -0,0 +1,63 @@ +package org.fox.ttrss.glide; + +import android.graphics.drawable.Drawable; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.bumptech.glide.request.Request; +import com.bumptech.glide.request.target.SizeReadyCallback; +import com.bumptech.glide.request.target.Target; +import com.bumptech.glide.request.transition.Transition; + +public class WrappingTarget implements Target { + protected final @NonNull Target target; + public WrappingTarget(@NonNull Target target) { + this.target = target; + } + public @NonNull Target getWrappedTarget() { + return target; + } + @Override public void getSize(SizeReadyCallback cb) { + target.getSize(cb); + } + + @Override + public void removeCallback(@NonNull SizeReadyCallback cb) { + + } + + @Override public void onLoadStarted(Drawable placeholder) { + target.onLoadStarted(placeholder); + } + @Override public void onLoadFailed(Drawable errorDrawable) { + target.onLoadFailed(errorDrawable); + } + + /** @noinspection unchecked*/ + @Override + public void onResourceReady(@NonNull Z resource, @Nullable Transition transition) { + target.onResourceReady(resource, (Transition)transition); + } + + @Override public void onLoadCleared(Drawable placeholder) { + target.onLoadCleared(placeholder); + } + + @Override public Request getRequest() { + return target.getRequest(); + } + @Override public void setRequest(Request request) { + target.setRequest(request); + } + + @Override public void onStart() { + target.onStart(); + } + @Override public void onStop() { + target.onStop(); + } + @Override public void onDestroy() { + target.onDestroy(); + } +} \ No newline at end of file -- cgit v1.2.3-54-g00ecf From ed6b40113fcfed13aebb91d36475634182a5742d Mon Sep 17 00:00:00 2001 From: Andrew Dolgov Date: Sun, 18 May 2025 11:13:18 +0300 Subject: rework image dimensions checking using two step cached glide request --- .../main/java/org/fox/ttrss/GalleryActivity.java | 2 +- .../java/org/fox/ttrss/GalleryImageFragment.java | 4 +- .../java/org/fox/ttrss/GalleryVideoFragment.java | 2 +- .../main/java/org/fox/ttrss/HeadlinesFragment.java | 146 +++++++++++---------- .../org/fox/ttrss/glide/BitmapSizeDecoder.java | 30 +++++ .../fox/ttrss/glide/OkHttpProgressGlideModule.java | 7 + .../ttrss/glide/OptionsSizeResourceTranscoder.java | 20 +++ .../src/main/res/layout/headlines_row.xml | 11 +- .../src/main/res/layout/headlines_row_unread.xml | 12 +- 9 files changed, 149 insertions(+), 85 deletions(-) create mode 100644 org.fox.ttrss/src/main/java/org/fox/ttrss/glide/BitmapSizeDecoder.java create mode 100644 org.fox.ttrss/src/main/java/org/fox/ttrss/glide/OptionsSizeResourceTranscoder.java diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryActivity.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryActivity.java index ced956c2..b0ada038 100755 --- a/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryActivity.java +++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryActivity.java @@ -95,7 +95,7 @@ public class GalleryActivity extends CommonActivity { @Override public void onCreate(Bundle savedInstanceState) { - // ActivityCompat.postponeEnterTransition(this); + ActivityCompat.postponeEnterTransition(this); // we use that before parent onCreate so let's init locally m_prefs = PreferenceManager diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryImageFragment.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryImageFragment.java index db141427..b249b889 100755 --- a/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryImageFragment.java +++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryImageFragment.java @@ -71,7 +71,7 @@ public class GalleryImageFragment extends GalleryBaseFragment { progressBar.setVisibility(View.GONE); errorMessage.setVisibility(View.VISIBLE); - // ActivityCompat.startPostponedEnterTransition(m_activity); + ActivityCompat.startPostponedEnterTransition(m_activity); return false; } @@ -81,7 +81,7 @@ public class GalleryImageFragment extends GalleryBaseFragment { progressBar.setVisibility(View.GONE); errorMessage.setVisibility(View.GONE); - // ActivityCompat.startPostponedEnterTransition(m_activity); + ActivityCompat.startPostponedEnterTransition(m_activity); return false; } diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryVideoFragment.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryVideoFragment.java index a171fb5b..18d08e4f 100755 --- a/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryVideoFragment.java +++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryVideoFragment.java @@ -56,7 +56,7 @@ public class GalleryVideoFragment extends GalleryBaseFragment { registerForContextMenu(imgView); - // ActivityCompat.startPostponedEnterTransition(m_activity); + ActivityCompat.startPostponedEnterTransition(m_activity); view.findViewById(R.id.flavor_image_progress).setVisibility(View.VISIBLE); 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 d5da9d6e..6c3829cc 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 @@ -22,6 +22,7 @@ import android.transition.Fade; import android.transition.Transition; import android.util.DisplayMetrics; import android.util.Log; +import android.util.Size; import android.util.TypedValue; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; @@ -45,6 +46,7 @@ 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; @@ -64,11 +66,11 @@ import com.bumptech.glide.Glide; import com.bumptech.glide.load.DataSource; import com.bumptech.glide.load.engine.DiskCacheStrategy; import com.bumptech.glide.load.engine.GlideException; -import com.bumptech.glide.load.resource.bitmap.DownsampleStrategy; import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions; import com.bumptech.glide.request.RequestListener; import com.bumptech.glide.request.RequestOptions; import com.bumptech.glide.request.target.DrawableImageViewTarget; +import com.bumptech.glide.request.target.SimpleTarget; import com.bumptech.glide.request.target.Target; import com.google.android.material.button.MaterialButton; import com.google.android.material.dialog.MaterialAlertDialogBuilder; @@ -124,7 +126,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { private MediaPlayer m_mediaPlayer; private TextureView m_activeTexture; - protected static HashMap m_flavorHeightsCache = new HashMap<>(); + protected static HashMap m_flavorMeasuredHeightsCache = new HashMap<>(); public ArticleList getSelectedArticles() { return Application.getArticles() @@ -618,7 +620,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { public View flavorImageOverflow; public TextureView flavorVideoView; public MaterialButton attachmentsView; - public ProgressTarget flavorProgressTarget; + // public ProgressTarget flavorProgressTarget; int articleId; public TextView linkHost; @@ -631,7 +633,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { View flavorImage = view.findViewById(R.id.flavor_image); if (flavorImage != null) { - HeadlinesFragment.m_flavorHeightsCache.put(articleId, flavorImage.getMeasuredHeight()); + HeadlinesFragment.m_flavorMeasuredHeightsCache.put(articleId, flavorImage.getMeasuredHeight()); } return true; @@ -659,11 +661,6 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { flavorVideoView = v.findViewById(R.id.flavor_video); attachmentsView = v.findViewById(R.id.attachments); linkHost = v.findViewById(R.id.link_host); - - if (flavorImageView != null && flavorImageLoadingBar != null) { - flavorProgressTarget = new FlavorProgressTarget<>(new DrawableImageViewTarget(flavorImageView), - flavorImageLoadingBar); - } } public void clearAnimation() { @@ -672,10 +669,11 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { } private static class FlavorProgressTarget extends ProgressTarget { - private final ProgressBar progress; - public FlavorProgressTarget(Target target, ProgressBar progress) { + private final ArticleViewHolder holder; + public FlavorProgressTarget(Target target, String model, ArticleViewHolder holder) { super(target); - this.progress = progress; + setModel(model); + this.holder = holder; } @Override public float getGranualityPercentage() { @@ -683,18 +681,26 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { } @Override protected void onConnecting() { - progress.setIndeterminate(true); - progress.setVisibility(View.VISIBLE); + holder.flavorImageHolder.setVisibility(View.VISIBLE); + + holder.flavorImageLoadingBar.setIndeterminate(true); + holder.flavorImageLoadingBar.setVisibility(View.VISIBLE); } @Override protected void onDownloading(long bytesRead, long expectedLength) { - progress.setIndeterminate(false); - progress.setProgress((int)(100 * bytesRead / expectedLength)); + holder.flavorImageHolder.setVisibility(View.VISIBLE); + + holder.flavorImageLoadingBar.setIndeterminate(false); + holder.flavorImageLoadingBar.setProgress((int)(100 * bytesRead / expectedLength)); } @Override protected void onDownloaded() { - progress.setIndeterminate(true); + holder.flavorImageHolder.setVisibility(View.VISIBLE); + + holder.flavorImageLoadingBar.setIndeterminate(true); } @Override protected void onDelivered() { - progress.setVisibility(View.INVISIBLE); + holder.flavorImageHolder.setVisibility(View.VISIBLE); + + holder.flavorImageLoadingBar.setVisibility(View.INVISIBLE); } } @@ -772,8 +778,6 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { View v = LayoutInflater.from(parent.getContext()).inflate(layoutId, parent, false); - //registerForContextMenu(v); - return new ArticleViewHolder(v); } @@ -997,13 +1001,6 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { } else { holder.excerptView.setVisibility(View.GONE); } - - if (!canShowFlavorImage()) { - holder.excerptView.setPadding(holder.excerptView.getPaddingLeft(), - 0, - holder.excerptView.getPaddingRight(), - holder.excerptView.getPaddingBottom()); - } } } @@ -1071,67 +1068,76 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { }); } - holder.flavorImageView.setVisibility(View.VISIBLE); - int maxImageSize = (int) (m_screenHeight * 0.5f); // we also downsample below using glide to save RAM - // holder.flavorImageView.setMaxHeight(maxImageSize); - - // only show holder if we're about to display a picture - holder.flavorImageHolder.setVisibility(View.VISIBLE); + holder.flavorImageView.setMaxHeight(maxImageSize); // prevent lower listiew entries from jumping around if this row is modified - /* if (m_flavorHeightsCache.containsKey(article.id)) { - int cachedHeight = m_flavorHeightsCache.get(article.id); + if (m_flavorMeasuredHeightsCache.containsKey(article.id)) { + int cachedHeight = m_flavorMeasuredHeightsCache.get(article.id); if (cachedHeight > 0) { FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) holder.flavorImageView.getLayoutParams(); lp.height = cachedHeight; } - } */ - - holder.flavorProgressTarget.setModel(article.flavorImageUri); + } - Glide.with(HeadlinesFragment.this) - .load(article.flavorImageUri) - .transition(DrawableTransitionOptions.withCrossFade()) - .diskCacheStrategy(DiskCacheStrategy.ALL) - .skipMemoryCache(false) - .listener(new RequestListener() { - @Override - public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) { + Log.d(TAG, "checking resource size for " + article.flavorImageUri); - holder.flavorImageLoadingBar.setVisibility(View.GONE); - holder.flavorImageView.setVisibility(View.GONE); + FlavorProgressTarget flavorProgressTarget = new FlavorProgressTarget<>(new SimpleTarget() { + @Override + public void onResourceReady(@NonNull Size resource, @Nullable com.bumptech.glide.request.transition.Transition transition) { + Log.d(TAG, "got resource of " + resource.getWidth() + "x" + resource.getHeight()); - return false; - } + if (resource.getWidth() > FLAVOR_IMG_MIN_SIZE && resource.getHeight() > FLAVOR_IMG_MIN_SIZE) { - @Override - public boolean onResourceReady(Drawable resource, Object model, Target target, DataSource dataSource, boolean isFirstResource) { + // now we can actually load the image into our drawable + Glide.with(HeadlinesFragment.this) + .load(article.flavorImageUri) + .transition(DrawableTransitionOptions.withCrossFade()) + .override(maxImageSize) + .diskCacheStrategy(DiskCacheStrategy.DATA) + .skipMemoryCache(true) + .listener(new RequestListener() { + @Override + public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) { + holder.flavorImageHolder.setVisibility(View.GONE); - holder.flavorImageLoadingBar.setVisibility(View.GONE); + holder.flavorImageView.setVisibility(View.GONE); + holder.flavorImageOverflow.setVisibility(View.VISIBLE); - if (resource.getIntrinsicWidth() > FLAVOR_IMG_MIN_SIZE && resource.getIntrinsicHeight() > FLAVOR_IMG_MIN_SIZE) { + return false; + } - holder.flavorImageView.setVisibility(View.VISIBLE); - holder.flavorImageOverflow.setVisibility(View.VISIBLE); + @Override + public boolean onResourceReady(Drawable resource, Object model, Target target, DataSource dataSource, boolean isFirstResource) { + holder.flavorImageHolder.setVisibility(View.VISIBLE); - adjustVideoKindView(holder, article); + holder.flavorImageView.setVisibility(View.VISIBLE); + holder.flavorImageOverflow.setVisibility(View.VISIBLE); - return false; - } else { + adjustVideoKindView(holder, article); - holder.flavorImageOverflow.setVisibility(View.GONE); - holder.flavorImageView.setVisibility(View.GONE); + return false; + } + }) + .into(new DrawableImageViewTarget(holder.flavorImageView)); + } else { + holder.flavorImageHolder.setVisibility(View.GONE); - return true; - } - } - }) - .into(holder.flavorProgressTarget); + holder.flavorImageView.setVisibility(View.VISIBLE); + holder.flavorImageOverflow.setVisibility(View.VISIBLE); + } + } + }, article.flavorImageUri, holder); + Glide.with(HeadlinesFragment.this) + .as(Size.class) + .load(article.flavorImageUri) + .diskCacheStrategy(DiskCacheStrategy.DATA) + .skipMemoryCache(true) + .into(flavorProgressTarget); } if (m_prefs.getBoolean("inline_video_player", false) && article.flavorImage != null && @@ -1403,14 +1409,14 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { intent.putExtra("content", tempContent); - /* ActivityOptionsCompat options = + ActivityOptionsCompat options = ActivityOptionsCompat.makeSceneTransitionAnimation(m_activity, transitionView != null ? transitionView : holder.flavorImageView, - "gallery:" + (article.flavorStreamUri != null ? article.flavorStreamUri : article.flavorImageUri)); */ + "gallery:" + (article.flavorStreamUri != null ? article.flavorStreamUri : article.flavorImageUri)); - // ActivityCompat.startActivity(m_activity, intent, options.toBundle()) + ActivityCompat.startActivity(m_activity, intent, options.toBundle()); - startActivity(intent); + // startActivity(intent); } } diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/glide/BitmapSizeDecoder.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/glide/BitmapSizeDecoder.java new file mode 100644 index 00000000..1a709073 --- /dev/null +++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/glide/BitmapSizeDecoder.java @@ -0,0 +1,30 @@ +package org.fox.ttrss.glide; + +import java.io.File; +import java.io.IOException; + +import com.bumptech.glide.load.Options; +import com.bumptech.glide.load.ResourceDecoder; +import com.bumptech.glide.load.engine.Resource; +import com.bumptech.glide.load.resource.SimpleResource; + +import android.graphics.BitmapFactory; +import android.util.Log; + +import androidx.annotation.NonNull; + +class BitmapSizeDecoder implements ResourceDecoder { + @Override + public Resource decode(File file, int width, int height, Options options) throws IOException { + BitmapFactory.Options bmOptions = new BitmapFactory.Options(); + bmOptions.inJustDecodeBounds = true; + BitmapFactory.decodeFile(file.getAbsolutePath(), bmOptions); + return new SimpleResource<>(bmOptions); + } + + @Override + public boolean handles(@NonNull File source, @NonNull Options options) throws IOException { + return true; + } +} + diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/glide/OkHttpProgressGlideModule.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/glide/OkHttpProgressGlideModule.java index 561f9a86..44f69b79 100644 --- a/org.fox.ttrss/src/main/java/org/fox/ttrss/glide/OkHttpProgressGlideModule.java +++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/glide/OkHttpProgressGlideModule.java @@ -1,9 +1,11 @@ package org.fox.ttrss.glide; import android.content.Context; +import android.graphics.BitmapFactory; import android.os.Handler; import android.os.Looper; import android.util.Log; +import android.util.Size; import com.bumptech.glide.Glide; import com.bumptech.glide.GlideBuilder; @@ -13,6 +15,7 @@ import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader; import com.bumptech.glide.load.model.GlideUrl; import com.bumptech.glide.module.AppGlideModule; +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; @@ -40,6 +43,9 @@ public class OkHttpProgressGlideModule extends AppGlideModule { // registry.append() doesn't work... registry.prepend(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory(client)); + + registry.prepend(File.class, BitmapFactory.Options.class, new BitmapSizeDecoder()); + registry.register(BitmapFactory.Options.class, Size.class, new OptionsSizeResourceTranscoder()); } public static Interceptor createInterceptor(final ResponseProgressListener listener) { @@ -92,6 +98,7 @@ public class OkHttpProgressGlideModule extends AppGlideModule { @Override public void update(HttpUrl url, final long bytesRead, final long contentLength) { //System.out.printf("%s: %d/%d = %.2f%%%n", url, bytesRead, contentLength, (100f * bytesRead) / contentLength); + //Log.d("resource progress", "url=" + url + " bytesRead="+ bytesRead + " of " + contentLength); String key = url.toString(); final UIProgressListener listener = LISTENERS.get(key); diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/glide/OptionsSizeResourceTranscoder.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/glide/OptionsSizeResourceTranscoder.java new file mode 100644 index 00000000..2fc56fe7 --- /dev/null +++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/glide/OptionsSizeResourceTranscoder.java @@ -0,0 +1,20 @@ +package org.fox.ttrss.glide; + +import android.graphics.BitmapFactory; +import android.util.Log; +import android.util.Size; + +import com.bumptech.glide.load.Options; +import com.bumptech.glide.load.resource.transcode.ResourceTranscoder; +import com.bumptech.glide.load.engine.Resource; +import com.bumptech.glide.load.resource.SimpleResource; + +class OptionsSizeResourceTranscoder implements ResourceTranscoder { + @Override + public Resource transcode(Resource resource, Options options) { + BitmapFactory.Options bmOptions = resource.get(); + Size size = new Size(bmOptions.outWidth, bmOptions.outHeight); + return new SimpleResource<>(size); + } +} + diff --git a/org.fox.ttrss/src/main/res/layout/headlines_row.xml b/org.fox.ttrss/src/main/res/layout/headlines_row.xml index d77ad6cc..5e4eca4e 100755 --- a/org.fox.ttrss/src/main/res/layout/headlines_row.xml +++ b/org.fox.ttrss/src/main/res/layout/headlines_row.xml @@ -103,6 +103,7 @@ android:id="@+id/flavorImageHolder" android:layout_width="match_parent" android:layout_height="wrap_content" + android:layout_marginBottom="16dp" android:layout_span="2"> @@ -178,7 +179,8 @@ android:textAlignment="viewStart" android:lineSpacingExtra="2sp" android:maxLines="5" - android:padding="16dp" + android:paddingStart="16dp" + android:paddingEnd="16dp" tools:text="Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." android:textSize="13sp" /> @@ -193,8 +195,7 @@ android:layout_height="wrap_content" android:layout_span="2" android:gravity="center_vertical" - android:paddingBottom="8dp" - android:paddingLeft="8dp"> + android:paddingBottom="8dp"> @@ -178,7 +178,8 @@ android:textAlignment="viewStart" android:lineSpacingExtra="2sp" android:maxLines="5" - android:padding="16dp" + android:paddingStart="16dp" + android:paddingEnd="16dp" tools:text="Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." android:textSize="13sp" /> @@ -193,8 +194,7 @@ android:layout_height="wrap_content" android:layout_span="2" android:gravity="center_vertical" - android:paddingBottom="8dp" - android:paddingLeft="8dp"> + android:paddingBottom="8dp"> Date: Sun, 18 May 2025 11:47:10 +0300 Subject: drop unused jar --- org.fox.ttrss/libs/glide-transformations-2.0.2.jar | Bin 38614 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 org.fox.ttrss/libs/glide-transformations-2.0.2.jar diff --git a/org.fox.ttrss/libs/glide-transformations-2.0.2.jar b/org.fox.ttrss/libs/glide-transformations-2.0.2.jar deleted file mode 100644 index b8b93cfb..00000000 Binary files a/org.fox.ttrss/libs/glide-transformations-2.0.2.jar and /dev/null differ -- cgit v1.2.3-54-g00ecf From 83f260efe0d2a1f0e2e81c53f89a029d91e70b25 Mon Sep 17 00:00:00 2001 From: Andrew Dolgov Date: Sun, 18 May 2025 12:36:14 +0300 Subject: disable shared transitions for now --- .../main/java/org/fox/ttrss/GalleryActivity.java | 67 +++++++++------------- .../java/org/fox/ttrss/GalleryImageFragment.java | 4 +- .../src/main/java/org/fox/ttrss/GalleryModel.java | 13 +++++ .../java/org/fox/ttrss/GalleryVideoFragment.java | 2 +- .../main/java/org/fox/ttrss/HeadlinesFragment.java | 6 +- 5 files changed, 47 insertions(+), 45 deletions(-) diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryActivity.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryActivity.java index b0ada038..fb87bc27 100755 --- a/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryActivity.java +++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryActivity.java @@ -95,7 +95,7 @@ public class GalleryActivity extends CommonActivity { @Override public void onCreate(Bundle savedInstanceState) { - ActivityCompat.postponeEnterTransition(this); + // ActivityCompat.postponeEnterTransition(this); // we use that before parent onCreate so let's init locally m_prefs = PreferenceManager @@ -117,29 +117,53 @@ public class GalleryActivity extends CommonActivity { getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().hide(); + setTitle(m_title); + + m_adapter = new ArticleImagesPagerAdapter(this, new GalleryEntryDiffItemCallback()); + + m_pager = findViewById(R.id.gallery_pager); + m_pager.setAdapter(m_adapter); + + m_checkProgress = findViewById(R.id.gallery_check_progress); + if (savedInstanceState == null) { m_title = getIntent().getStringExtra("title"); m_content = getIntent().getStringExtra("content"); + GalleryModel model = new ViewModelProvider(this).get(GalleryModel.class); + // this should be dealt with first so that transition completes properly String firstSrc = getIntent().getStringExtra("firstSrc"); - GalleryModel model = new ViewModelProvider(this).get(GalleryModel.class); + /* but what about videos? if (firstSrc != null) { + List initialItems = new ArrayList(); + + initialItems.add(0, new GalleryEntry(firstSrc, GalleryEntry.GalleryEntryType.TYPE_IMAGE, null)); + + m_adapter.submitList(initialItems); + + model.update(initialItems); + } */ + model.collectItems(m_content, firstSrc); model.getItemsToCheck().observe(this, itemsToCheck -> { Log.d(TAG, "observed items to check=" + itemsToCheck); m_checkProgress.setMax(itemsToCheck); - m_checkProgress.setVisibility(itemsToCheck > 0 ? View.VISIBLE : View.GONE); m_checkProgress.setProgress(0); }); + model.getIsChecking().observe(this, isChecking -> { + Log.d(TAG, "observed isChecking=" + isChecking); + + m_checkProgress.setVisibility(isChecking ? View.VISIBLE : View.GONE); + }); + model.getCheckProgress().observe(this, progress -> { Log.d(TAG, "observed item check progress=" + progress); m_checkProgress.setProgress(progress); - m_checkProgress.setVisibility(progress < m_checkProgress.getMax() ? View.VISIBLE : View.GONE); }); model.getItems().observe(this, galleryEntries -> { @@ -187,41 +211,6 @@ public class GalleryActivity extends CommonActivity { e.printStackTrace(); } }); - - setTitle(m_title); - - m_adapter = new ArticleImagesPagerAdapter(this, new GalleryEntryDiffItemCallback()); - - m_pager = findViewById(R.id.gallery_pager); - m_pager.setAdapter(m_adapter); - - m_checkProgress = findViewById(R.id.gallery_check_progress); - - /* Log.d(TAG, "items to check:" + uncheckedItems.size()); - - MediaCheckTask mct = new MediaCheckTask() { - @Override - protected void onProgressUpdate(MediaProgressResult... result) { - //m_items.add(result[0].item); - m_adapter.notifyDataSetChanged(); - - if (result[0].position < result[0].count) { - m_checkProgress.setVisibility(View.VISIBLE); - m_checkProgress.setMax(result[0].count); - m_checkProgress.setProgress(result[0].position); - } else { - m_checkProgress.setVisibility(View.GONE); - } - - } - - @Override - protected void onPostExecute(List result) { - m_items.addAll(result); - } - }; - - mct.execute(uncheckedItems); */ } @Override diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryImageFragment.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryImageFragment.java index b249b889..db141427 100755 --- a/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryImageFragment.java +++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryImageFragment.java @@ -71,7 +71,7 @@ public class GalleryImageFragment extends GalleryBaseFragment { progressBar.setVisibility(View.GONE); errorMessage.setVisibility(View.VISIBLE); - ActivityCompat.startPostponedEnterTransition(m_activity); + // ActivityCompat.startPostponedEnterTransition(m_activity); return false; } @@ -81,7 +81,7 @@ public class GalleryImageFragment extends GalleryBaseFragment { progressBar.setVisibility(View.GONE); errorMessage.setVisibility(View.GONE); - ActivityCompat.startPostponedEnterTransition(m_activity); + // ActivityCompat.startPostponedEnterTransition(m_activity); return false; } diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryModel.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryModel.java index 2f0451fb..750f85cd 100644 --- a/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryModel.java +++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryModel.java @@ -32,6 +32,7 @@ public class GalleryModel extends AndroidViewModel { private MutableLiveData> m_items = new MutableLiveData<>(new ArrayList<>()); private MutableLiveData m_checkProgress = new MutableLiveData<>(Integer.valueOf(0)); private MutableLiveData m_itemsToCheck = new MutableLiveData<>(Integer.valueOf(0)); + private MutableLiveData m_isChecking = new MutableLiveData<>(Boolean.valueOf(false)); public GalleryModel(@NonNull Application application) { super(application); @@ -65,6 +66,14 @@ public class GalleryModel extends AndroidViewModel { return false; } + public void update(List items) { + m_items.postValue(items); + } + + public LiveData getIsChecking() { + return m_isChecking; + } + public void collectItems(String articleText, String srcFirst) { m_executor.execute(() -> { @@ -81,6 +90,8 @@ public class GalleryModel extends AndroidViewModel { int currentItem = 0; boolean firstFound = false; + m_isChecking.postValue(true); + for (Element elem : elems) { ++currentItem; @@ -160,6 +171,8 @@ public class GalleryModel extends AndroidViewModel { checkList.add(0, new GalleryEntry(srcFirst, GalleryEntry.GalleryEntryType.TYPE_IMAGE, null)); m_items.postValue(checkList); } + + m_isChecking.postValue(false); }); } } diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryVideoFragment.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryVideoFragment.java index 18d08e4f..a171fb5b 100755 --- a/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryVideoFragment.java +++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryVideoFragment.java @@ -56,7 +56,7 @@ public class GalleryVideoFragment extends GalleryBaseFragment { registerForContextMenu(imgView); - ActivityCompat.startPostponedEnterTransition(m_activity); + // ActivityCompat.startPostponedEnterTransition(m_activity); view.findViewById(R.id.flavor_image_progress).setVisibility(View.VISIBLE); 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 6c3829cc..67e9a75d 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 @@ -1409,14 +1409,14 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { intent.putExtra("content", tempContent); - ActivityOptionsCompat options = + /* ActivityOptionsCompat options = ActivityOptionsCompat.makeSceneTransitionAnimation(m_activity, transitionView != null ? transitionView : holder.flavorImageView, "gallery:" + (article.flavorStreamUri != null ? article.flavorStreamUri : article.flavorImageUri)); - ActivityCompat.startActivity(m_activity, intent, options.toBundle()); + ActivityCompat.startActivity(m_activity, intent, options.toBundle()); */ - // startActivity(intent); + startActivity(intent); } } -- cgit v1.2.3-54-g00ecf From 78e5c82809a122070cbc4326b56483df6de963fa Mon Sep 17 00:00:00 2001 From: Andrew Dolgov Date: Sun, 18 May 2025 13:38:13 +0300 Subject: disable inline video player until it is tested, disable predraw hacks (memory cache is good enough for now) --- .../main/java/org/fox/ttrss/HeadlinesFragment.java | 194 ++++++++++----------- .../java/org/fox/ttrss/HeadlinesFragmentModel.java | 22 +++ org.fox.ttrss/src/main/res/xml/preferences.xml | 1 + 3 files changed, 115 insertions(+), 102 deletions(-) create mode 100644 org.fox.ttrss/src/main/java/org/fox/ttrss/HeadlinesFragmentModel.java 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 67e9a75d..63ecbdb8 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 @@ -48,10 +48,9 @@ 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.content.ContextCompat; import androidx.core.view.ViewCompat; +import androidx.lifecycle.ViewModelProvider; import androidx.preference.PreferenceManager; import androidx.recyclerview.widget.DefaultItemAnimator; import androidx.recyclerview.widget.ItemTouchHelper; @@ -104,7 +103,6 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { public enum ArticlesSelection { ALL, NONE, UNREAD } public static final int FLAVOR_IMG_MIN_SIZE = 128; - public static final int THUMB_IMG_MIN_SIZE = 32; private final String TAG = this.getClass().getSimpleName(); @@ -122,12 +120,11 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { private boolean m_compactLayoutMode = false; private RecyclerView m_list; private LinearLayoutManager m_layoutManager; + private HeadlinesFragmentModel m_headlinesFragmentModel; private MediaPlayer m_mediaPlayer; private TextureView m_activeTexture; - protected static HashMap m_flavorMeasuredHeightsCache = new HashMap<>(); - public ArticleList getSelectedArticles() { return Application.getArticles() .stream() @@ -299,6 +296,8 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { Log.d(TAG, "onCreateView"); + m_headlinesFragmentModel = new ViewModelProvider(this).get(HeadlinesFragmentModel.class); + String headlineMode = m_prefs.getString("headline_mode", "HL_DEFAULT"); if ("HL_COMPACT".equals(headlineMode) || "HL_COMPACT_NOIMAGES".equals(headlineMode)) @@ -620,7 +619,6 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { public View flavorImageOverflow; public TextureView flavorVideoView; public MaterialButton attachmentsView; - // public ProgressTarget flavorProgressTarget; int articleId; public TextView linkHost; @@ -629,16 +627,6 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { view = v; - view.getViewTreeObserver().addOnPreDrawListener(() -> { - View flavorImage = view.findViewById(R.id.flavor_image); - - if (flavorImage != null) { - HeadlinesFragment.m_flavorMeasuredHeightsCache.put(articleId, flavorImage.getMeasuredHeight()); - } - - return true; - }); - titleView = v.findViewById(R.id.title); feedTitleView = v.findViewById(R.id.feed_title); @@ -781,6 +769,13 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { return new ArticleViewHolder(v); } + @Override public void onViewRecycled(ArticleViewHolder holder){ + super.onViewRecycled(holder); + + if (holder.flavorImageView != null) + Glide.with(HeadlinesFragment.this).clear(holder.flavorImageView); + } + @Override public void onBindViewHolder(final ArticleViewHolder holder, int position) { int headlineFontSize = m_prefs.getInt("headlines_font_size_sp_int", 13); @@ -1006,16 +1001,14 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { if (!m_compactLayoutMode && holder.flavorImageHolder != null) { - /* reset to default in case of convertview */ + // reset our view to default in case of recycling holder.flavorImageLoadingBar.setVisibility(View.GONE); holder.flavorImageLoadingBar.setIndeterminate(false); + holder.flavorImageView.setVisibility(View.GONE); holder.flavorVideoKindView.setVisibility(View.GONE); holder.flavorImageOverflow.setVisibility(View.GONE); holder.flavorVideoView.setVisibility(View.GONE); - holder.flavorImageHolder.setVisibility(View.GONE); - - Glide.with(HeadlinesFragment.this).clear(holder.flavorImageView); // this is needed if our flavor image goes behind base listview element holder.headlineHeader.setOnClickListener(v -> { @@ -1029,6 +1022,9 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { }); if (canShowFlavorImage() && article.flavorImageUri != null && holder.flavorImageView != null) { + + holder.flavorImageView.setOnClickListener(view -> openGalleryForType(article, holder, holder.flavorImageView)); + if (holder.flavorImageOverflow != null) { holder.flavorImageOverflow.setOnClickListener(v -> { PopupMenu popup = new PopupMenu(getActivity(), holder.flavorImageOverflow); @@ -1068,87 +1064,29 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { }); } - int maxImageSize = (int) (m_screenHeight * 0.5f); + int maxImageHeight = (int) (m_screenHeight * 0.5f); // we also downsample below using glide to save RAM - holder.flavorImageView.setMaxHeight(maxImageSize); - - // prevent lower listiew entries from jumping around if this row is modified - if (m_flavorMeasuredHeightsCache.containsKey(article.id)) { - int cachedHeight = m_flavorMeasuredHeightsCache.get(article.id); - - if (cachedHeight > 0) { - FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) holder.flavorImageView.getLayoutParams(); - lp.height = cachedHeight; - } - } + holder.flavorImageView.setMaxHeight(maxImageHeight); - Log.d(TAG, "checking resource size for " + article.flavorImageUri); + if (m_headlinesFragmentModel.getFlavorImageSizes().containsKey(article.flavorImageUri)) { + Size size = m_headlinesFragmentModel.getFlavorImageSizes().get(article.flavorImageUri); - FlavorProgressTarget flavorProgressTarget = new FlavorProgressTarget<>(new SimpleTarget() { - @Override - public void onResourceReady(@NonNull Size resource, @Nullable com.bumptech.glide.request.transition.Transition transition) { - Log.d(TAG, "got resource of " + resource.getWidth() + "x" + resource.getHeight()); - - if (resource.getWidth() > FLAVOR_IMG_MIN_SIZE && resource.getHeight() > FLAVOR_IMG_MIN_SIZE) { - - // now we can actually load the image into our drawable - Glide.with(HeadlinesFragment.this) - .load(article.flavorImageUri) - .transition(DrawableTransitionOptions.withCrossFade()) - .override(maxImageSize) - .diskCacheStrategy(DiskCacheStrategy.DATA) - .skipMemoryCache(true) - .listener(new RequestListener() { - @Override - public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) { - holder.flavorImageHolder.setVisibility(View.GONE); - - holder.flavorImageView.setVisibility(View.GONE); - holder.flavorImageOverflow.setVisibility(View.VISIBLE); - - return false; - } - - @Override - public boolean onResourceReady(Drawable resource, Object model, Target target, DataSource dataSource, boolean isFirstResource) { - holder.flavorImageHolder.setVisibility(View.VISIBLE); - - holder.flavorImageView.setVisibility(View.VISIBLE); - holder.flavorImageOverflow.setVisibility(View.VISIBLE); - - adjustVideoKindView(holder, article); - - return false; - } - }) - .into(new DrawableImageViewTarget(holder.flavorImageView)); - } else { - holder.flavorImageHolder.setVisibility(View.GONE); + Log.d(TAG, "using cached resource size for " + article.flavorImageUri + " " + size.getWidth() + "x" + size.getHeight()); - holder.flavorImageView.setVisibility(View.VISIBLE); - holder.flavorImageOverflow.setVisibility(View.VISIBLE); - } + if (size.getWidth() > FLAVOR_IMG_MIN_SIZE && size.getHeight() > FLAVOR_IMG_MIN_SIZE) { + loadFlavorImage(article, holder, maxImageHeight); } - }, article.flavorImageUri, holder); - Glide.with(HeadlinesFragment.this) - .as(Size.class) - .load(article.flavorImageUri) - .diskCacheStrategy(DiskCacheStrategy.DATA) - .skipMemoryCache(true) - .into(flavorProgressTarget); + } else { + Log.d(TAG, "checking resource size for " + article.flavorImageUri); + checkImageAndLoad(article, holder, maxImageHeight); + } } - if (m_prefs.getBoolean("inline_video_player", false) && article.flavorImage != null && + /* if (m_prefs.getBoolean("inline_video_player", false) && article.flavorImage != null && "video".equalsIgnoreCase(article.flavorImage.tagName()) && article.flavorStreamUri != null) { - holder.flavorImageView.setOnLongClickListener(v -> { - releaseSurface(); - openGalleryForType(article, holder, holder.flavorImageView); - return true; - }); - holder.flavorVideoView.setOnLongClickListener(v -> { releaseSurface(); openGalleryForType(article, holder, holder.flavorImageView); @@ -1243,7 +1181,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { } else { holder.flavorImageView.setOnClickListener(view -> openGalleryForType(article, holder, holder.flavorImageView)); - } + } */ } String articleAuthor = article.author != null ? article.author : ""; @@ -1314,6 +1252,69 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { } } + private void loadFlavorImage(Article article, ArticleViewHolder holder, int maxImageHeight) { + Glide.with(HeadlinesFragment.this) + .load(article.flavorImageUri) + .transition(DrawableTransitionOptions.withCrossFade()) + .override(m_screenWidth, maxImageHeight) + .diskCacheStrategy(DiskCacheStrategy.DATA) + .skipMemoryCache(false) + .listener(new RequestListener() { + @Override + public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) { + holder.flavorImageHolder.setVisibility(View.GONE); + + holder.flavorImageView.setVisibility(View.GONE); + holder.flavorImageOverflow.setVisibility(View.VISIBLE); + + return false; + } + + @Override + public boolean onResourceReady(Drawable resource, Object model, Target target, DataSource dataSource, boolean isFirstResource) { + holder.flavorImageHolder.setVisibility(View.VISIBLE); + + holder.flavorImageView.setVisibility(View.VISIBLE); + holder.flavorImageOverflow.setVisibility(View.VISIBLE); + + adjustVideoKindView(holder, article); + + return false; + } + }) + .into(new DrawableImageViewTarget(holder.flavorImageView)); + } + + private void checkImageAndLoad(Article article, ArticleViewHolder holder, int maxImageHeight) { + FlavorProgressTarget flavorProgressTarget = new FlavorProgressTarget<>(new SimpleTarget() { + @Override + public void onResourceReady(@NonNull Size resource, @Nullable com.bumptech.glide.request.transition.Transition transition) { + Log.d(TAG, "got resource of " + resource.getWidth() + "x" + resource.getHeight()); + + m_headlinesFragmentModel.getFlavorImageSizes().put(article.flavorImageUri, resource); + + if (resource.getWidth() > FLAVOR_IMG_MIN_SIZE && resource.getHeight() > FLAVOR_IMG_MIN_SIZE) { + + // now we can actually load the image into our drawable + loadFlavorImage(article, holder, maxImageHeight); + + } else { + holder.flavorImageHolder.setVisibility(View.GONE); + + holder.flavorImageView.setVisibility(View.VISIBLE); + holder.flavorImageOverflow.setVisibility(View.VISIBLE); + } + } + }, article.flavorImageUri, holder); + + Glide.with(HeadlinesFragment.this) + .as(Size.class) + .load(article.flavorImageUri) + .diskCacheStrategy(DiskCacheStrategy.DATA) + .skipMemoryCache(true) + .into(flavorProgressTarget); + } + @Override public int getItemViewType(int position) { Article a = getItem(position); @@ -1356,17 +1357,6 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { .apply(RequestOptions.circleCropTransform()) .diskCacheStrategy(DiskCacheStrategy.ALL) .skipMemoryCache(false) - .listener(new RequestListener() { - @Override - public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) { - return false; - } - - @Override - public boolean onResourceReady(Drawable resource, Object model, Target target, DataSource dataSource, boolean isFirstResource) { - return resource.getIntrinsicWidth() < THUMB_IMG_MIN_SIZE || resource.getIntrinsicHeight() < THUMB_IMG_MIN_SIZE; - } - }) .into(holder.textImage); } diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/HeadlinesFragmentModel.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/HeadlinesFragmentModel.java new file mode 100644 index 00000000..dd6d83cd --- /dev/null +++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/HeadlinesFragmentModel.java @@ -0,0 +1,22 @@ +package org.fox.ttrss; + +import android.app.Application; +import android.util.Size; + +import androidx.annotation.NonNull; +import androidx.lifecycle.AndroidViewModel; + +import java.util.HashMap; + +// this is used to store fragment data which is temporary but should survive orientation changes +public class HeadlinesFragmentModel extends AndroidViewModel { + private HashMap m_flavorImageSizes = new HashMap<>(); + + public HashMap getFlavorImageSizes() { + return m_flavorImageSizes; + } + + public HeadlinesFragmentModel(@NonNull Application application) { + super(application); + } +} diff --git a/org.fox.ttrss/src/main/res/xml/preferences.xml b/org.fox.ttrss/src/main/res/xml/preferences.xml index 8d064587..33095c5f 100755 --- a/org.fox.ttrss/src/main/res/xml/preferences.xml +++ b/org.fox.ttrss/src/main/res/xml/preferences.xml @@ -134,6 +134,7 @@ android:summary="@string/prefs_always_downsample_images_long" /> Date: Sun, 18 May 2025 14:28:33 +0300 Subject: fix score not handled in areContentsTheSame, add selective updates to headline recycler view via getChangePayload --- .../main/java/org/fox/ttrss/HeadlinesFragment.java | 315 ++++++++++++--------- .../fox/ttrss/util/ArticleDiffItemCallback.java | 34 ++- 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); } @@ -776,6 +785,39 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { Glide.with(HeadlinesFragment.this).clear(holder.flavorImageView); } + @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 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); @@ -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
{ 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); } } -- cgit v1.2.3-54-g00ecf From 4117ba3e72cc0c529ebe266e17dc06a4bdccd98a Mon Sep 17 00:00:00 2001 From: Andrew Dolgov Date: Sun, 18 May 2025 15:38:26 +0300 Subject: drop all views specific to article states in favor of updating necessary colors on viewholder bind --- .../main/java/org/fox/ttrss/HeadlinesFragment.java | 225 ++++++++++------- .../res/layout/headlines_row_compact_active.xml | 109 -------- .../layout/headlines_row_compact_active_unread.xml | 110 --------- .../res/layout/headlines_row_compact_unread.xml | 110 --------- .../src/main/res/layout/headlines_row_unread.xml | 273 --------------------- 5 files changed, 131 insertions(+), 696 deletions(-) delete mode 100755 org.fox.ttrss/src/main/res/layout/headlines_row_compact_active.xml delete mode 100755 org.fox.ttrss/src/main/res/layout/headlines_row_compact_active_unread.xml delete mode 100755 org.fox.ttrss/src/main/res/layout/headlines_row_compact_unread.xml delete mode 100755 org.fox.ttrss/src/main/res/layout/headlines_row_unread.xml 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 2a9b71b2..608950d7 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 @@ -6,8 +6,8 @@ import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.res.ColorStateList; -import android.graphics.Paint; import android.graphics.Point; +import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.media.MediaPlayer; import android.net.ConnectivityManager; @@ -69,6 +69,7 @@ import com.bumptech.glide.request.target.DrawableImageViewTarget; import com.bumptech.glide.request.target.SimpleTarget; import com.bumptech.glide.request.target.Target; import com.google.android.material.button.MaterialButton; +import com.google.android.material.card.MaterialCardView; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.snackbar.Snackbar; @@ -81,6 +82,7 @@ import org.fox.ttrss.util.ArticleDiffItemCallback; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; +import java.lang.reflect.Type; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; @@ -104,6 +106,9 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { private final String TAG = this.getClass().getSimpleName(); private Feed m_feed; + + /** TODO this should be stored in model, either as an observable or a field - article.active or something */ + @Deprecated private int m_activeArticleId; private String m_searchQuery = ""; @@ -155,9 +160,13 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { m_activity.editArticleNote(article); return true; } else if (itemId == R.id.headlines_article_unread) { - article.unread = !article.unread; - m_activity.saveArticleUnread(article); - m_adapter.notifyItemChanged(position); + Article articleClone = new Article(article); + articleClone.unread = !articleClone.unread; + + m_activity.saveArticleUnread(articleClone); + + Application.getArticlesModel().update(position, articleClone); + return true; } else if (itemId == R.id.headlines_article_link_copy) { m_activity.copyToClipboard(article.link); @@ -166,11 +175,13 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { m_activity.openUri(Uri.parse(article.link)); if (article.unread) { - article.unread = false; - m_activity.saveArticleUnread(article); + Article articleClone = new Article(article); + articleClone.unread = !articleClone.unread; - m_adapter.notifyItemChanged(position); - } + m_activity.saveArticleUnread(articleClone); + + Application.getArticlesModel().update(position, articleClone); + } return true; } else if (itemId == R.id.headlines_share_article) { m_activity.shareArticle(article); @@ -194,20 +205,24 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { } 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) { - a.unread = false; - tmp.add(a); + Article articleClone = new Article(a); - int position = articles.getPositionById(a.id); + articleClone.unread = false; + tmp.add(articleClone); + + int position = articles.getPositionById(articleClone.id); if (position != -1) - m_adapter.notifyItemChanged(position); + Application.getArticlesModel().update(position, articleClone); } } @@ -419,12 +434,14 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { m_activity.setArticlesUnread(m_readArticles, Article.UPDATE_SET_FALSE); for (Article a : m_readArticles) { - a.unread = false; + Article articleClone = new Article(a); + + articleClone.unread = false; int position = Application.getArticles().getPositionById(a.id); if (position != -1) - m_adapter.notifyItemChanged(position); + Application.getArticlesModel().update(position, articleClone); } m_readArticles.clear(); @@ -691,19 +708,20 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { private class ArticleListAdapter extends ListAdapter { public static final int VIEW_NORMAL = 0; - public static final int VIEW_UNREAD = 1; - public static final int VIEW_ACTIVE = 2; - public static final int VIEW_ACTIVE_UNREAD = 3; - public static final int VIEW_AMR_FOOTER = 4; - - public static final int VIEW_COUNT = VIEW_AMR_FOOTER + 1; - - private final Integer[] origTitleColors = new Integer[VIEW_COUNT]; + public static final int VIEW_AMR_FOOTER = 1; 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; + private final ColorStateList m_cslTertiary; + private final ColorStateList m_cslPrimary; + private final int m_colorSurfaceContainerLowest; + private final int m_colorSurface; + private final int m_colorPrimary; + private final int m_colorTertiary; + private final int m_colorSecondary; + private final int m_colorOnSurface; + private final int m_colorTertiaryContainer; + private final int m_colorOnTertiaryContainer; boolean m_flavorImageEnabled; private final int m_screenWidth; @@ -728,6 +746,12 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { return false; } + private int colorFromAttr(int attr) { + TypedValue tv = new TypedValue(); + m_activity.getTheme().resolveAttribute(attr, tv, true); + return ContextCompat.getColor(m_activity, tv.resourceId); + } + public ArticleListAdapter() { super(new ArticleDiffItemCallback()); @@ -740,15 +764,19 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { String headlineMode = m_prefs.getString("headline_mode", "HL_DEFAULT"); m_flavorImageEnabled = "HL_DEFAULT".equals(headlineMode) || "HL_COMPACT".equals(headlineMode); - TypedValue tvTertiary = new TypedValue(); - m_activity.getTheme().resolveAttribute(R.attr.colorTertiary, tvTertiary, true); + m_colorPrimary = colorFromAttr(R.attr.colorPrimary); + m_colorSecondary = colorFromAttr(R.attr.colorSecondary); + m_colorTertiary = colorFromAttr(R.attr.colorTertiary); - m_colorTertiary = ColorStateList.valueOf(ContextCompat.getColor(m_activity, tvTertiary.resourceId)); + m_cslTertiary = ColorStateList.valueOf(m_colorTertiary); + m_cslPrimary = ColorStateList.valueOf(m_colorPrimary); - TypedValue tvPrimary = new TypedValue(); - m_activity.getTheme().resolveAttribute(R.attr.colorPrimary, tvPrimary, true); + m_colorSurfaceContainerLowest = colorFromAttr(R.attr.colorSurfaceContainerLowest); + m_colorSurface = colorFromAttr(R.attr.colorSurface); + m_colorOnSurface = colorFromAttr(R.attr.colorOnSurface); - m_colorPrimary = ColorStateList.valueOf(ContextCompat.getColor(m_activity, tvPrimary.resourceId)); + m_colorTertiaryContainer = colorFromAttr(R.attr.colorTertiaryContainer); + m_colorOnTertiaryContainer = colorFromAttr(R.attr.colorOnTertiaryContainer); m_cmgr = (ConnectivityManager) m_activity.getSystemService(Context.CONNECTIVITY_SERVICE); } @@ -758,20 +786,9 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { int layoutId = m_compactLayoutMode ? R.layout.headlines_row_compact : R.layout.headlines_row; - switch (viewType) { - case VIEW_AMR_FOOTER: - layoutId = R.layout.headlines_footer; - break; - case VIEW_UNREAD: - layoutId = m_compactLayoutMode ? R.layout.headlines_row_compact_unread : R.layout.headlines_row_unread; - break; - case VIEW_ACTIVE: - layoutId = m_compactLayoutMode ? R.layout.headlines_row_compact_active : R.layout.headlines_row; - break; - case VIEW_ACTIVE_UNREAD: - layoutId = m_compactLayoutMode ? R.layout.headlines_row_compact_active_unread : R.layout.headlines_row_unread; - break; - } + if (viewType == VIEW_AMR_FOOTER) { + layoutId = R.layout.headlines_footer; + } View v = LayoutInflater.from(parent.getContext()).inflate(layoutId, parent, false); @@ -798,6 +815,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { switch (payload) { case UNREAD: + updateUnreadView(article, holder); break; case MARKED: updateMarkedView(article, holder, position); @@ -810,7 +828,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { updatePublishedView(article, holder, position); break; case SCORE: - updateScoreView(article, holder); + updateScoreView(article, holder, position); break; } } @@ -818,6 +836,43 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { super.onBindViewHolder(holder, position, payloads); } } + + private void updateUnreadView(final Article article, ArticleViewHolder holder) { + if (m_compactLayoutMode) { + holder.view.setBackgroundColor(article.unread ? m_colorSurfaceContainerLowest : 0); + } else { + MaterialCardView card = (MaterialCardView) holder.view; + + card.setCardBackgroundColor(article.unread ? m_colorSurfaceContainerLowest : m_colorSurface); + } + + if (holder.titleView != null) { + holder.titleView.setTypeface(null, article.unread ? Typeface.BOLD : Typeface.NORMAL); + holder.titleView.setTextColor(article.unread ? m_colorOnSurface : m_colorPrimary); + } + + updateActiveView(article, holder); + } + + private void updateActiveView(final Article article, ArticleViewHolder holder) { + if (article.id == m_activeArticleId) { + holder.view.setBackgroundColor(m_colorTertiaryContainer); + + if (holder.titleView != null) { + holder.titleView.setTextColor(m_colorOnTertiaryContainer); + } + } + + if (holder.excerptView != null) { + holder.excerptView.setTextColor(article.id == m_activeArticleId ? m_colorOnTertiaryContainer : m_colorOnSurface); + } + + if (holder.feedTitleView != null) { + holder.feedTitleView.setTextColor(article.id == m_activeArticleId ? m_colorOnTertiaryContainer : m_colorSecondary); + } + + } + @Override public void onBindViewHolder(final ArticleViewHolder holder, int position) { int headlineFontSize = m_prefs.getInt("headlines_font_size_sp_int", 13); @@ -838,6 +893,8 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { // nothing else of interest for those below anyway if (article.id < 0) return; + updateUnreadView(article, holder); + holder.view.setOnLongClickListener(v -> { m_list.showContextMenuForChild(v); return true; @@ -866,8 +923,6 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { if (holder.titleView != null) { holder.titleView.setText(Html.fromHtml(article.title)); holder.titleView.setTextSize(TypedValue.COMPLEX_UNIT_SP, Math.min(21, headlineFontSize + 3)); - - adjustTitleTextView(article.score, holder.titleView, position); } if (holder.feedTitleView != null) { @@ -890,7 +945,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { } updateMarkedView(article, holder, position); - updateScoreView(article, holder); + updateScoreView(article, holder, position); updatePublishedView(article, holder, position); if (holder.attachmentsView != null) { @@ -1177,7 +1232,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { 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.setIconTint(article.marked ? m_cslTertiary : m_cslPrimary); holder.markedView.setOnClickListener(v -> { Article selectedArticle = new Article(getItem(position)); @@ -1224,20 +1279,22 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { if (holder.selectionBoxView != null) { holder.selectionBoxView.setChecked(article.selected); holder.selectionBoxView.setOnClickListener(view -> { - Article currentArticle = getItem(position); + Article selectedArticle = new Article(getItem(position)); Log.d(TAG, "selectionCb onClick pos=" + position + " article=" + article); CheckBox cb = (CheckBox)view; - currentArticle.selected = cb.isChecked(); + selectedArticle.selected = cb.isChecked(); + + Application.getArticlesModel().update(position, selectedArticle); m_listener.onArticleListSelectionChange(); }); } } - private void updateScoreView(Article article, ArticleViewHolder holder) { + private void updateScoreView(Article article, ArticleViewHolder holder, int position) { if (holder.scoreView != null) { int scoreDrawable = R.drawable.baseline_trending_flat_24; @@ -1249,23 +1306,27 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { holder.scoreView.setIconResource(scoreDrawable); if (article.score > Article.SCORE_HIGH) - holder.scoreView.setIconTint(m_colorTertiary); + holder.scoreView.setIconTint(m_cslTertiary); else - holder.scoreView.setIconTint(m_colorPrimary); + holder.scoreView.setIconTint(m_cslPrimary); if (m_activity.getApiLevel() >= 16) { holder.scoreView.setOnClickListener(v -> { + Article selectedArticle = new Article(getItem(position)); + final EditText edit = new EditText(getActivity()); - edit.setText(String.valueOf(article.score)); + edit.setText(String.valueOf(selectedArticle.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()); + selectedArticle.score = Integer.parseInt(edit.getText().toString()); m_activity.saveArticleScore(article); - m_adapter.notifyItemChanged(m_list.getChildAdapterPosition(holder.view)); + + Application.getArticlesModel().update(position, selectedArticle); + } catch (NumberFormatException e) { m_activity.toast(R.string.score_invalid); e.printStackTrace(); @@ -1288,7 +1349,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { holder.publishedView.setIconResource(article.published ? R.drawable.rss_box : R.drawable.rss); } - holder.publishedView.setIconTint(article.published ? m_colorTertiary : m_colorPrimary); + holder.publishedView.setIconTint(article.published ? m_cslTertiary : m_cslPrimary); holder.publishedView.setOnClickListener(v -> { Article selectedArticle = new Article(getItem(position)); @@ -1370,12 +1431,6 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { if (a.id == Article.TYPE_AMR_FOOTER) { return VIEW_AMR_FOOTER; - } else if (a.id == m_activeArticleId && a.unread) { - return VIEW_ACTIVE_UNREAD; - } else if (a.id == m_activeArticleId) { - return VIEW_ACTIVE; - } else if (a.unread) { - return VIEW_UNREAD; } else { return VIEW_NORMAL; } @@ -1475,20 +1530,6 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { holder.flavorVideoKindView.setVisibility(View.INVISIBLE); } } - - private void adjustTitleTextView(int score, TextView tv, int position) { - int viewType = getItemViewType(position); - if (origTitleColors[viewType] == null) - // store original color - origTitleColors[viewType] = tv.getCurrentTextColor(); - - if (score < Article.SCORE_LOW) { - tv.setPaintFlags(tv.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); - } else { - tv.setTextColor(origTitleColors[viewType]); - tv.setPaintFlags(tv.getPaintFlags() & ~Paint.STRIKE_THRU_TEXT_FLAG); - } - } } private void releaseSurface() { @@ -1543,26 +1584,22 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { } public void setSelection(ArticlesSelection select) { - ArticleList articlesWithoutFooters = Application.getArticles().getWithoutFooters(); - - for (Article a : articlesWithoutFooters) { - if (select == ArticlesSelection.ALL || select == ArticlesSelection.UNREAD && a.unread) { - a.selected = true; - - int position = Application.getArticles().getPositionById(a.id); - - if (position != -1) - m_adapter.notifyItemChanged(position); - - } else if (a.selected) { - a.selected = false; + ArticleList articles = Application.getArticles(); + ArticleList tmp = new ArticleList(); - int position = Application.getArticles().getPositionById(a.id); + for (Article a : articles) { + Article articleClone = new Article(a); - if (position != -1) - m_adapter.notifyItemChanged(position); + if (select == ArticlesSelection.ALL || select == ArticlesSelection.UNREAD && a.unread) { + articleClone.selected = true; + } else { + articleClone.selected = false; } + + tmp.add(articleClone); } + + Application.getArticlesModel().update(tmp); } public String getSearchQuery() { diff --git a/org.fox.ttrss/src/main/res/layout/headlines_row_compact_active.xml b/org.fox.ttrss/src/main/res/layout/headlines_row_compact_active.xml deleted file mode 100755 index 72ae99b8..00000000 --- a/org.fox.ttrss/src/main/res/layout/headlines_row_compact_active.xml +++ /dev/null @@ -1,109 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/org.fox.ttrss/src/main/res/layout/headlines_row_compact_active_unread.xml b/org.fox.ttrss/src/main/res/layout/headlines_row_compact_active_unread.xml deleted file mode 100755 index b2b3a21e..00000000 --- a/org.fox.ttrss/src/main/res/layout/headlines_row_compact_active_unread.xml +++ /dev/null @@ -1,110 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/org.fox.ttrss/src/main/res/layout/headlines_row_compact_unread.xml b/org.fox.ttrss/src/main/res/layout/headlines_row_compact_unread.xml deleted file mode 100755 index 2fbbe062..00000000 --- a/org.fox.ttrss/src/main/res/layout/headlines_row_compact_unread.xml +++ /dev/null @@ -1,110 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/org.fox.ttrss/src/main/res/layout/headlines_row_unread.xml b/org.fox.ttrss/src/main/res/layout/headlines_row_unread.xml deleted file mode 100755 index cff1502d..00000000 --- a/org.fox.ttrss/src/main/res/layout/headlines_row_unread.xml +++ /dev/null @@ -1,273 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file -- cgit v1.2.3-54-g00ecf From e2d12ad0a07eef36d7b592208f08a11015d5dd88 Mon Sep 17 00:00:00 2001 From: Andrew Dolgov Date: Sun, 18 May 2025 15:51:22 +0300 Subject: switch between display modes without restart, add missing string resource --- .../main/java/org/fox/ttrss/CommonActivity.java | 2 +- .../main/java/org/fox/ttrss/HeadlinesFragment.java | 2 +- .../main/java/org/fox/ttrss/OnlineActivity.java | 22 +++++++++++----------- org.fox.ttrss/src/main/res/values/strings.xml | 1 + 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/CommonActivity.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/CommonActivity.java index 749d1131..29bb1390 100755 --- a/org.fox.ttrss/src/main/java/org/fox/ttrss/CommonActivity.java +++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/CommonActivity.java @@ -312,7 +312,7 @@ public class CommonActivity extends AppCompatActivity implements SharedPreferenc setAppTheme(sharedPreferences); } - String[] filter = new String[] { "enable_cats", "headline_mode", "widget_update_interval", + String[] filter = new String[] { "enable_cats", "widget_update_interval", "headlines_swipe_to_dismiss", "headlines_mark_read_scroll", "headlines_request_size", "force_phone_layout", "open_on_startup"}; 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 608950d7..197a48bd 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 @@ -136,7 +136,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { public void initialize(Feed feed) { // clear loaded headlines before switching feed - if (feed != m_feed) + if (m_feed == null || feed.id != m_feed.id || feed.is_cat != m_feed.is_cat) Application.getArticlesModel().update(new ArticleList()); m_feed = feed; 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 467b0bfd..b0979d5b 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 @@ -27,6 +27,7 @@ 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; @@ -439,7 +440,7 @@ public class OnlineActivity extends CommonActivity { int selectedIndex = Arrays.asList(headlineModeValues).indexOf(headlineMode); MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this) - .setTitle(R.string.headlines_set_view_mode) + .setTitle(R.string.headlines_set_display_mode) .setSingleChoiceItems(headlineModeNames, selectedIndex, (dialog2, which) -> { dialog2.cancel(); @@ -448,20 +449,19 @@ public class OnlineActivity extends CommonActivity { editor.putString("headline_mode", headlineModeValues[which]); editor.apply(); - Intent intent = getIntent(); + FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); - Feed feed = hf.getFeed(); + HeadlinesFragment hfnew = new HeadlinesFragment(); - if (feed != null) { - intent.putExtra("feed_id", feed.id); - intent.putExtra("feed_is_cat", feed.is_cat); - intent.putExtra("feed_title", feed.title); - } + hfnew.initialize(hf.getFeed()); + hfnew.setSearchQuery(hf.getSearchQuery()); + + ft.replace(R.id.headlines_fragment, hfnew, FRAG_HEADLINES); - finish(); + ft.commit(); + + invalidateOptionsMenu(); - startActivity(intent); - overridePendingTransition(0, 0); }); Dialog dialog = builder.create(); diff --git a/org.fox.ttrss/src/main/res/values/strings.xml b/org.fox.ttrss/src/main/res/values/strings.xml index df82688b..3b49d1ae 100755 --- a/org.fox.ttrss/src/main/res/values/strings.xml +++ b/org.fox.ttrss/src/main/res/values/strings.xml @@ -305,4 +305,5 @@ Open on startup Operation completed successfully Error: 400 bad request + Set display mode -- cgit v1.2.3-54-g00ecf From 6a4940666a05191210dbf5732baa53ca5f94e2af Mon Sep 17 00:00:00 2001 From: Andrew Dolgov Date: Sun, 18 May 2025 16:09:50 +0300 Subject: use observable in a few more places, switch article display mode without reload --- .../src/main/java/org/fox/ttrss/ArticleModel.java | 6 ++ .../main/java/org/fox/ttrss/HeadlinesFragment.java | 23 ++---- .../main/java/org/fox/ttrss/OnlineActivity.java | 81 ++++++++++++---------- 3 files changed, 57 insertions(+), 53 deletions(-) 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 2b596828..b1f99208 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 @@ -70,6 +70,12 @@ public class ArticleModel extends AndroidViewModel implements ApiCommon.ApiCalle return m_articles; } + public void updateById(@NonNull Article article) { + int position = m_articles.getValue().getPositionById(article.id); + + if (position != -1) + update(position, article); + } public void update(int position, Article article) { m_articles.getValue().set(position, article); 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 197a48bd..3d923494 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 @@ -134,11 +134,6 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { } public void initialize(Feed feed) { - - // clear loaded headlines before switching feed - if (m_feed == null || feed.id != m_feed.id || feed.is_cat != m_feed.is_cat) - Application.getArticlesModel().update(new ArticleList()); - m_feed = feed; } @@ -219,10 +214,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { articleClone.unread = false; tmp.add(articleClone); - int position = articles.getPositionById(articleClone.id); - - if (position != -1) - Application.getArticlesModel().update(position, articleClone); + Application.getArticlesModel().updateById(articleClone); } } @@ -438,10 +430,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { articleClone.unread = false; - int position = Application.getArticles().getPositionById(a.id); - - if (position != -1) - Application.getArticlesModel().update(position, articleClone); + Application.getArticlesModel().updateById(articleClone); } m_readArticles.clear(); @@ -602,8 +591,10 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { if (m_activity instanceof DetailActivity && !append) return; - if (!append) + if (!append) { m_activeArticleId = -1; + Application.getArticlesModel().update(new ArticleList()); + } model.setSearchQuery(getSearchQuery()); model.startLoading(append, m_feed, m_activity.getResizeWidth()); @@ -664,10 +655,6 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { attachmentsView = v.findViewById(R.id.attachments); linkHost = v.findViewById(R.id.link_host); } - - public void clearAnimation() { - view.clearAnimation(); - } } private static class FlavorProgressTarget extends ProgressTarget { 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 b0979d5b..566aee2d 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 @@ -570,8 +570,6 @@ public class OnlineActivity extends CommonActivity { if (selectedArticle != null) { setArticleScore(selectedArticle); - - hf.notifyItemChanged(Application.getArticles().indexOf(selectedArticle)); } } return true; @@ -580,11 +578,13 @@ public class OnlineActivity extends CommonActivity { Article selectedArticle = Application.getArticles().getById(ap.getSelectedArticleId()); if (selectedArticle != null) { - selectedArticle.marked = !selectedArticle.marked; + Article articleClone = new Article(selectedArticle); + + articleClone.marked = !articleClone.marked; saveArticleMarked(selectedArticle); - hf.notifyItemChanged(Application.getArticles().indexOf(selectedArticle)); + Application.getArticlesModel().updateById(articleClone); } } return true; @@ -593,11 +593,13 @@ public class OnlineActivity extends CommonActivity { Article selectedArticle = Application.getArticles().getById(ap.getSelectedArticleId()); if (selectedArticle != null) { - selectedArticle.unread = !selectedArticle.unread; + Article articleClone = new Article(selectedArticle); + + articleClone.unread = !articleClone.unread; saveArticleUnread(selectedArticle); - hf.notifyItemChanged(Application.getArticles().indexOf(selectedArticle)); + Application.getArticlesModel().updateById(articleClone); } } return true; @@ -607,9 +609,11 @@ public class OnlineActivity extends CommonActivity { if (!selected.isEmpty()) { for (Article a : selected) { - a.unread = !a.unread; + Article articleClone = new Article(a); - hf.notifyItemChanged(Application.getArticles().indexOf(a)); + articleClone.unread = !articleClone.unread; + + Application.getArticlesModel().updateById(articleClone); } toggleArticlesUnread(selected); @@ -623,9 +627,11 @@ public class OnlineActivity extends CommonActivity { if (!selected.isEmpty()) { for (Article a : selected) { - a.marked = !a.marked; + Article articleClone = new Article(a); + + articleClone.marked = !articleClone.marked; - hf.notifyItemChanged(Application.getArticles().indexOf(a)); + Application.getArticlesModel().updateById(articleClone); } toggleArticlesMarked(selected); @@ -639,9 +645,11 @@ public class OnlineActivity extends CommonActivity { if (!selected.isEmpty()) { for (Article a : selected) { - a.published = !a.published; + Article articleClone = new Article(a); + + articleClone.published = !articleClone.published; - hf.notifyItemChanged(Application.getArticles().indexOf(a)); + Application.getArticlesModel().updateById(articleClone); } toggleArticlesPublished(selected); @@ -654,10 +662,12 @@ public class OnlineActivity extends CommonActivity { Article selectedArticle = Application.getArticles().getById(ap.getSelectedArticleId()); if (selectedArticle != null) { - selectedArticle.published = !selectedArticle.published; - saveArticlePublished(selectedArticle); + Article articleClone = new Article(selectedArticle); - hf.notifyItemChanged(Application.getArticles().indexOf(selectedArticle)); + articleClone.published = !articleClone.published; + saveArticlePublished(articleClone); + + Application.getArticlesModel().updateById(articleClone); } } return true; @@ -707,15 +717,14 @@ public class OnlineActivity extends CommonActivity { if (a.id == selectedArticleId) break; - if (a.unread) { - a.unread = false; - tmp.add(a); + Article articleClone = new Article(a); + + if (articleClone.unread) { + articleClone.unread = false; - if (hf != null) { - int position = Application.getArticles().indexOf(a); + tmp.add(articleClone); - hf.notifyItemChanged(position); - } + Application.getArticlesModel().updateById(articleClone); } } @@ -739,16 +748,6 @@ public class OnlineActivity extends CommonActivity { String note = topicEdit.getText().toString().trim(); saveArticleNote(article, note); - - int position = Application.getArticles().getPositionById(article.id); - - if (position != -1) { - HeadlinesFragment hf = (HeadlinesFragment) getSupportFragmentManager().findFragmentByTag(FRAG_HEADLINES); - if (hf != null) hf.notifyItemChanged(position); - - ArticlePager ap = (ArticlePager) getSupportFragmentManager().findFragmentByTag(FRAG_ARTICLE); - if (ap != null) ap.notifyItemChanged(position); - } }); builder.setNegativeButton(R.string.dialog_cancel, (dialog, which) -> { @@ -952,7 +951,11 @@ public class OnlineActivity extends CommonActivity { public void saveArticleNote(final Article article, final String note) { ApiRequest req = new ApiRequest(getApplicationContext()) { protected void onPostExecute(JsonElement result) { - article.note = note; + Article articleClone = new Article(article); + + articleClone.note = note; + + Application.getArticlesModel().updateById(articleClone); } }; @@ -983,9 +986,17 @@ public class OnlineActivity extends CommonActivity { (dialog, which) -> { try { -article.score = Integer.parseInt(edit.getText().toString()); + Article articleClone = new Article(article); + + articleClone.score = Integer.parseInt(edit.getText().toString()); + + saveArticleScore(articleClone); + + int position = Application.getArticles().getPositionById(articleClone.id); + + if (position != -1) + Application.getArticlesModel().update(position, articleClone); - saveArticleScore(article); } catch (NumberFormatException e) { toast(R.string.score_invalid); e.printStackTrace(); -- cgit v1.2.3-54-g00ecf From 48e84b1987573fcff92b69ba4cd9d154a27e1971 Mon Sep 17 00:00:00 2001 From: Andrew Dolgov Date: Sun, 18 May 2025 16:17:33 +0300 Subject: add option to enable secure window mode --- org.fox.ttrss/src/main/java/org/fox/ttrss/CommonActivity.java | 6 +++++- org.fox.ttrss/src/main/res/values/strings.xml | 2 ++ org.fox.ttrss/src/main/res/xml/preferences.xml | 6 ++++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/CommonActivity.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/CommonActivity.java index 29bb1390..12c74d2a 100755 --- a/org.fox.ttrss/src/main/java/org/fox/ttrss/CommonActivity.java +++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/CommonActivity.java @@ -24,6 +24,7 @@ import android.util.DisplayMetrics; import android.util.Log; import android.view.Display; import android.view.View; +import android.view.WindowManager; import android.widget.CheckBox; import androidx.activity.EdgeToEdge; @@ -236,6 +237,9 @@ public class CommonActivity extends AppCompatActivity implements SharedPreferenc m_prefs.registerOnSharedPreferenceChangeListener(this); + if (m_prefs.getBoolean("window_secure_mode", false)) + getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE); + setupWidgetUpdates(this); if (savedInstanceState == null) { @@ -314,7 +318,7 @@ public class CommonActivity extends AppCompatActivity implements SharedPreferenc String[] filter = new String[] { "enable_cats", "widget_update_interval", "headlines_swipe_to_dismiss", "headlines_mark_read_scroll", "headlines_request_size", - "force_phone_layout", "open_on_startup"}; + "force_phone_layout", "open_on_startup", "window_secure_mode" }; m_needRestart = Arrays.asList(filter).contains(key); } diff --git a/org.fox.ttrss/src/main/res/values/strings.xml b/org.fox.ttrss/src/main/res/values/strings.xml index 3b49d1ae..6f34b705 100755 --- a/org.fox.ttrss/src/main/res/values/strings.xml +++ b/org.fox.ttrss/src/main/res/values/strings.xml @@ -306,4 +306,6 @@ Operation completed successfully Error: 400 bad request Set display mode + Disables screenshots and hides window contents on non-secure displays + Secure window mode diff --git a/org.fox.ttrss/src/main/res/xml/preferences.xml b/org.fox.ttrss/src/main/res/xml/preferences.xml index 33095c5f..9e1f6642 100755 --- a/org.fox.ttrss/src/main/res/xml/preferences.xml +++ b/org.fox.ttrss/src/main/res/xml/preferences.xml @@ -67,6 +67,12 @@ android:key="force_phone_layout" android:summary="@string/force_phone_layout_summary" android:title="@string/force_phone_layout" /> + + Date: Sun, 18 May 2025 16:20:34 +0300 Subject: hide flavor image holder in case of recycled view --- org.fox.ttrss/src/main/java/org/fox/ttrss/HeadlinesFragment.java | 1 + 1 file changed, 1 insertion(+) 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 3d923494..04d1e93b 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 @@ -987,6 +987,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { holder.flavorVideoKindView.setVisibility(View.GONE); holder.flavorImageOverflow.setVisibility(View.GONE); holder.flavorVideoView.setVisibility(View.GONE); + holder.flavorImageHolder.setVisibility(View.GONE); // this is needed if our flavor image goes behind base listview element holder.headlineHeader.setOnClickListener(v -> { -- cgit v1.2.3-54-g00ecf From d6fd7f09a45cbe480e73dcc901b68a85905a2b6c Mon Sep 17 00:00:00 2001 From: Andrew Dolgov Date: Sun, 18 May 2025 16:28:24 +0300 Subject: bring back circle indicator for gallery --- org.fox.ttrss/build.gradle | 2 +- org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryActivity.java | 9 +++++++++ org.fox.ttrss/src/main/res/layout/activity_gallery.xml | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/org.fox.ttrss/build.gradle b/org.fox.ttrss/build.gradle index 03be018f..72c971c5 100755 --- a/org.fox.ttrss/build.gradle +++ b/org.fox.ttrss/build.gradle @@ -151,7 +151,7 @@ dependencies { implementation 'com.github.natario1:NestedScrollCoordinatorLayout:5a33a7dbd8' implementation 'com.google.android.material:material:1.12.0' implementation 'com.google.code.gson:gson:2.10.1' - implementation 'me.relex:circleindicator:1.2.2@aar' + implementation 'me.relex:circleindicator:2.1.6' implementation 'com.github.amulyakhare:TextDrawable:558677ea31' implementation 'com.telefonica:nestedscrollwebview:0.1.6' implementation 'androidx.preference:preference:1.2.1' diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryActivity.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryActivity.java index fb87bc27..d03f7349 100755 --- a/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryActivity.java +++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryActivity.java @@ -23,6 +23,7 @@ import androidx.fragment.app.FragmentActivity; import androidx.lifecycle.ViewModelProvider; import androidx.preference.PreferenceManager; import androidx.recyclerview.widget.DiffUtil; +import androidx.recyclerview.widget.PagerSnapHelper; import androidx.viewpager2.widget.ViewPager2; import com.bumptech.glide.Glide; @@ -35,6 +36,9 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutionException; +import me.relex.circleindicator.CircleIndicator2; +import me.relex.circleindicator.CircleIndicator3; + public class GalleryActivity extends CommonActivity { private final String TAG = this.getClass().getSimpleName(); @@ -186,6 +190,11 @@ public class GalleryActivity extends CommonActivity { }); }); + CircleIndicator3 indicator = findViewById(R.id.gallery_pager_indicator); + indicator.setViewPager(m_pager); + + m_adapter.registerAdapterDataObserver(indicator.getAdapterDataObserver()); + } else { // ArrayList list = savedInstanceState.getParcelableArrayList("m_items"); m_title = savedInstanceState.getString("m_title"); diff --git a/org.fox.ttrss/src/main/res/layout/activity_gallery.xml b/org.fox.ttrss/src/main/res/layout/activity_gallery.xml index b38f0408..1a489bfc 100644 --- a/org.fox.ttrss/src/main/res/layout/activity_gallery.xml +++ b/org.fox.ttrss/src/main/res/layout/activity_gallery.xml @@ -38,7 +38,7 @@ android:layout_marginLeft="@dimen/activity_horizontal_margin" android:layout_marginRight="@dimen/activity_horizontal_margin" /> - Date: Sun, 18 May 2025 16:31:50 +0300 Subject: set indicator color to tertiary --- org.fox.ttrss/src/main/res/drawable/indicator_dot.xml | 8 ++++++++ org.fox.ttrss/src/main/res/layout/activity_gallery.xml | 1 + 2 files changed, 9 insertions(+) create mode 100644 org.fox.ttrss/src/main/res/drawable/indicator_dot.xml diff --git a/org.fox.ttrss/src/main/res/drawable/indicator_dot.xml b/org.fox.ttrss/src/main/res/drawable/indicator_dot.xml new file mode 100644 index 00000000..b6dd6c82 --- /dev/null +++ b/org.fox.ttrss/src/main/res/drawable/indicator_dot.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/org.fox.ttrss/src/main/res/layout/activity_gallery.xml b/org.fox.ttrss/src/main/res/layout/activity_gallery.xml index 1a489bfc..f1ad3f8c 100644 --- a/org.fox.ttrss/src/main/res/layout/activity_gallery.xml +++ b/org.fox.ttrss/src/main/res/layout/activity_gallery.xml @@ -45,6 +45,7 @@ android:layout_marginBottom="55dp" android:layout_alignParentBottom="true" android:layout_alignParentLeft="true" + app:ci_drawable="@drawable/indicator_dot" android:layout_alignParentStart="true" android:visibility="visible" /> -- cgit v1.2.3-54-g00ecf From 26f73d46578856538900f6488ec15cbf1c92ebc6 Mon Sep 17 00:00:00 2001 From: Andrew Dolgov Date: Sun, 18 May 2025 16:33:52 +0300 Subject: fix gallery activity not surviving theme change --- .../main/java/org/fox/ttrss/GalleryActivity.java | 76 +++++++++++----------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryActivity.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryActivity.java index d03f7349..0a19702e 100755 --- a/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryActivity.java +++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryActivity.java @@ -133,11 +133,16 @@ public class GalleryActivity extends CommonActivity { if (savedInstanceState == null) { m_title = getIntent().getStringExtra("title"); m_content = getIntent().getStringExtra("content"); + } else { + // ArrayList list = savedInstanceState.getParcelableArrayList("m_items"); + m_title = savedInstanceState.getString("m_title"); + m_content = savedInstanceState.getString("m_content"); + } - GalleryModel model = new ViewModelProvider(this).get(GalleryModel.class); + GalleryModel model = new ViewModelProvider(this).get(GalleryModel.class); - // this should be dealt with first so that transition completes properly - String firstSrc = getIntent().getStringExtra("firstSrc"); + // this should be dealt with first so that transition completes properly + String firstSrc = getIntent().getStringExtra("firstSrc"); /* but what about videos? if (firstSrc != null) { List initialItems = new ArrayList(); @@ -149,57 +154,52 @@ public class GalleryActivity extends CommonActivity { model.update(initialItems); } */ - model.collectItems(m_content, firstSrc); + model.collectItems(m_content, firstSrc); - model.getItemsToCheck().observe(this, itemsToCheck -> { - Log.d(TAG, "observed items to check=" + itemsToCheck); + model.getItemsToCheck().observe(this, itemsToCheck -> { + Log.d(TAG, "observed items to check=" + itemsToCheck); - m_checkProgress.setMax(itemsToCheck); - m_checkProgress.setProgress(0); - }); + m_checkProgress.setMax(itemsToCheck); + m_checkProgress.setProgress(0); + }); - model.getIsChecking().observe(this, isChecking -> { - Log.d(TAG, "observed isChecking=" + isChecking); + model.getIsChecking().observe(this, isChecking -> { + Log.d(TAG, "observed isChecking=" + isChecking); - m_checkProgress.setVisibility(isChecking ? View.VISIBLE : View.GONE); - }); + m_checkProgress.setVisibility(isChecking ? View.VISIBLE : View.GONE); + }); - model.getCheckProgress().observe(this, progress -> { - Log.d(TAG, "observed item check progress=" + progress); + model.getCheckProgress().observe(this, progress -> { + Log.d(TAG, "observed item check progress=" + progress); - m_checkProgress.setProgress(progress); - }); + m_checkProgress.setProgress(progress); + }); - model.getItems().observe(this, galleryEntries -> { - Log.d(TAG, "observed gallery entries=" + galleryEntries + " firstSrc=" + firstSrc); + model.getItems().observe(this, galleryEntries -> { + Log.d(TAG, "observed gallery entries=" + galleryEntries + " firstSrc=" + firstSrc); - m_adapter.submitList(galleryEntries, () -> { - if (!m_firstWasSelected) { - for (GalleryEntry entry : galleryEntries) { - if (entry.url.equals(firstSrc)) { - int position = galleryEntries.indexOf(entry); + m_adapter.submitList(galleryEntries, () -> { + if (!m_firstWasSelected) { + for (GalleryEntry entry : galleryEntries) { + if (entry.url.equals(firstSrc)) { + int position = galleryEntries.indexOf(entry); - Log.d(TAG, "selecting first src=" + firstSrc + " pos=" + position); - m_pager.setCurrentItem(position); + Log.d(TAG, "selecting first src=" + firstSrc + " pos=" + position); + m_pager.setCurrentItem(position); - m_firstWasSelected = true; - break; - } + m_firstWasSelected = true; + break; } } - }); + } }); + }); - CircleIndicator3 indicator = findViewById(R.id.gallery_pager_indicator); - indicator.setViewPager(m_pager); + CircleIndicator3 indicator = findViewById(R.id.gallery_pager_indicator); + indicator.setViewPager(m_pager); - m_adapter.registerAdapterDataObserver(indicator.getAdapterDataObserver()); + m_adapter.registerAdapterDataObserver(indicator.getAdapterDataObserver()); - } else { - // ArrayList list = savedInstanceState.getParcelableArrayList("m_items"); - m_title = savedInstanceState.getString("m_title"); - m_content = savedInstanceState.getString("m_content"); - } findViewById(R.id.gallery_overflow).setOnClickListener(v -> { try { -- cgit v1.2.3-54-g00ecf From 065f2ed37d0951038b6eec20079da35e23d51158 Mon Sep 17 00:00:00 2001 From: Andrew Dolgov Date: Sun, 18 May 2025 16:40:55 +0300 Subject: do nothing if setActiveArticleId passes invalid id, prevent force-clearing article list on refresh for now --- .../src/main/java/org/fox/ttrss/HeadlinesFragment.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 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 04d1e93b..a62fa0dc 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 @@ -592,8 +592,8 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { return; if (!append) { - m_activeArticleId = -1; - Application.getArticlesModel().update(new ArticleList()); + setActiveArticleId(-1); + //Application.getArticlesModel().update(new ArticleList()); } model.setSearchQuery(getSearchQuery()); @@ -1562,12 +1562,14 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { if (oldPosition != -1) m_adapter.notifyItemChanged(oldPosition); - m_adapter.notifyItemChanged(newPosition); + if (newPosition != -1) { + m_adapter.notifyItemChanged(newPosition); - scrollToArticleId(articleId); + scrollToArticleId(articleId); - if (newPosition >= articles.size() - 5) - new Handler().postDelayed(() -> refresh(true), 0); + if (newPosition >= articles.size() - 5) + new Handler().postDelayed(() -> refresh(true), 0); + } } } -- cgit v1.2.3-54-g00ecf From 4a1340567628cc8347482da0a01e3269db25188a Mon Sep 17 00:00:00 2001 From: Andrew Dolgov Date: Sun, 18 May 2025 16:44:34 +0300 Subject: revert previous because reordering seems to do something weird with onBindViewHolder() position --- org.fox.ttrss/src/main/java/org/fox/ttrss/HeadlinesFragment.java | 4 ++-- 1 file changed, 2 insertions(+), 2 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 a62fa0dc..968a32e8 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 @@ -593,7 +593,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { if (!append) { setActiveArticleId(-1); - //Application.getArticlesModel().update(new ArticleList()); + Application.getArticlesModel().update(new ArticleList()); } model.setSearchQuery(getSearchQuery()); @@ -793,7 +793,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { // 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 payloads) { if (!payloads.isEmpty()) { - Log.d(TAG, "onBindViewHolder, payloads: " + payloads); + Log.d(TAG, "onBindViewHolder, payloads=" + payloads + " position=" + position); final Article article = getItem(position); -- cgit v1.2.3-54-g00ecf From 16b191cf4594df8b7ed3462836fd57859729c60f Mon Sep 17 00:00:00 2001 From: Andrew Dolgov Date: Sun, 18 May 2025 17:37:25 +0300 Subject: fix bottombar toggle unread not using observable --- org.fox.ttrss/src/main/java/org/fox/ttrss/DetailActivity.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/DetailActivity.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/DetailActivity.java index 10b06022..9b07de38 100755 --- a/org.fox.ttrss/src/main/java/org/fox/ttrss/DetailActivity.java +++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/DetailActivity.java @@ -90,12 +90,12 @@ public class DetailActivity extends OnlineActivity implements HeadlinesEventList return true; } else if (itemId == R.id.toggle_unread) { - article.unread = !article.unread; + Article articleClone = new Article(article); + + articleClone.unread = !articleClone.unread; saveArticleUnread(article); - if (hf != null) { - hf.notifyItemChanged(Application.getArticles().indexOf(article)); - } + Application.getArticlesModel().updateById(articleClone); } } -- cgit v1.2.3-54-g00ecf From b5c26f8dcf4563df56142b15047c78cc304bdd77 Mon Sep 17 00:00:00 2001 From: Andrew Dolgov Date: Sun, 18 May 2025 17:54:41 +0300 Subject: fix several more unread-related observable triggers --- .../main/java/org/fox/ttrss/DetailActivity.java | 12 +++++--- .../main/java/org/fox/ttrss/MasterActivity.java | 33 ++++++++++------------ 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/DetailActivity.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/DetailActivity.java index 9b07de38..59ff4e66 100755 --- a/org.fox.ttrss/src/main/java/org/fox/ttrss/DetailActivity.java +++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/DetailActivity.java @@ -93,7 +93,7 @@ public class DetailActivity extends OnlineActivity implements HeadlinesEventList Article articleClone = new Article(article); articleClone.unread = !articleClone.unread; - saveArticleUnread(article); + saveArticleUnread(articleClone); Application.getArticlesModel().updateById(articleClone); } @@ -272,9 +272,13 @@ public class DetailActivity extends OnlineActivity implements HeadlinesEventList @Override public void onArticleSelected(Article article, boolean open) { - if (article.unread) { - article.unread = false; - saveArticleUnread(article); + Article articleClone = new Article(article); + + if (articleClone.unread) { + articleClone.unread = false; + saveArticleUnread(articleClone); + + Application.getArticlesModel().updateById(articleClone); } if (!getSupportActionBar().isShowing()) getSupportActionBar().show(); diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/MasterActivity.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/MasterActivity.java index f2ef3aa0..19cfbe0c 100755 --- a/org.fox.ttrss/src/main/java/org/fox/ttrss/MasterActivity.java +++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/MasterActivity.java @@ -417,26 +417,28 @@ public class MasterActivity extends OnlineActivity implements HeadlinesEventList } public void onArticleSelected(Article article, boolean open) { + Article articleClone = new Article(article); + + if (articleClone.unread) { + articleClone.unread = false; + saveArticleUnread(articleClone); + + Application.getArticlesModel().updateById(articleClone); + } + if (open) { - boolean alwaysOpenUri = m_prefs.getBoolean("always_open_uri", false); - if (alwaysOpenUri) { - if (article.unread) { - article.unread = false; - saveArticleUnread(article); - } + HeadlinesFragment hf = (HeadlinesFragment) getSupportFragmentManager().findFragmentByTag(FRAG_HEADLINES); - HeadlinesFragment hf = (HeadlinesFragment) getSupportFragmentManager().findFragmentByTag(FRAG_HEADLINES); + if (m_prefs.getBoolean("always_open_uri", false)) { - if (hf != null) { + if (hf != null) { hf.setActiveArticleId(article.id); } openUri(Uri.parse(article.link)); - } - else { - HeadlinesFragment hf = (HeadlinesFragment) getSupportFragmentManager().findFragmentByTag(FRAG_HEADLINES); + } else if (hf != null) { - Intent intent = new Intent(MasterActivity.this, DetailActivity.class); + Intent intent = new Intent(MasterActivity.this, DetailActivity.class); intent.putExtra("feed", hf.getFeed()); intent.putExtra("searchQuery", hf.getSearchQuery()); intent.putExtra("openedArticleId", article.id); @@ -446,12 +448,7 @@ public class MasterActivity extends OnlineActivity implements HeadlinesEventList } } else { invalidateOptionsMenu(); - - if (article.unread) { - article.unread = false; - saveArticleUnread(article); - } - } + } } @Override -- cgit v1.2.3-54-g00ecf From ad2efff97e07fa4b227046b9538c394484d3e81f Mon Sep 17 00:00:00 2001 From: Andrew Dolgov Date: Sun, 18 May 2025 22:08:51 +0300 Subject: fix position desync if recycler view reorders, only set headline row click handlers once on view create --- .../main/java/org/fox/ttrss/HeadlinesFragment.java | 471 ++++++++++++--------- .../main/java/org/fox/ttrss/OnlineActivity.java | 2 +- .../src/main/res/layout/headlines_row.xml | 1 - 3 files changed, 266 insertions(+), 208 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 968a32e8..c57f11c5 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 @@ -160,7 +160,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { m_activity.saveArticleUnread(articleClone); - Application.getArticlesModel().update(position, articleClone); + Application.getArticlesModel().updateById(articleClone); return true; } else if (itemId == R.id.headlines_article_link_copy) { @@ -175,7 +175,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { m_activity.saveArticleUnread(articleClone); - Application.getArticlesModel().update(position, articleClone); + Application.getArticlesModel().updateById(articleClone); } return true; } else if (itemId == R.id.headlines_share_article) { @@ -591,10 +591,8 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { if (m_activity instanceof DetailActivity && !append) return; - if (!append) { + if (!append) setActiveArticleId(-1); - Application.getArticlesModel().update(new ArticleList()); - } model.setSearchQuery(getSearchQuery()); model.startLoading(append, m_feed, m_activity.getResizeWidth()); @@ -768,8 +766,9 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { m_cmgr = (ConnectivityManager) m_activity.getSystemService(Context.CONNECTIVITY_SERVICE); } + @NonNull @Override - public ArticleViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + public ArticleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { int layoutId = m_compactLayoutMode ? R.layout.headlines_row_compact : R.layout.headlines_row; @@ -779,7 +778,247 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { View v = LayoutInflater.from(parent.getContext()).inflate(layoutId, parent, false); - return new ArticleViewHolder(v); + ArticleViewHolder holder = new ArticleViewHolder(v); + + // set on click handlers once when view is created + + holder.view.setOnClickListener(view -> { + int position = m_list.getChildAdapterPosition(view); + + if (position != -1) { + Article article = m_adapter.getItem(position); + + m_listener.onArticleSelected(article); + + // only set active article when it makes sense (in DetailActivity) + if (getActivity() instanceof DetailActivity) { + m_activeArticleId = article.id; + + m_adapter.notifyItemChanged(position); + } + } + }); + + holder.view.setOnLongClickListener(view -> { + m_list.showContextMenuForChild(view); + return true; + }); + + // block footer clicks to make button/selection clicking easier + if (holder.headlineFooter != null) { + holder.headlineFooter.setOnClickListener(view -> { + // + }); + } + + if (holder.attachmentsView != null) { + holder.attachmentsView.setOnClickListener(view -> { + int position = m_list.getChildAdapterPosition(holder.view); + + if (position != -1) { + Article article = m_adapter.getItem(position); + m_activity.displayAttachments(article); + } + }); + } + + if (holder.flavorImageView != null) { + holder.flavorImageView.setOnClickListener(view -> { + int position = m_list.getChildAdapterPosition(holder.view); + + if (position != -1) { + Article article = m_adapter.getItem(position); + openGalleryForType(article, holder, holder.flavorImageView); + } + }); + } + + if (holder.flavorImageOverflow != null) { + holder.flavorImageOverflow.setOnClickListener(view -> { + PopupMenu popup = new PopupMenu(getContext(), holder.flavorImageOverflow); + MenuInflater inflater = popup.getMenuInflater(); + inflater.inflate(R.menu.content_gallery_entry, popup.getMenu()); + + int position = m_list.getChildAdapterPosition(holder.view); + + if (position != -1) { + Article article = m_adapter.getItem(position); + + popup.setOnMenuItemClickListener(item -> { + + Uri mediaUri = Uri.parse(article.flavorStreamUri != null ? article.flavorStreamUri : + article.flavorImageUri); + + int itemId = item.getItemId(); + if (itemId == R.id.article_img_open) { + m_activity.openUri(mediaUri); + return true; + } else if (itemId == R.id.article_img_copy) { + m_activity.copyToClipboard(mediaUri.toString()); + return true; + } else if (itemId == R.id.article_img_share) { + m_activity.shareImageFromUri(mediaUri.toString()); + return true; + } else if (itemId == R.id.article_img_share_url) { + m_activity.shareText(mediaUri.toString()); + return true; + } else if (itemId == R.id.article_img_view_caption) { + m_activity.displayImageCaption(article.flavorImageUri, article.content); + return true; + } + return false; + }); + + popup.show(); + } + }); + + holder.flavorImageView.setOnLongClickListener(view -> { + m_list.showContextMenuForChild(view); + return true; + }); + } + + if (holder.menuButtonView != null) { + holder.menuButtonView.setOnClickListener(view -> { + + int position = m_list.getChildAdapterPosition(holder.view); + + if (position != -1) { + PopupMenu popup = new PopupMenu(getContext(), view); + MenuInflater inflater = popup.getMenuInflater(); + inflater.inflate(R.menu.context_headlines, popup.getMenu()); + + popup.getMenu().findItem(R.id.article_set_labels).setEnabled(m_activity.getApiLevel() >= 1); + popup.getMenu().findItem(R.id.article_edit_note).setEnabled(m_activity.getApiLevel() >= 1); + + popup.setOnMenuItemClickListener(item -> onArticleMenuItemSelected(item, + getItem(position), + m_list.getChildAdapterPosition(holder.view))); + + popup.show(); + } + }); + } + + if (holder.markedView != null) { + holder.markedView.setOnClickListener(view -> { + int position = m_list.getChildAdapterPosition(holder.view); + + if (position != -1) { + Article article = new Article(getItem(position)); + article.marked = !article.marked; + + m_activity.saveArticleMarked(article); + + Application.getArticlesModel().updateById(article); + } + }); + } + + if (holder.selectionBoxView != null) { + holder.selectionBoxView.setOnClickListener(view -> { + int position = m_list.getChildAdapterPosition(holder.view); + + if (position != -1) { + Article article = new Article(getItem(position)); + + CheckBox cb = (CheckBox) view; + + article.selected = cb.isChecked(); + + Application.getArticlesModel().updateById(article); + + m_listener.onArticleListSelectionChange(); + } + }); + } + + if (holder.publishedView != null) { + holder.publishedView.setOnClickListener(view -> { + int position = m_list.getChildAdapterPosition(holder.view); + + if (position != -1) { + Article article = new Article(getItem(position)); + article.published = !article.published; + + m_activity.saveArticlePublished(article); + + Application.getArticlesModel().updateById(article); + } + }); + } + + if (holder.textImage != null) { + holder.textImage.setOnClickListener(view -> { + int position = m_list.getChildAdapterPosition(holder.view); + + if (position != -1) { + Article article = new Article(getItem(position)); + article.selected = !article.selected; + + Application.getArticlesModel().updateById(article); + + // updateTextCheckedState(holder, position); + + m_listener.onArticleListSelectionChange(); + } + }); + + holder.textImage.setOnLongClickListener(view -> { + int position = m_list.getChildAdapterPosition(holder.view); + + if (position != -1) { + Article article = getItem(position); + + openGalleryForType(article, holder, holder.textImage); + } + + return true; + }); + } + + if (holder.scoreView != null) { + if (m_activity.getApiLevel() >= 16) { + holder.scoreView.setOnClickListener(view -> { + int position = m_list.getChildAdapterPosition(holder.view); + + if (position != -1) { + + Article article = new Article(getItem(position)); + + 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); + + Application.getArticlesModel().updateById(article); + + } 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(); + } + }); + } else { + holder.scoreView.setVisibility(View.GONE); + } + } + + return holder; } @Override public void onViewRecycled(ArticleViewHolder holder){ @@ -805,17 +1044,17 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { updateUnreadView(article, holder); break; case MARKED: - updateMarkedView(article, holder, position); + updateMarkedView(article, holder); break; case SELECTED: - updateSelectedView(article, holder, position); - updateTextImage(article, holder, position); + updateSelectedView(article, holder); + updateTextImage(article, holder); break; case PUBLISHED: - updatePublishedView(article, holder, position); + updatePublishedView(article, holder); break; case SCORE: - updateScoreView(article, holder, position); + updateScoreView(article, holder); break; } } @@ -881,31 +1120,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { if (article.id < 0) return; updateUnreadView(article, holder); - - holder.view.setOnLongClickListener(v -> { - m_list.showContextMenuForChild(v); - return true; - }); - - holder.view.setOnClickListener(v -> { - m_listener.onArticleSelected(article); - - // only set active article when it makes sense (in DetailActivity) - if (getActivity() instanceof DetailActivity) { - m_activeArticleId = article.id; - - m_adapter.notifyItemChanged(position); - } - }); - - // block footer clicks to make button/selection clicking easier - if (holder.headlineFooter != null) { - holder.headlineFooter.setOnClickListener(view -> { - // - }); - } - - updateTextImage(article, holder, position); + updateTextImage(article, holder); if (holder.titleView != null) { holder.titleView.setText(Html.fromHtml(article.title)); @@ -931,16 +1146,13 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { } } - updateMarkedView(article, holder, position); - updateScoreView(article, holder, position); - updatePublishedView(article, holder, position); + updateMarkedView(article, holder); + updateScoreView(article, holder); + updatePublishedView(article, holder); if (holder.attachmentsView != null) { if (article.attachments != null && !article.attachments.isEmpty()) { holder.attachmentsView.setVisibility(View.VISIBLE); - - holder.attachmentsView.setOnClickListener(v -> m_activity.displayAttachments(article)); - } else { holder.attachmentsView.setVisibility(View.GONE); } @@ -989,60 +1201,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { holder.flavorVideoView.setVisibility(View.GONE); holder.flavorImageHolder.setVisibility(View.GONE); - // this is needed if our flavor image goes behind base listview element - holder.headlineHeader.setOnClickListener(v -> { - m_listener.onArticleSelected(article); - }); - - holder.headlineHeader.setOnLongClickListener(v -> { - m_list.showContextMenuForChild(holder.view); - - return true; - }); - if (canShowFlavorImage() && article.flavorImageUri != null && holder.flavorImageView != null) { - - holder.flavorImageView.setOnClickListener(view -> openGalleryForType(article, holder, holder.flavorImageView)); - - if (holder.flavorImageOverflow != null) { - holder.flavorImageOverflow.setOnClickListener(v -> { - PopupMenu popup = new PopupMenu(getActivity(), holder.flavorImageOverflow); - MenuInflater inflater = popup.getMenuInflater(); - inflater.inflate(R.menu.content_gallery_entry, popup.getMenu()); - - popup.setOnMenuItemClickListener(item -> { - - Uri mediaUri = Uri.parse(article.flavorStreamUri != null ? article.flavorStreamUri : article.flavorImageUri); - - int itemId = item.getItemId(); - if (itemId == R.id.article_img_open) { - m_activity.openUri(mediaUri); - return true; - } else if (itemId == R.id.article_img_copy) { - m_activity.copyToClipboard(mediaUri.toString()); - return true; - } else if (itemId == R.id.article_img_share) { - m_activity.shareImageFromUri(mediaUri.toString()); - return true; - } else if (itemId == R.id.article_img_share_url) { - m_activity.shareText(mediaUri.toString()); - return true; - } else if (itemId == R.id.article_img_view_caption) { - m_activity.displayImageCaption(article.flavorImageUri, article.content); - return true; - } - return false; - }); - - popup.show(); - }); - - holder.flavorImageView.setOnLongClickListener(v -> { - m_list.showContextMenuForChild(holder.view); - return true; - }); - } - int maxImageHeight = (int) (m_screenHeight * 0.5f); // we also downsample below using glide to save RAM @@ -1196,93 +1355,35 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { holder.dateView.setText(df.format(d)); } - updateSelectedView(article, holder, position); - - if (holder.menuButtonView != null) { - holder.menuButtonView.setOnClickListener(v -> { - - PopupMenu popup = new PopupMenu(getActivity(), v); - MenuInflater inflater = popup.getMenuInflater(); - inflater.inflate(R.menu.context_headlines, popup.getMenu()); - - popup.getMenu().findItem(R.id.article_set_labels).setEnabled(m_activity.getApiLevel() >= 1); - popup.getMenu().findItem(R.id.article_edit_note).setEnabled(m_activity.getApiLevel() >= 1); + updateSelectedView(article, holder); - popup.setOnMenuItemClickListener(item -> onArticleMenuItemSelected(item, - getItem(position), - m_list.getChildAdapterPosition(holder.view))); - popup.show(); - }); - } } - private void updateMarkedView(Article article, ArticleViewHolder holder, int position) { + private void updateMarkedView(Article article, ArticleViewHolder holder) { 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_cslTertiary : m_cslPrimary); - - 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) { + private void updateTextImage(Article article, ArticleViewHolder holder) { 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); + updateTextCheckedState(article, holder); - return true; - }); - - } + ViewCompat.setTransitionName(holder.textImage, + "gallery:" + article.flavorImageUri); } } - private void updateSelectedView(Article article, ArticleViewHolder holder, int position) { + private void updateSelectedView(Article article, ArticleViewHolder holder) { if (holder.selectionBoxView != null) { holder.selectionBoxView.setChecked(article.selected); - holder.selectionBoxView.setOnClickListener(view -> { - Article selectedArticle = new Article(getItem(position)); - - Log.d(TAG, "selectionCb onClick pos=" + position + " article=" + article); - - CheckBox cb = (CheckBox)view; - selectedArticle.selected = cb.isChecked(); - - Application.getArticlesModel().update(position, selectedArticle); - - m_listener.onArticleListSelectionChange(); - }); } } - private void updateScoreView(Article article, ArticleViewHolder holder, int position) { + private void updateScoreView(Article article, ArticleViewHolder holder) { if (holder.scoreView != null) { int scoreDrawable = R.drawable.baseline_trending_flat_24; @@ -1297,40 +1398,10 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { holder.scoreView.setIconTint(m_cslTertiary); else holder.scoreView.setIconTint(m_cslPrimary); - - if (m_activity.getApiLevel() >= 16) { - holder.scoreView.setOnClickListener(v -> { - Article selectedArticle = new Article(getItem(position)); - - final EditText edit = new EditText(getActivity()); - edit.setText(String.valueOf(selectedArticle.score)); - - MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(getContext()) - .setTitle(R.string.score_for_this_article) - .setPositiveButton(R.string.set_score, - (dialog, which) -> { - try { - selectedArticle.score = Integer.parseInt(edit.getText().toString()); - m_activity.saveArticleScore(article); - - Application.getArticlesModel().update(position, selectedArticle); - - } 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) { + private void updatePublishedView(final Article article, ArticleViewHolder holder) { if (holder.publishedView != null) { // otherwise we just use tinting in actionbar if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { @@ -1338,15 +1409,6 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { } holder.publishedView.setIconTint(article.published ? m_cslTertiary : m_cslPrimary); - - holder.publishedView.setOnClickListener(v -> { - Article selectedArticle = new Article(getItem(position)); - selectedArticle.published = !selectedArticle.published; - - m_activity.saveArticlePublished(selectedArticle); - - Application.getArticlesModel().update(position, selectedArticle); - }); } } @@ -1424,9 +1486,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { } } - private void updateTextCheckedState(final ArticleViewHolder holder, int position) { - Article article = getItem(position); - + private void updateTextCheckedState(final Article article, final ArticleViewHolder holder) { String tmp = !article.title.isEmpty() ? article.title.substring(0, 1).toUpperCase() : "?"; if (article.selected) { @@ -1439,7 +1499,6 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { if (!canShowFlavorImage() || article.flavorImage == null) { holder.textImage.setImageDrawable(textDrawable); - } else { Glide.with(HeadlinesFragment.this) .load(article.flavorImageUri) 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 566aee2d..3be7c454 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 @@ -995,7 +995,7 @@ public class OnlineActivity extends CommonActivity { int position = Application.getArticles().getPositionById(articleClone.id); if (position != -1) - Application.getArticlesModel().update(position, articleClone); + Application.getArticlesModel().updateById(articleClone); } catch (NumberFormatException e) { toast(R.string.score_invalid); diff --git a/org.fox.ttrss/src/main/res/layout/headlines_row.xml b/org.fox.ttrss/src/main/res/layout/headlines_row.xml index 5e4eca4e..98a66299 100755 --- a/org.fox.ttrss/src/main/res/layout/headlines_row.xml +++ b/org.fox.ttrss/src/main/res/layout/headlines_row.xml @@ -25,7 +25,6 @@ android:id="@+id/headline_header" android:layout_width="match_parent" android:layout_height="wrap_content" - android:clickable="true" android:layout_span="2" android:padding="16dp"> -- cgit v1.2.3-54-g00ecf From c433e6f7b3c96acddce7591f4642dbdc84b37668 Mon Sep 17 00:00:00 2001 From: Andrew Dolgov Date: Sun, 18 May 2025 22:21:01 +0300 Subject: split some more headline row view stuff into separate methods --- .../main/java/org/fox/ttrss/HeadlinesFragment.java | 202 +++++++++++---------- 1 file changed, 107 insertions(+), 95 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 c57f11c5..0fbd3721 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 @@ -82,7 +82,6 @@ import org.fox.ttrss.util.ArticleDiffItemCallback; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; -import java.lang.reflect.Type; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; @@ -622,7 +621,6 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { public View flavorImageOverflow; public TextureView flavorVideoView; public MaterialButton attachmentsView; - int articleId; public TextView linkHost; public ArticleViewHolder(View v) { @@ -712,6 +710,9 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { private final int m_screenWidth; private final int m_screenHeight; + private final int m_headlineSmallFontSize; + private final int m_headlineFontSize; + private final ConnectivityManager m_cmgr; private boolean canShowFlavorImage() { @@ -763,6 +764,9 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { m_colorTertiaryContainer = colorFromAttr(R.attr.colorTertiaryContainer); m_colorOnTertiaryContainer = colorFromAttr(R.attr.colorOnTertiaryContainer); + m_headlineFontSize = m_prefs.getInt("headlines_font_size_sp_int", 13); + m_headlineSmallFontSize = Math.max(10, Math.min(18, m_headlineFontSize - 2)); + m_cmgr = (ConnectivityManager) m_activity.getSystemService(Context.CONNECTIVITY_SERVICE); } @@ -1021,7 +1025,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { return holder; } - @Override public void onViewRecycled(ArticleViewHolder holder){ + @Override public void onViewRecycled(@NonNull ArticleViewHolder holder){ super.onViewRecycled(holder); if (holder.flavorImageView != null) @@ -1030,7 +1034,7 @@ 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 payloads) { + public void onBindViewHolder(@NonNull final ArticleViewHolder holder, final int position, final List payloads) { if (!payloads.isEmpty()) { Log.d(TAG, "onBindViewHolder, payloads=" + payloads + " position=" + position); @@ -1063,7 +1067,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { } } - private void updateUnreadView(final Article article, ArticleViewHolder holder) { + private void updateUnreadView(final Article article, final ArticleViewHolder holder) { if (m_compactLayoutMode) { holder.view.setBackgroundColor(article.unread ? m_colorSurfaceContainerLowest : 0); } else { @@ -1080,7 +1084,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { updateActiveView(article, holder); } - private void updateActiveView(final Article article, ArticleViewHolder holder) { + private void updateActiveView(final Article article, final ArticleViewHolder holder) { if (article.id == m_activeArticleId) { holder.view.setBackgroundColor(m_colorTertiaryContainer); @@ -1100,14 +1104,9 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { } @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)); - + public void onBindViewHolder(@NonNull final ArticleViewHolder holder, int position) { Article article = getItem(position); - holder.articleId = article.id; - if (article.id == Article.TYPE_AMR_FOOTER && m_prefs.getBoolean("headlines_mark_read_scroll", false)) { WindowManager wm = (WindowManager) m_activity.getSystemService(Context.WINDOW_SERVICE); Display display = wm.getDefaultDisplay(); @@ -1121,73 +1120,16 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { updateUnreadView(article, holder); updateTextImage(article, holder); - - if (holder.titleView != null) { - holder.titleView.setText(Html.fromHtml(article.title)); - holder.titleView.setTextSize(TypedValue.COMPLEX_UNIT_SP, Math.min(21, headlineFontSize + 3)); - } - - if (holder.feedTitleView != null) { - if (article.feed_title != null && m_feed != null && (m_feed.is_cat || m_feed.id < 0)) { - holder.feedTitleView.setTextSize(TypedValue.COMPLEX_UNIT_SP, headlineSmallFontSize); - holder.feedTitleView.setText(article.feed_title); - } else { - holder.feedTitleView.setVisibility(View.GONE); - } - } - - if (holder.linkHost != null) { - if (article.isHostDistinct()) { - holder.linkHost.setTextSize(TypedValue.COMPLEX_UNIT_SP, headlineSmallFontSize); - holder.linkHost.setText(article.getLinkHost()); - holder.linkHost.setVisibility(View.VISIBLE); - } else { - holder.linkHost.setVisibility(View.GONE); - } - } - + updateTitleView(article, holder); updateMarkedView(article, holder); updateScoreView(article, holder); updatePublishedView(article, holder); - - if (holder.attachmentsView != null) { - if (article.attachments != null && !article.attachments.isEmpty()) { - holder.attachmentsView.setVisibility(View.VISIBLE); - } else { - holder.attachmentsView.setVisibility(View.GONE); - } - } - - if (holder.excerptView != null) { - if (!m_prefs.getBoolean("headlines_show_content", true)) { - holder.excerptView.setVisibility(View.GONE); - } else { - String excerpt = ""; - - try { - if (article.excerpt != null) { - excerpt = article.excerpt; - } else if (article.articleDoc != null) { - excerpt = article.articleDoc.text(); - - if (excerpt.length() > CommonActivity.EXCERPT_MAX_LENGTH) - excerpt = excerpt.substring(0, CommonActivity.EXCERPT_MAX_LENGTH) + "…"; - } - } catch (Exception e) { - e.printStackTrace(); - excerpt = ""; - } - - holder.excerptView.setTextSize(TypedValue.COMPLEX_UNIT_SP, headlineFontSize); - holder.excerptView.setText(excerpt); - - if (!excerpt.isEmpty()) { - holder.excerptView.setVisibility(View.VISIBLE); - } else { - holder.excerptView.setVisibility(View.GONE); - } - } - } + updateAttachmentsView(article, holder); + updateLinkHost(article, holder); + updateExcerptView(article, holder); + updateAuthorView(article, holder); + updateDateView(article, holder); + updateSelectedView(article, holder); if (!m_compactLayoutMode && holder.flavorImageHolder != null) { @@ -1321,21 +1263,27 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { holder.flavorImageView.setOnClickListener(view -> openGalleryForType(article, holder, holder.flavorImageView)); } */ } + } - String articleAuthor = article.author != null ? article.author : ""; - - if (holder.authorView != null) { - holder.authorView.setTextSize(TypedValue.COMPLEX_UNIT_SP, headlineSmallFontSize); + private void updateTitleView(final Article article, final ArticleViewHolder holder) { + if (holder.titleView != null) { + holder.titleView.setText(Html.fromHtml(article.title)); + holder.titleView.setTextSize(TypedValue.COMPLEX_UNIT_SP, Math.min(21, m_headlineFontSize + 3)); + } - if (!articleAuthor.isEmpty()) { - holder.authorView.setText(getString(R.string.author_formatted, articleAuthor)); + if (holder.feedTitleView != null) { + if (article.feed_title != null && m_feed != null && (m_feed.is_cat || m_feed.id < 0)) { + holder.feedTitleView.setTextSize(TypedValue.COMPLEX_UNIT_SP, m_headlineSmallFontSize); + holder.feedTitleView.setText(article.feed_title); } else { - holder.authorView.setText(""); + holder.feedTitleView.setVisibility(View.GONE); } } + } + private void updateDateView(final Article article, final ArticleViewHolder holder) { if (holder.dateView != null) { - holder.dateView.setTextSize(TypedValue.COMPLEX_UNIT_SP, headlineSmallFontSize); + holder.dateView.setTextSize(TypedValue.COMPLEX_UNIT_SP, m_headlineSmallFontSize); Date d = new Date((long)article.updated * 1000); Date now = new Date(); @@ -1354,20 +1302,85 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { df.setTimeZone(TimeZone.getDefault()); holder.dateView.setText(df.format(d)); } + } - updateSelectedView(article, holder); + private void updateAuthorView(final Article article, final ArticleViewHolder holder) { + String articleAuthor = article.author != null ? article.author : ""; + + if (holder.authorView != null) { + holder.authorView.setTextSize(TypedValue.COMPLEX_UNIT_SP, m_headlineSmallFontSize); + + if (!articleAuthor.isEmpty()) { + holder.authorView.setText(getString(R.string.author_formatted, articleAuthor)); + } else { + holder.authorView.setText(""); + } + } + } + + private void updateExcerptView(final Article article, final ArticleViewHolder holder) { + if (holder.excerptView != null) { + if (!m_prefs.getBoolean("headlines_show_content", true)) { + holder.excerptView.setVisibility(View.GONE); + } else { + String excerpt = ""; + + try { + if (article.excerpt != null) { + excerpt = article.excerpt; + } else if (article.articleDoc != null) { + excerpt = article.articleDoc.text(); + + if (excerpt.length() > CommonActivity.EXCERPT_MAX_LENGTH) + excerpt = excerpt.substring(0, CommonActivity.EXCERPT_MAX_LENGTH) + "…"; + } + } catch (Exception e) { + e.printStackTrace(); + excerpt = ""; + } + holder.excerptView.setTextSize(TypedValue.COMPLEX_UNIT_SP, m_headlineFontSize); + holder.excerptView.setText(excerpt); + if (!excerpt.isEmpty()) { + holder.excerptView.setVisibility(View.VISIBLE); + } else { + holder.excerptView.setVisibility(View.GONE); + } + } + } } - private void updateMarkedView(Article article, ArticleViewHolder holder) { + private void updateLinkHost(final Article article, final ArticleViewHolder holder) { + if (holder.linkHost != null) { + if (article.isHostDistinct()) { + holder.linkHost.setTextSize(TypedValue.COMPLEX_UNIT_SP, m_headlineSmallFontSize); + holder.linkHost.setText(article.getLinkHost()); + holder.linkHost.setVisibility(View.VISIBLE); + } else { + holder.linkHost.setVisibility(View.GONE); + } + } + } + + private void updateAttachmentsView(final Article article, final ArticleViewHolder holder) { + if (holder.attachmentsView != null) { + if (article.attachments != null && !article.attachments.isEmpty()) { + holder.attachmentsView.setVisibility(View.VISIBLE); + } else { + holder.attachmentsView.setVisibility(View.GONE); + } + } + } + + private void updateMarkedView(final Article article, final ArticleViewHolder holder) { 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_cslTertiary : m_cslPrimary); } } - private void updateTextImage(Article article, ArticleViewHolder holder) { + private void updateTextImage(final Article article, final ArticleViewHolder holder) { if (holder.textImage != null) { updateTextCheckedState(article, holder); @@ -1376,14 +1389,13 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { } } - private void updateSelectedView(Article article, ArticleViewHolder holder) { + private void updateSelectedView(final Article article, final ArticleViewHolder holder) { if (holder.selectionBoxView != null) { holder.selectionBoxView.setChecked(article.selected); - } } - private void updateScoreView(Article article, ArticleViewHolder holder) { + private void updateScoreView(final Article article, final ArticleViewHolder holder) { if (holder.scoreView != null) { int scoreDrawable = R.drawable.baseline_trending_flat_24; @@ -1401,7 +1413,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { } } - private void updatePublishedView(final Article article, ArticleViewHolder holder) { + private void updatePublishedView(final Article article, final ArticleViewHolder holder) { if (holder.publishedView != null) { // otherwise we just use tinting in actionbar if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { @@ -1412,7 +1424,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { } } - private void loadFlavorImage(final Article article, ArticleViewHolder holder, int maxImageHeight) { + private void loadFlavorImage(final Article article, final ArticleViewHolder holder, final int maxImageHeight) { Glide.with(HeadlinesFragment.this) .load(article.flavorImageUri) .transition(DrawableTransitionOptions.withCrossFade()) @@ -1445,7 +1457,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { .into(new DrawableImageViewTarget(holder.flavorImageView)); } - private void checkImageAndLoad(Article article, ArticleViewHolder holder, int maxImageHeight) { + private void checkImageAndLoad(final Article article, final ArticleViewHolder holder, final int maxImageHeight) { FlavorProgressTarget flavorProgressTarget = new FlavorProgressTarget<>(new SimpleTarget() { @Override public void onResourceReady(@NonNull Size resource, @Nullable com.bumptech.glide.request.transition.Transition transition) { @@ -1515,7 +1527,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { } } - private void openGalleryForType(Article article, ArticleViewHolder holder, View transitionView) { + private void openGalleryForType(final Article article, final ArticleViewHolder holder, final View transitionView) { //Log.d(TAG, "openGalleryForType: " + article + " " + holder + " " + transitionView); if ("iframe".equalsIgnoreCase(article.flavorImage.tagName())) { @@ -1562,7 +1574,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { } - private void adjustVideoKindView(ArticleViewHolder holder, Article article) { + private void adjustVideoKindView(final ArticleViewHolder holder, final Article article) { if (article.flavorImage != null) { if (article.flavor_kind == Article.FLAVOR_KIND_YOUTUBE || "iframe".equalsIgnoreCase(article.flavorImage.tagName())) { holder.flavorVideoKindView.setImageResource(R.drawable.baseline_play_circle_outline_24); -- cgit v1.2.3-54-g00ecf From db331412437bd342bb9a3a4d62c3898828e10249 Mon Sep 17 00:00:00 2001 From: Andrew Dolgov Date: Sun, 18 May 2025 22:22:35 +0300 Subject: adjust flavor image loading bar margins --- org.fox.ttrss/src/main/java/org/fox/ttrss/HeadlinesFragment.java | 4 ++-- org.fox.ttrss/src/main/res/layout/fragment_gallery_entry.xml | 2 +- org.fox.ttrss/src/main/res/layout/headlines_row.xml | 6 ++++-- 3 files changed, 7 insertions(+), 5 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 0fbd3721..e20fbcae 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 @@ -641,8 +641,8 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { dateView = v.findViewById(R.id.date); selectionBoxView = v.findViewById(R.id.selected); menuButtonView = v.findViewById(R.id.article_menu_button); - flavorImageHolder = v.findViewById(R.id.flavorImageHolder); - flavorImageLoadingBar = v.findViewById(R.id.flavorImageLoadingBar); + flavorImageHolder = v.findViewById(R.id.flavor_image_holder); + flavorImageLoadingBar = v.findViewById(R.id.flavor_image_progressbar); textImage = v.findViewById(R.id.text_image); textChecked = v.findViewById(R.id.text_checked); headlineHeader = v.findViewById(R.id.headline_header); diff --git a/org.fox.ttrss/src/main/res/layout/fragment_gallery_entry.xml b/org.fox.ttrss/src/main/res/layout/fragment_gallery_entry.xml index 5bde3166..3c507cba 100644 --- a/org.fox.ttrss/src/main/res/layout/fragment_gallery_entry.xml +++ b/org.fox.ttrss/src/main/res/layout/fragment_gallery_entry.xml @@ -1,6 +1,6 @@ diff --git a/org.fox.ttrss/src/main/res/layout/headlines_row.xml b/org.fox.ttrss/src/main/res/layout/headlines_row.xml index 98a66299..85a93f33 100755 --- a/org.fox.ttrss/src/main/res/layout/headlines_row.xml +++ b/org.fox.ttrss/src/main/res/layout/headlines_row.xml @@ -99,18 +99,20 @@ android:layout_height="match_parent" > -- cgit v1.2.3-54-g00ecf