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 --- org.fox.ttrss/src/main/res/layout/activity_gallery.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'org.fox.ttrss/src/main/res') 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 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 (limited to 'org.fox.ttrss/src/main/res') 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 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 (limited to 'org.fox.ttrss/src/main/res') 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 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 (limited to 'org.fox.ttrss/src/main/res') 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 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 (limited to 'org.fox.ttrss/src/main/res') 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(-) (limited to 'org.fox.ttrss/src/main/res') 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 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(-) (limited to 'org.fox.ttrss/src/main/res') 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: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(-) (limited to 'org.fox.ttrss/src/main/res') 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 (limited to 'org.fox.ttrss/src/main/res') 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 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(-) (limited to 'org.fox.ttrss/src/main/res') 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 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(-) (limited to 'org.fox.ttrss/src/main/res') 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