summaryrefslogtreecommitdiff
path: root/org.fox.ttrss/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'org.fox.ttrss/src/main')
-rwxr-xr-xorg.fox.ttrss/src/main/AndroidManifest.xml3
-rw-r--r--org.fox.ttrss/src/main/java/org/fox/ttrss/ApiCommon.java73
-rw-r--r--org.fox.ttrss/src/main/java/org/fox/ttrss/ArticleModel.java6
-rwxr-xr-xorg.fox.ttrss/src/main/java/org/fox/ttrss/CommonActivity.java16
-rwxr-xr-xorg.fox.ttrss/src/main/java/org/fox/ttrss/DetailActivity.java20
-rwxr-xr-xorg.fox.ttrss/src/main/java/org/fox/ttrss/GalleryActivity.java330
-rwxr-xr-xorg.fox.ttrss/src/main/java/org/fox/ttrss/GalleryImageFragment.java26
-rw-r--r--org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryModel.java178
-rwxr-xr-xorg.fox.ttrss/src/main/java/org/fox/ttrss/GalleryVideoFragment.java80
-rwxr-xr-xorg.fox.ttrss/src/main/java/org/fox/ttrss/HeadlinesFragment.java1083
-rw-r--r--org.fox.ttrss/src/main/java/org/fox/ttrss/HeadlinesFragmentModel.java22
-rwxr-xr-xorg.fox.ttrss/src/main/java/org/fox/ttrss/MasterActivity.java33
-rwxr-xr-xorg.fox.ttrss/src/main/java/org/fox/ttrss/OnlineActivity.java103
-rw-r--r--org.fox.ttrss/src/main/java/org/fox/ttrss/glide/BitmapSizeDecoder.java30
-rw-r--r--org.fox.ttrss/src/main/java/org/fox/ttrss/glide/OkHttpProgressGlideModule.java25
-rw-r--r--org.fox.ttrss/src/main/java/org/fox/ttrss/glide/OptionsSizeResourceTranscoder.java20
-rw-r--r--org.fox.ttrss/src/main/java/org/fox/ttrss/glide/ProgressTarget.java17
-rwxr-xr-xorg.fox.ttrss/src/main/java/org/fox/ttrss/glide/WrappingTarget.java21
-rw-r--r--org.fox.ttrss/src/main/java/org/fox/ttrss/util/ArticleDiffItemCallback.java34
-rw-r--r--org.fox.ttrss/src/main/res/drawable/indicator_dot.xml8
-rw-r--r--org.fox.ttrss/src/main/res/layout/activity_gallery.xml5
-rw-r--r--org.fox.ttrss/src/main/res/layout/fragment_gallery_entry.xml2
-rwxr-xr-xorg.fox.ttrss/src/main/res/layout/headlines_row.xml16
-rwxr-xr-xorg.fox.ttrss/src/main/res/layout/headlines_row_compact_active.xml109
-rwxr-xr-xorg.fox.ttrss/src/main/res/layout/headlines_row_compact_active_unread.xml110
-rwxr-xr-xorg.fox.ttrss/src/main/res/layout/headlines_row_compact_unread.xml110
-rwxr-xr-xorg.fox.ttrss/src/main/res/layout/headlines_row_unread.xml273
-rwxr-xr-xorg.fox.ttrss/src/main/res/values/strings.xml3
-rwxr-xr-xorg.fox.ttrss/src/main/res/xml/preferences.xml7
29 files changed, 1269 insertions, 1494 deletions
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" >
- <meta-data android:name="org.fox.ttrss.glide.OkHttpProgressGlideModule"
- android:value="GlideModule" />
-
<meta-data android:name="android.max_aspect" android:value="2.1" />
<activity
diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/ApiCommon.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/ApiCommon.java
index 17d0c75a..e472a6b0 100644
--- a/org.fox.ttrss/src/main/java/org/fox/ttrss/ApiCommon.java
+++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/ApiCommon.java
@@ -1,6 +1,5 @@
package org.fox.ttrss;
-import static org.fox.ttrss.glide.OkHttpProgressGlideModule.createInterceptor;
import android.content.Context;
import android.content.SharedPreferences;
@@ -18,8 +17,6 @@ import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
-import org.fox.ttrss.glide.OkHttpProgressGlideModule;
-
import java.io.IOException;
import java.util.HashMap;
import java.util.Locale;
@@ -27,11 +24,18 @@ import java.util.concurrent.TimeUnit;
import okhttp3.Credentials;
import okhttp3.HttpUrl;
+import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
+import okhttp3.ResponseBody;
+import okio.Buffer;
+import okio.BufferedSource;
+import okio.ForwardingSource;
+import okio.Okio;
+import okio.Source;
public class ApiCommon {
public static final String TAG = "ApiCommon";
@@ -146,7 +150,7 @@ public class ApiCommon {
Request request = requestBuilder.build();
- OkHttpProgressGlideModule.ResponseProgressListener listener = new OkHttpProgressGlideModule.ResponseProgressListener() {
+ ResponseProgressListener listener = new ResponseProgressListener() {
@Override
public void update(HttpUrl url, long bytesRead, long contentLength) {
// Log.d(TAG, "[progress] " + url + " " + bytesRead + " of " + contentLength);
@@ -266,6 +270,67 @@ public class ApiCommon {
return null;
}
+ private interface ResponseProgressListener {
+ void update(HttpUrl url, long bytesRead, long contentLength);
+ }
+
+ private static Interceptor createInterceptor(final ResponseProgressListener listener) {
+ return chain -> {
+ Request request = chain.request();
+ Response response = chain.proceed(request);
+ return response.newBuilder()
+ .body(new ProgressResponseBody(request.url(), response.body(), listener))
+ .build();
+ };
+ }
+
+ private static class ProgressResponseBody extends ResponseBody {
+ private final HttpUrl url;
+ private final ResponseBody responseBody;
+ private final ResponseProgressListener progressListener;
+ private BufferedSource bufferedSource;
+
+ public ProgressResponseBody(HttpUrl url, ResponseBody responseBody,
+ ResponseProgressListener progressListener) {
+
+ this.url = url;
+ this.responseBody = responseBody;
+ this.progressListener = progressListener;
+ }
+
+ @Override public MediaType contentType() {
+ return responseBody.contentType();
+ }
+
+ @Override public long contentLength() {
+ return responseBody.contentLength();
+ }
+
+ @Override public BufferedSource source() {
+ if (bufferedSource == null) {
+ bufferedSource = Okio.buffer(source(responseBody.source()));
+ }
+ return bufferedSource;
+ }
+
+ private Source source(Source source) {
+ return new ForwardingSource(source) {
+ long totalBytesRead = 0L;
+ @Override public long read(Buffer sink, long byteCount) throws IOException {
+ long bytesRead = super.read(sink, byteCount);
+ long fullLength = responseBody.contentLength();
+ if (bytesRead == -1) { // this source is exhausted
+ totalBytesRead = fullLength;
+ } else {
+ totalBytesRead += bytesRead;
+ }
+ progressListener.update(url, totalBytesRead, fullLength);
+ return bytesRead;
+ }
+ };
+ }
+ }
+
private static String getUserAgent(Context context) {
try {
PackageInfo packageInfo = context.getPackageManager().
diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/ArticleModel.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/ArticleModel.java
index 2b596828..b1f99208 100644
--- a/org.fox.ttrss/src/main/java/org/fox/ttrss/ArticleModel.java
+++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/ArticleModel.java
@@ -70,6 +70,12 @@ public class ArticleModel extends AndroidViewModel implements ApiCommon.ApiCalle
return m_articles;
}
+ public void updateById(@NonNull Article article) {
+ int position = m_articles.getValue().getPositionById(article.id);
+
+ if (position != -1)
+ update(position, article);
+ }
public void update(int position, Article article) {
m_articles.getValue().set(position, article);
diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/CommonActivity.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/CommonActivity.java
index 97e356c4..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,9 +24,12 @@ 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;
+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 +43,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;
@@ -234,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) {
@@ -310,9 +316,9 @@ 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"};
+ "force_phone_layout", "open_on_startup", "window_secure_mode" };
m_needRestart = Arrays.asList(filter).contains(key);
}
@@ -348,13 +354,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<Bitmap>() {
@Override
- public void onResourceReady(Bitmap resource, GlideAnimation<? super Bitmap> glideAnimation) {
+ public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition<? super Bitmap> transition) {
Log.d(TAG, "image resource ready: " + resource);
if (resource != null) {
diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/DetailActivity.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/DetailActivity.java
index 10b06022..59ff4e66 100755
--- a/org.fox.ttrss/src/main/java/org/fox/ttrss/DetailActivity.java
+++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/DetailActivity.java
@@ -90,12 +90,12 @@ public class DetailActivity extends OnlineActivity implements HeadlinesEventList
return true;
} else if (itemId == R.id.toggle_unread) {
- article.unread = !article.unread;
- saveArticleUnread(article);
+ Article articleClone = new Article(article);
- if (hf != null) {
- hf.notifyItemChanged(Application.getArticles().indexOf(article));
- }
+ articleClone.unread = !articleClone.unread;
+ saveArticleUnread(articleClone);
+
+ Application.getArticlesModel().updateById(articleClone);
}
}
@@ -272,9 +272,13 @@ public class DetailActivity extends OnlineActivity implements HeadlinesEventList
@Override
public void onArticleSelected(Article article, boolean open) {
- if (article.unread) {
- article.unread = false;
- saveArticleUnread(article);
+ Article articleClone = new Article(article);
+
+ if (articleClone.unread) {
+ articleClone.unread = false;
+ saveArticleUnread(articleClone);
+
+ Application.getArticlesModel().updateById(articleClone);
}
if (!getSupportActionBar().isShowing()) getSupportActionBar().show();
diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryActivity.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryActivity.java
index c527730b..0a19702e 100755
--- a/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryActivity.java
+++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryActivity.java
@@ -12,62 +12,65 @@ import android.view.Window;
import android.widget.PopupMenu;
import android.widget.ProgressBar;
+import androidx.annotation.NonNull;
import androidx.appcompat.widget.Toolbar;
import androidx.core.app.ActivityCompat;
import androidx.core.view.WindowCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.core.view.WindowInsetsControllerCompat;
import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentManager;
-import androidx.fragment.app.FragmentStatePagerAdapter;
+import androidx.fragment.app.FragmentActivity;
+import androidx.lifecycle.ViewModelProvider;
import androidx.preference.PreferenceManager;
-import androidx.viewpager.widget.ViewPager;
+import androidx.recyclerview.widget.DiffUtil;
+import androidx.recyclerview.widget.PagerSnapHelper;
+import androidx.viewpager2.widget.ViewPager2;
-import com.ToxicBakery.viewpager.transforms.DepthPageTransformer;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import org.fox.ttrss.types.GalleryEntry;
-import org.jsoup.Jsoup;
-import org.jsoup.nodes.Document;
-import org.jsoup.nodes.Element;
-import org.jsoup.select.Elements;
+import org.fox.ttrss.util.DiffFragmentStateAdapter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
-import me.relex.circleindicator.CircleIndicator;
+import me.relex.circleindicator.CircleIndicator2;
+import me.relex.circleindicator.CircleIndicator3;
public class GalleryActivity extends CommonActivity {
private final String TAG = this.getClass().getSimpleName();
- protected ArrayList<GalleryEntry> m_items = new ArrayList<>();
protected String m_title;
private ArticleImagesPagerAdapter m_adapter;
public String m_content;
- private ViewPager m_pager; // TODO replace with viewpager2
+ private ViewPager2 m_pager; // TODO replace with viewpager2
private ProgressBar m_checkProgress;
+ private boolean m_firstWasSelected;
- private static class ArticleImagesPagerAdapter extends FragmentStatePagerAdapter {
- private final List<GalleryEntry> m_items;
+ private static class GalleryEntryDiffItemCallback extends DiffUtil.ItemCallback<GalleryEntry> {
- public ArticleImagesPagerAdapter(FragmentManager fm, List<GalleryEntry> items) {
- super(fm);
- m_items = items;
+ @Override
+ public boolean areItemsTheSame(@NonNull GalleryEntry oldItem, @NonNull GalleryEntry newItem) {
+ return oldItem.url.equals(newItem.url);
}
@Override
- public int getCount() {
- return m_items.size();
+ public boolean areContentsTheSame(@NonNull GalleryEntry oldItem, @NonNull GalleryEntry newItem) {
+ return oldItem.url.equals(newItem.url) && oldItem.type.equals(newItem.type);
}
+ }
- @Override
- public Fragment getItem(int position) {
+ private static class ArticleImagesPagerAdapter extends DiffFragmentStateAdapter<GalleryEntry> {
+ protected ArticleImagesPagerAdapter(FragmentActivity fragmentActivity, DiffUtil.ItemCallback<GalleryEntry> diffCallback) {
+ super(fragmentActivity, diffCallback);
+ }
- //Log.d(TAG, "getItem: " + position + " " + m_urls.get(position));
+ @Override
+ public Fragment createFragment(int position) {
- GalleryEntry item = m_items.get(position);
+ GalleryEntry item = getItem(position);
switch (item.type) {
case TYPE_IMAGE: {
@@ -87,161 +90,16 @@ 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<List<GalleryEntry>, MediaProgressResult, List<GalleryEntry>> {
-
- private final List<GalleryEntry> m_checkedItems = new ArrayList<>();
-
- @Override
- protected List<GalleryEntry> doInBackground(List<GalleryEntry>... params) {
-
- ArrayList<GalleryEntry> 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<GalleryEntry> 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);
- out.putParcelableArrayList("m_items", m_items);
out.putString("m_title", m_title);
out.putString("m_content", m_content);
}
@Override
public void onCreate(Bundle savedInstanceState) {
- ActivityCompat.postponeEnterTransition(this);
+ // ActivityCompat.postponeEnterTransition(this);
// we use that before parent onCreate so let's init locally
m_prefs = PreferenceManager
@@ -263,102 +121,120 @@ public class GalleryActivity extends CommonActivity {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().hide();
- ArrayList<GalleryEntry> uncheckedItems = new ArrayList<>();
+ setTitle(m_title);
+
+ m_adapter = new ArticleImagesPagerAdapter(this, new GalleryEntryDiffItemCallback());
+
+ m_pager = findViewById(R.id.gallery_pager);
+ m_pager.setAdapter(m_adapter);
+
+ m_checkProgress = findViewById(R.id.gallery_check_progress);
if (savedInstanceState == null) {
m_title = getIntent().getStringExtra("title");
m_content = getIntent().getStringExtra("content");
+ } else {
+ // ArrayList<GalleryEntry> list = savedInstanceState.getParcelableArrayList("m_items");
+ m_title = savedInstanceState.getString("m_title");
+ m_content = savedInstanceState.getString("m_content");
+ }
- String imgSrcFirst = getIntent().getStringExtra("firstSrc");
+ GalleryModel model = new ViewModelProvider(this).get(GalleryModel.class);
- Document doc = Jsoup.parse(m_content);
+ // this should be dealt with first so that transition completes properly
+ String firstSrc = getIntent().getStringExtra("firstSrc");
- // if we were unable to find first image, try again for all media content so that
- // gallery doesn't lock up because of a pending shared transition
- if (!collectGalleryContents(imgSrcFirst, doc, uncheckedItems))
- if (!collectGalleryContents("", doc, uncheckedItems))
- m_items.add(new GalleryEntry(imgSrcFirst, GalleryEntry.GalleryEntryType.TYPE_IMAGE, null));
- } else {
- ArrayList<GalleryEntry> list = savedInstanceState.getParcelableArrayList("m_items");
+ /* but what about videos? if (firstSrc != null) {
+ List<GalleryEntry> initialItems = new ArrayList<GalleryEntry>();
- m_items.clear();
- m_items.addAll(list);
+ initialItems.add(0, new GalleryEntry(firstSrc, GalleryEntry.GalleryEntryType.TYPE_IMAGE, null));
- m_title = savedInstanceState.getString("m_title");
- m_content = savedInstanceState.getString("m_content");
- }
+ m_adapter.submitList(initialItems);
- findViewById(R.id.gallery_overflow).setOnClickListener(v -> {
- PopupMenu popup = new PopupMenu(GalleryActivity.this, v);
- MenuInflater inflater = popup.getMenuInflater();
- inflater.inflate(R.menu.content_gallery_entry, popup.getMenu());
+ model.update(initialItems);
+ } */
- final GalleryEntry entry = m_items.get(m_pager.getCurrentItem());
+ model.collectItems(m_content, firstSrc);
- popup.getMenu().findItem(R.id.article_img_share)
- .setVisible(entry.type == GalleryEntry.GalleryEntryType.TYPE_IMAGE);
+ model.getItemsToCheck().observe(this, itemsToCheck -> {
+ Log.d(TAG, "observed items to check=" + itemsToCheck);
- popup.setOnMenuItemClickListener(item -> onImageMenuItemSelected(item, entry));
+ m_checkProgress.setMax(itemsToCheck);
+ m_checkProgress.setProgress(0);
+ });
- popup.show();
+ model.getIsChecking().observe(this, isChecking -> {
+ Log.d(TAG, "observed isChecking=" + isChecking);
+ m_checkProgress.setVisibility(isChecking ? View.VISIBLE : View.GONE);
});
- setTitle(m_title);
+ model.getCheckProgress().observe(this, progress -> {
+ Log.d(TAG, "observed item check progress=" + progress);
+
+ m_checkProgress.setProgress(progress);
+ });
- m_adapter = new ArticleImagesPagerAdapter(getSupportFragmentManager(), m_items);
+ model.getItems().observe(this, galleryEntries -> {
+ Log.d(TAG, "observed gallery entries=" + galleryEntries + " firstSrc=" + firstSrc);
- m_pager = findViewById(R.id.gallery_pager);
- m_pager.setAdapter(m_adapter);
- m_pager.setPageTransformer(true, new DepthPageTransformer());
+ m_adapter.submitList(galleryEntries, () -> {
+ if (!m_firstWasSelected) {
+ for (GalleryEntry entry : galleryEntries) {
+ if (entry.url.equals(firstSrc)) {
+ int position = galleryEntries.indexOf(entry);
+
+ Log.d(TAG, "selecting first src=" + firstSrc + " pos=" + position);
+ m_pager.setCurrentItem(position);
+
+ m_firstWasSelected = true;
+ break;
+ }
+ }
+ }
+ });
+ });
- CircleIndicator indicator = findViewById(R.id.gallery_pager_indicator);
+ CircleIndicator3 indicator = findViewById(R.id.gallery_pager_indicator);
indicator.setViewPager(m_pager);
- m_adapter.registerDataSetObserver(indicator.getDataSetObserver());
- m_checkProgress = findViewById(R.id.gallery_check_progress);
+ m_adapter.registerAdapterDataObserver(indicator.getAdapterDataObserver());
- Log.d(TAG, "items to check:" + uncheckedItems.size());
- MediaCheckTask mct = new MediaCheckTask() {
- @Override
- protected void onProgressUpdate(MediaProgressResult... result) {
- //m_items.add(result[0].item);
- m_adapter.notifyDataSetChanged();
+ findViewById(R.id.gallery_overflow).setOnClickListener(v -> {
+ try {
+ GalleryEntry entry = m_adapter.getCurrentList().get(m_pager.getCurrentItem());
- if (result[0].position < result[0].count) {
- m_checkProgress.setVisibility(View.VISIBLE);
- m_checkProgress.setMax(result[0].count);
- m_checkProgress.setProgress(result[0].position);
- } else {
- m_checkProgress.setVisibility(View.GONE);
- }
+ PopupMenu popup = new PopupMenu(GalleryActivity.this, v);
+ MenuInflater inflater = popup.getMenuInflater();
+ inflater.inflate(R.menu.content_gallery_entry, popup.getMenu());
- }
+ popup.getMenu().findItem(R.id.article_img_share)
+ .setVisible(entry.type == GalleryEntry.GalleryEntryType.TYPE_IMAGE);
- @Override
- protected void onPostExecute(List<GalleryEntry> result) {
- m_items.addAll(result);
- m_adapter.notifyDataSetChanged();
- }
- };
+ popup.setOnMenuItemClickListener(item -> onImageMenuItemSelected(item, entry));
- mct.execute(uncheckedItems);
+ popup.show();
+ } catch (IndexOutOfBoundsException e) {
+ e.printStackTrace();
+ }
+ });
}
@Override
public boolean onContextItemSelected(MenuItem item) {
int position = m_pager.getCurrentItem();
- GalleryEntry entry = m_items.get(position);
-
- //String url = m_items.get(position).url;
-
+ try {
+ GalleryEntry entry = m_adapter.getCurrentList().get(position);
+ if (onImageMenuItemSelected(item, entry))
+ return true;
- if (onImageMenuItemSelected(item, entry))
- return true;
+ } catch (IndexOutOfBoundsException e) {
+ e.printStackTrace();
+ }
return super.onContextItemSelected(item);
}
diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryImageFragment.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryImageFragment.java
index 8f9d5783..db141427 100755
--- a/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryImageFragment.java
+++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryImageFragment.java
@@ -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(this)
.load(m_url)
- //.dontAnimate()
.diskCacheStrategy(DiskCacheStrategy.ALL)
.skipMemoryCache(false)
- .listener(new RequestListener<String, GlideDrawable>() {
+ .listener(new RequestListener<Drawable>() {
@Override
- public boolean onException(Exception e, String model, Target<GlideDrawable> target, boolean isFirstResource) {
+ public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
progressBar.setVisibility(View.GONE);
errorMessage.setVisibility(View.VISIBLE);
- ActivityCompat.startPostponedEnterTransition(m_activity);
+ // ActivityCompat.startPostponedEnterTransition(m_activity);
+
return false;
}
@Override
- public boolean onResourceReady(GlideDrawable resource, String model, Target<GlideDrawable> target, boolean isFromMemoryCache, boolean isFirstResource) {
+ public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
progressBar.setVisibility(View.GONE);
errorMessage.setVisibility(View.GONE);
- ActivityCompat.startPostponedEnterTransition(m_activity);
+ // 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
new file mode 100644
index 00000000..750f85cd
--- /dev/null
+++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryModel.java
@@ -0,0 +1,178 @@
+package org.fox.ttrss;
+
+import android.app.Application;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.AndroidViewModel;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
+
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.load.engine.DiskCacheStrategy;
+
+import org.fox.ttrss.types.GalleryEntry;
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Document;
+import org.jsoup.nodes.Element;
+import org.jsoup.select.Elements;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+public class GalleryModel extends AndroidViewModel {
+ private final String TAG = this.getClass().getSimpleName();
+
+ private MutableLiveData<List<GalleryEntry>> m_items = new MutableLiveData<>(new ArrayList<>());
+ private MutableLiveData<Integer> m_checkProgress = new MutableLiveData<>(Integer.valueOf(0));
+ private MutableLiveData<Integer> m_itemsToCheck = new MutableLiveData<>(Integer.valueOf(0));
+ private MutableLiveData<Boolean> m_isChecking = new MutableLiveData<>(Boolean.valueOf(false));
+
+ public GalleryModel(@NonNull Application application) {
+ super(application);
+ }
+
+ public LiveData<List<GalleryEntry>> getItems() {
+ return m_items;
+ }
+
+ private ExecutorService m_executor = Executors.newSingleThreadExecutor();
+ private Handler m_mainHandler = new Handler(Looper.getMainLooper());
+
+ public LiveData<Integer> getItemsToCheck() {
+ return m_itemsToCheck;
+ }
+
+ public LiveData<Integer> getCheckProgress() {
+ return m_checkProgress;
+ }
+
+ private boolean isDataUri(String src) {
+ try {
+ Uri uri = Uri.parse(src);
+
+ return "data".equalsIgnoreCase(uri.getScheme());
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ return false;
+ }
+
+ public void update(List<GalleryEntry> items) {
+ m_items.postValue(items);
+ }
+
+ public LiveData<Boolean> getIsChecking() {
+ return m_isChecking;
+ }
+
+ public void collectItems(String articleText, String srcFirst) {
+ m_executor.execute(() -> {
+
+ Document doc = Jsoup.parse(articleText);
+
+ List<GalleryEntry> checkList = new ArrayList<>();
+
+ Log.d(TAG, "looking for srcFirst=" + srcFirst);
+
+ Elements elems = doc.select("img,video");
+
+ m_itemsToCheck.postValue(elems.size());
+
+ int currentItem = 0;
+ boolean firstFound = false;
+
+ m_isChecking.postValue(true);
+
+ for (Element elem : elems) {
+ ++currentItem;
+
+ if ("video".equalsIgnoreCase(elem.tagName())) {
+ Element source = elem.select("source").first();
+ String poster = elem.attr("abs:poster");
+
+ if (source != null) {
+ String src = source.attr("abs:src");
+
+ Log.d(TAG, "checking vid src=" + src + " poster=" + poster);
+
+ if (src != null && src.equals(srcFirst)) {
+ Log.d(TAG, "first item found, vid=" + src);
+
+ firstFound = true;
+
+ GalleryEntry item = new GalleryEntry(src, GalleryEntry.GalleryEntryType.TYPE_VIDEO, poster);
+
+ checkList.add(item);
+
+ m_items.postValue(checkList);
+ } else {
+ if (!isDataUri(src)) {
+ checkList.add(new GalleryEntry(src, GalleryEntry.GalleryEntryType.TYPE_VIDEO, poster));
+ m_items.postValue(checkList);
+ }
+ }
+ }
+ } else {
+ String src = elem.attr("abs:src");
+
+ Log.d(TAG, "checking img src=" + src);
+
+ if (src != null && src.equals(srcFirst)) {
+ Log.d(TAG, "first item found, img=" + src);
+
+ firstFound = true;
+
+ GalleryEntry item = new GalleryEntry(src, GalleryEntry.GalleryEntryType.TYPE_IMAGE, null);
+
+ checkList.add(item);
+
+ m_items.postValue(checkList);
+ } else {
+ if (!isDataUri(src)) {
+ Log.d(TAG, "checking image with glide: " + src);
+
+ try {
+ Bitmap bmp = Glide.with(getApplication().getApplicationContext())
+ .asBitmap()
+ .load(src)
+ .skipMemoryCache(false)
+ .diskCacheStrategy(DiskCacheStrategy.ALL)
+ .into(HeadlinesFragment.FLAVOR_IMG_MIN_SIZE, HeadlinesFragment.FLAVOR_IMG_MIN_SIZE)
+ .get();
+
+ if (bmp != null && bmp.getWidth() >= HeadlinesFragment.FLAVOR_IMG_MIN_SIZE && bmp.getHeight() >= HeadlinesFragment.FLAVOR_IMG_MIN_SIZE) {
+ Log.d(TAG, "image matches gallery criteria, adding...");
+
+ checkList.add(new GalleryEntry(src, GalleryEntry.GalleryEntryType.TYPE_IMAGE, null));
+ m_items.postValue(checkList);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+
+ m_checkProgress.postValue(currentItem);
+ }
+
+ // if we didn't find it in the document, let's add insert to the list anyway so shared transition
+ // would hopefully work
+ if (!firstFound) {
+ checkList.add(0, new GalleryEntry(srcFirst, GalleryEntry.GalleryEntryType.TYPE_IMAGE, null));
+ m_items.postValue(checkList);
+ }
+
+ m_isChecking.postValue(false);
+ });
+ }
+}
diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryVideoFragment.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryVideoFragment.java
index a96e59b5..a171fb5b 100755
--- a/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryVideoFragment.java
+++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/GalleryVideoFragment.java
@@ -26,7 +26,6 @@ public class GalleryVideoFragment extends GalleryBaseFragment {
String m_url;
String m_coverUrl;
MediaPlayer m_mediaPlayer;
- private boolean m_userVisibleHint = false;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -57,87 +56,35 @@ public class GalleryVideoFragment extends GalleryBaseFragment {
registerForContextMenu(imgView);
- /*final GlideDrawableImageViewTarget glideImage = new GlideDrawableImageViewTarget(imgView);
+ // ActivityCompat.startPostponedEnterTransition(m_activity);
- Glide.with(this)
- .load(m_coverUrl)
- //.dontAnimate()
- .diskCacheStrategy(DiskCacheStrategy.ALL)
- .skipMemoryCache(false)
- .listener(new RequestListener<String, GlideDrawable>() {
- @Override
- public boolean onException(Exception e, String model, Target<GlideDrawable> target, boolean isFirstResource) {
- ActivityCompat.startPostponedEnterTransition(m_activity);
-
- initializeVideoPlayer(view);
- return false;
- }
-
- @Override
- public boolean onResourceReady(GlideDrawable resource, String model, Target<GlideDrawable> target, boolean isFromMemoryCache, boolean isFirstResource) {
- ActivityCompat.startPostponedEnterTransition(m_activity);
-
- initializeVideoPlayer(view);
- return false;
- }
- })
- .into(glideImage); */
+ view.findViewById(R.id.flavor_image_progress).setVisibility(View.VISIBLE);
- ActivityCompat.startPostponedEnterTransition(m_activity);
initializeVideoPlayer(view);
return view;
}
- @Override
- public void setUserVisibleHint(boolean isVisibleToUser) {
- super.setUserVisibleHint(isVisibleToUser);
- m_userVisibleHint = isVisibleToUser;
-
- Log.d(TAG, "setUserVisibleHint: " + isVisibleToUser);
-
- if (getView() == null) return;
-
- try {
-
- if (isVisibleToUser) {
- if (m_mediaPlayer != null && !m_mediaPlayer.isPlaying()) {
- m_mediaPlayer.start();
- }
-
- } else {
- if (m_mediaPlayer != null && m_mediaPlayer.isPlaying()) {
- m_mediaPlayer.pause();
- }
- }
- } catch (IllegalStateException e) {
- e.printStackTrace();
- }
-
- }
-
private void initializeVideoPlayer(final View view) {
-
- //Log.d(TAG, "initializeVideoPlayer: " + m_activity + " " + view);
-
-
- final MediaController m_mediaController = new MediaController(m_activity);
+ final MediaController mediaController = new MediaController(m_activity);
final TextureView textureView = view.findViewById(R.id.flavor_video);
registerForContextMenu(textureView);
textureView.setOnClickListener(v -> {
try {
- if (!m_mediaController.isShowing())
- m_mediaController.show(5000);
+
+ if (mediaController.isShowing())
+ mediaController.hide();
else
- m_mediaController.hide();
+ mediaController.show();
+
} catch (Exception e) {
e.printStackTrace();
}
});
- m_mediaController.setAnchorView(textureView);
+ mediaController.setAnchorView(textureView);
textureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
@Override
@@ -146,7 +93,7 @@ public class GalleryVideoFragment extends GalleryBaseFragment {
m_mediaPlayer = new MediaPlayer();
- m_mediaController.setMediaPlayer(new MediaController.MediaPlayerControl() {
+ mediaController.setMediaPlayer(new MediaController.MediaPlayerControl() {
@Override
public void start() {
m_mediaPlayer.start();
@@ -213,16 +160,13 @@ public class GalleryVideoFragment extends GalleryBaseFragment {
}
m_mediaPlayer.setOnPreparedListener(mp -> {
- getView().findViewById(R.id.flavor_image).setVisibility(View.GONE);
- getView().findViewById(R.id.flavor_image_progress).setVisibility(View.GONE);
+ view.findViewById(R.id.flavor_image_progress).setVisibility(View.GONE);
try {
resizeSurface(textureView);
mp.setLooping(true);
- if (m_userVisibleHint) {
- mp.start();
- }
+ mp.start();
} catch (IllegalStateException e) {
e.printStackTrace();
}
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..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
@@ -6,9 +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.SurfaceTexture;
+import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.media.MediaPlayer;
import android.net.ConnectivityManager;
@@ -22,6 +21,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;
@@ -29,7 +29,6 @@ import android.view.Display;
import android.view.LayoutInflater;
import android.view.MenuInflater;
import android.view.MenuItem;
-import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import android.view.ViewGroup;
@@ -38,17 +37,17 @@ import android.widget.AdapterView;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.CheckBox;
import android.widget.EditText;
-import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.PopupMenu;
import android.widget.ProgressBar;
import android.widget.TextView;
-import androidx.core.app.ActivityCompat;
-import androidx.core.app.ActivityOptionsCompat;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
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;
@@ -60,12 +59,17 @@ 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.load.resource.drawable.DrawableTransitionOptions;
import com.bumptech.glide.request.RequestListener;
-import com.bumptech.glide.request.target.GlideDrawableImageViewTarget;
+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.card.MaterialCardView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.snackbar.Snackbar;
@@ -81,12 +85,10 @@ import org.jsoup.nodes.Element;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
-import java.util.HashMap;
+import java.util.List;
import java.util.TimeZone;
import java.util.stream.Collectors;
-import jp.wasabeef.glide.transformations.CropCircleTransformation;
-
public class HeadlinesFragment extends androidx.fragment.app.Fragment {
private boolean m_isLazyLoading;
@@ -99,11 +101,13 @@ 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();
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 = "";
@@ -117,12 +121,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<Integer, Integer> m_flavorHeightsCache = new HashMap<>();
-
public ArticleList getSelectedArticles() {
return Application.getArticles()
.stream()
@@ -130,11 +133,6 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment {
}
public void initialize(Feed feed) {
-
- // clear loaded headlines before switching feed
- if (feed != m_feed)
- Application.getArticlesModel().update(new ArticleList());
-
m_feed = feed;
}
@@ -156,9 +154,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().updateById(articleClone);
+
return true;
} else if (itemId == R.id.headlines_article_link_copy) {
m_activity.copyToClipboard(article.link);
@@ -167,11 +169,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().updateById(articleClone);
+ }
return true;
} else if (itemId == R.id.headlines_share_article) {
m_activity.shareArticle(article);
@@ -195,20 +199,21 @@ 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);
- if (position != -1)
- m_adapter.notifyItemChanged(position);
+ Application.getArticlesModel().updateById(articleClone);
}
}
@@ -294,6 +299,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))
@@ -418,12 +425,11 @@ 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);
- int position = Application.getArticles().getPositionById(a.id);
+ articleClone.unread = false;
- if (position != -1)
- m_adapter.notifyItemChanged(position);
+ Application.getArticlesModel().updateById(articleClone);
}
m_readArticles.clear();
@@ -585,7 +591,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment {
return;
if (!append)
- m_activeArticleId = -1;
+ setActiveArticleId(-1);
model.setSearchQuery(getSearchQuery());
model.startLoading(append, m_feed, m_activity.getResizeWidth());
@@ -615,8 +621,6 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment {
public View flavorImageOverflow;
public TextureView flavorVideoView;
public MaterialButton attachmentsView;
- public ProgressTarget<String, GlideDrawable> flavorProgressTarget;
- int articleId;
public TextView linkHost;
public ArticleViewHolder(View v) {
@@ -624,16 +628,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_flavorHeightsCache.put(articleId, flavorImage.getMeasuredHeight());
- }
-
- return true;
- });
-
titleView = v.findViewById(R.id.title);
feedTitleView = v.findViewById(R.id.feed_title);
@@ -647,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);
@@ -656,23 +650,15 @@ 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 GlideDrawableImageViewTarget(flavorImageView), flavorImageLoadingBar);
- }
-
- }
-
- public void clearAnimation() {
- view.clearAnimation();
}
}
private static class FlavorProgressTarget<Z> extends ProgressTarget<String, Z> {
- private final ProgressBar progress;
- public FlavorProgressTarget(Target<Z> target, ProgressBar progress) {
+ private final ArticleViewHolder holder;
+ public FlavorProgressTarget(Target<Z> target, String model, ArticleViewHolder holder) {
super(target);
- this.progress = progress;
+ setModel(model);
+ this.holder = holder;
}
@Override public float getGranualityPercentage() {
@@ -680,43 +666,57 @@ 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);
}
}
private class ArticleListAdapter extends ListAdapter<Article, ArticleViewHolder> {
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();
-
- boolean flavorImageEnabled;
+ 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;
private final int m_screenHeight;
- private int m_lastAddedPosition;
+
+ private final int m_headlineSmallFontSize;
+ private final int m_headlineFontSize;
private final ConnectivityManager m_cmgr;
private boolean canShowFlavorImage() {
- if (flavorImageEnabled) {
+ if (m_flavorImageEnabled) {
if (m_prefs.getBoolean("headline_images_wifi_only", false)) {
// why do i have to get this service every time instead of using a member variable :(
NetworkInfo wifi = m_cmgr.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
@@ -732,6 +732,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());
@@ -739,406 +745,428 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment {
Point size = new Point();
display.getSize(size);
m_screenHeight = size.y;
+ m_screenWidth = size.x;
String headlineMode = m_prefs.getString("headline_mode", "HL_DEFAULT");
- flavorImageEnabled = "HL_DEFAULT".equals(headlineMode) || "HL_COMPACT".equals(headlineMode);
-
- m_cmgr = (ConnectivityManager) m_activity.getSystemService(Context.CONNECTIVITY_SERVICE);
- }
+ m_flavorImageEnabled = "HL_DEFAULT".equals(headlineMode) || "HL_COMPACT".equals(headlineMode);
- @Override
- public ArticleViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ m_colorPrimary = colorFromAttr(R.attr.colorPrimary);
+ m_colorSecondary = colorFromAttr(R.attr.colorSecondary);
+ m_colorTertiary = colorFromAttr(R.attr.colorTertiary);
- int layoutId = m_compactLayoutMode ? R.layout.headlines_row_compact : R.layout.headlines_row;
+ m_cslTertiary = ColorStateList.valueOf(m_colorTertiary);
+ m_cslPrimary = ColorStateList.valueOf(m_colorPrimary);
- 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;
- }
+ m_colorSurfaceContainerLowest = colorFromAttr(R.attr.colorSurfaceContainerLowest);
+ m_colorSurface = colorFromAttr(R.attr.colorSurface);
+ m_colorOnSurface = colorFromAttr(R.attr.colorOnSurface);
- View v = LayoutInflater.from(parent.getContext()).inflate(layoutId, parent, false);
+ m_colorTertiaryContainer = colorFromAttr(R.attr.colorTertiaryContainer);
+ m_colorOnTertiaryContainer = colorFromAttr(R.attr.colorOnTertiaryContainer);
- //registerForContextMenu(v);
+ m_headlineFontSize = m_prefs.getInt("headlines_font_size_sp_int", 13);
+ m_headlineSmallFontSize = Math.max(10, Math.min(18, m_headlineFontSize - 2));
- return new ArticleViewHolder(v);
+ m_cmgr = (ConnectivityManager) m_activity.getSystemService(Context.CONNECTIVITY_SERVICE);
}
+ @NonNull
@Override
- public void onBindViewHolder(final ArticleViewHolder holder, int position) {
- int headlineFontSize = m_prefs.getInt("headlines_font_size_sp_int", 13);
- int headlineSmallFontSize = Math.max(10, Math.min(18, headlineFontSize - 2));
+ public ArticleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
- Article article = getItem(position);
+ int layoutId = m_compactLayoutMode ? R.layout.headlines_row_compact : R.layout.headlines_row;
- holder.articleId = article.id;
+ if (viewType == VIEW_AMR_FOOTER) {
+ layoutId = R.layout.headlines_footer;
+ }
- if (article.id == Article.TYPE_AMR_FOOTER && m_prefs.getBoolean("headlines_mark_read_scroll", false)) {
- WindowManager wm = (WindowManager) m_activity.getSystemService(Context.WINDOW_SERVICE);
- Display display = wm.getDefaultDisplay();
- int screenHeight = (int)(display.getHeight() * 1.5);
+ View v = LayoutInflater.from(parent.getContext()).inflate(layoutId, parent, false);
- holder.view.setLayoutParams(new ListView.LayoutParams(ListView.LayoutParams.MATCH_PARENT, screenHeight));
- }
+ ArticleViewHolder holder = new ArticleViewHolder(v);
- // nothing else of interest for those below anyway
- if (article.id < 0) return;
+ // set on click handlers once when view is created
+
+ holder.view.setOnClickListener(view -> {
+ int position = m_list.getChildAdapterPosition(view);
- holder.view.setOnLongClickListener(v -> {
- m_list.showContextMenuForChild(v);
- return true;
- });
+ if (position != -1) {
+ Article article = m_adapter.getItem(position);
- holder.view.setOnClickListener(v -> {
- m_listener.onArticleSelected(article);
+ m_listener.onArticleSelected(article);
- // only set active article when it makes sense (in DetailActivity)
- if (getActivity() instanceof DetailActivity) {
- m_activeArticleId = article.id;
+ // only set active article when it makes sense (in DetailActivity)
+ if (getActivity() instanceof DetailActivity) {
+ m_activeArticleId = article.id;
- m_adapter.notifyItemChanged(position);
+ 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.textImage != null) {
- updateTextCheckedState(holder, position);
+ if (holder.attachmentsView != null) {
+ holder.attachmentsView.setOnClickListener(view -> {
+ int position = m_list.getChildAdapterPosition(holder.view);
- holder.textImage.setOnClickListener(view -> {
- Article selectedArticle = getItem(position);
+ 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);
+ }
+ });
+ }
- Log.d(TAG, "textImage onClick pos=" + position + " article=" + article);
+ 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;
+ });
- selectedArticle.selected = !selectedArticle.selected;
+ popup.show();
+ }
+ });
- updateTextCheckedState(holder, position);
+ holder.flavorImageView.setOnLongClickListener(view -> {
+ m_list.showContextMenuForChild(view);
+ return true;
+ });
+ }
- m_listener.onArticleListSelectionChange();
- });
- ViewCompat.setTransitionName(holder.textImage, "gallery:" + article.flavorImageUri);
+ if (holder.menuButtonView != null) {
+ holder.menuButtonView.setOnClickListener(view -> {
- if (article.flavorImage != null) {
+ int position = m_list.getChildAdapterPosition(holder.view);
- holder.textImage.setOnLongClickListener(v -> {
+ if (position != -1) {
+ PopupMenu popup = new PopupMenu(getContext(), view);
+ MenuInflater inflater = popup.getMenuInflater();
+ inflater.inflate(R.menu.context_headlines, popup.getMenu());
- openGalleryForType(article, holder, holder.textImage);
+ 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);
- return true;
- });
+ popup.setOnMenuItemClickListener(item -> onArticleMenuItemSelected(item,
+ getItem(position),
+ m_list.getChildAdapterPosition(holder.view)));
- }
+ popup.show();
+ }
+ });
}
- if (holder.titleView != null) {
- holder.titleView.setText(Html.fromHtml(article.title));
- holder.titleView.setTextSize(TypedValue.COMPLEX_UNIT_SP, Math.min(21, headlineFontSize + 3));
+ if (holder.markedView != null) {
+ holder.markedView.setOnClickListener(view -> {
+ int position = m_list.getChildAdapterPosition(holder.view);
- adjustTitleTextView(article.score, holder.titleView, position);
+ if (position != -1) {
+ Article article = new Article(getItem(position));
+ article.marked = !article.marked;
+
+ m_activity.saveArticleMarked(article);
+
+ Application.getArticlesModel().updateById(article);
+ }
+ });
}
- if (holder.feedTitleView != null) {
- if (article.feed_title != null && m_feed != null && (m_feed.is_cat || m_feed.id < 0)) {
- holder.feedTitleView.setTextSize(TypedValue.COMPLEX_UNIT_SP, headlineSmallFontSize);
- holder.feedTitleView.setText(article.feed_title);
- } else {
- holder.feedTitleView.setVisibility(View.GONE);
- }
+ if (holder.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.linkHost != null) {
- if (article.isHostDistinct()) {
- holder.linkHost.setTextSize(TypedValue.COMPLEX_UNIT_SP, headlineSmallFontSize);
- holder.linkHost.setText(article.getLinkHost());
- holder.linkHost.setVisibility(View.VISIBLE);
- } else {
- holder.linkHost.setVisibility(View.GONE);
- }
+ 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);
+ }
+ });
}
- TypedValue tvTertiary = new TypedValue();
- m_activity.getTheme().resolveAttribute(R.attr.colorTertiary, tvTertiary, true);
+ if (holder.textImage != null) {
+ holder.textImage.setOnClickListener(view -> {
+ int position = m_list.getChildAdapterPosition(holder.view);
- ColorStateList colorTertiary = ColorStateList.valueOf(ContextCompat.getColor(m_activity, tvTertiary.resourceId));
+ if (position != -1) {
+ Article article = new Article(getItem(position));
+ article.selected = !article.selected;
- TypedValue tvPrimary = new TypedValue();
- m_activity.getTheme().resolveAttribute(R.attr.colorPrimary, tvPrimary, true);
+ Application.getArticlesModel().updateById(article);
- ColorStateList colorPrimary = ColorStateList.valueOf(ContextCompat.getColor(m_activity, tvPrimary.resourceId));
+ // updateTextCheckedState(holder, 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 ? colorTertiary : colorPrimary);
+ m_listener.onArticleListSelectionChange();
+ }
+ });
+
+ holder.textImage.setOnLongClickListener(view -> {
+ int position = m_list.getChildAdapterPosition(holder.view);
- holder.markedView.setOnClickListener(v -> {
- Article selectedArticle = new Article(getItem(position));
- selectedArticle.marked = !selectedArticle.marked;
+ if (position != -1) {
+ Article article = getItem(position);
- m_activity.saveArticleMarked(selectedArticle);
- Application.getArticlesModel().update(position, selectedArticle);
- });
+ openGalleryForType(article, holder, holder.textImage);
+ }
+
+ return true;
+ });
}
if (holder.scoreView != null) {
- int scoreDrawable = R.drawable.baseline_trending_flat_24;
+ if (m_activity.getApiLevel() >= 16) {
+ holder.scoreView.setOnClickListener(view -> {
+ int position = m_list.getChildAdapterPosition(holder.view);
- if (article.score > 0)
- scoreDrawable = R.drawable.baseline_trending_up_24;
- else if (article.score < 0)
- scoreDrawable = R.drawable.baseline_trending_down_24;
+ if (position != -1) {
- holder.scoreView.setIconResource(scoreDrawable);
+ Article article = new Article(getItem(position));
- if (article.score > Article.SCORE_HIGH)
- holder.scoreView.setIconTint(colorTertiary);
- else
- holder.scoreView.setIconTint(colorPrimary);
+ final EditText edit = new EditText(getActivity());
+ edit.setText(String.valueOf(article.score));
- if (m_activity.getApiLevel() >= 16) {
- holder.scoreView.setOnClickListener(v -> {
- final EditText edit = new EditText(getActivity());
- edit.setText(String.valueOf(article.score));
-
- MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(getContext())
- .setTitle(R.string.score_for_this_article)
- .setPositiveButton(R.string.set_score,
- (dialog, which) -> {
- try {
- article.score = Integer.parseInt(edit.getText().toString());
- m_activity.saveArticleScore(article);
- m_adapter.notifyItemChanged(m_list.getChildAdapterPosition(holder.view));
- } catch (NumberFormatException e) {
- m_activity.toast(R.string.score_invalid);
- e.printStackTrace();
- }
- })
- .setNegativeButton(getString(R.string.cancel),
- (dialog, which) -> { }).setView(edit);
-
- Dialog dialog = builder.create();
- dialog.show();
- });
+ 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);
}
}
- if (holder.publishedView != null) {
+ return holder;
+ }
- // otherwise we just use tinting in actionbar
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
- holder.publishedView.setIconResource(article.published ? R.drawable.rss_box : R.drawable.rss);
- }
+ @Override public void onViewRecycled(@NonNull ArticleViewHolder holder){
+ super.onViewRecycled(holder);
+
+ if (holder.flavorImageView != null)
+ Glide.with(HeadlinesFragment.this).clear(holder.flavorImageView);
+ }
- holder.publishedView.setIconTint(article.published ? colorTertiary : colorPrimary);
+ @Override
+ // https://stackoverflow.com/questions/33176336/need-an-example-about-recyclerview-adapter-notifyitemchangedint-position-objec/50085835#50085835
+ public void onBindViewHolder(@NonNull final ArticleViewHolder holder, final int position, final List<Object> payloads) {
+ if (!payloads.isEmpty()) {
+ Log.d(TAG, "onBindViewHolder, payloads=" + payloads + " position=" + position);
+
+ final Article article = getItem(position);
+
+ for (final Object pobject : payloads) {
+ ArticleDiffItemCallback.ChangePayload payload = (ArticleDiffItemCallback.ChangePayload) pobject;
+
+ switch (payload) {
+ case UNREAD:
+ updateUnreadView(article, holder);
+ break;
+ case MARKED:
+ updateMarkedView(article, holder);
+ break;
+ case SELECTED:
+ updateSelectedView(article, holder);
+ updateTextImage(article, holder);
+ break;
+ case PUBLISHED:
+ updatePublishedView(article, holder);
+ break;
+ case SCORE:
+ updateScoreView(article, holder);
+ break;
+ }
+ }
+ } else {
+ super.onBindViewHolder(holder, position, payloads);
+ }
+ }
- holder.publishedView.setOnClickListener(v -> {
- Article selectedArticle = new Article(getItem(position));
- selectedArticle.published = !selectedArticle.published;
+ private void updateUnreadView(final Article article, final ArticleViewHolder holder) {
+ if (m_compactLayoutMode) {
+ holder.view.setBackgroundColor(article.unread ? m_colorSurfaceContainerLowest : 0);
+ } else {
+ MaterialCardView card = (MaterialCardView) holder.view;
- m_activity.saveArticlePublished(selectedArticle);
+ card.setCardBackgroundColor(article.unread ? m_colorSurfaceContainerLowest : m_colorSurface);
+ }
- Application.getArticlesModel().update(position, selectedArticle);
- });
+ if (holder.titleView != null) {
+ holder.titleView.setTypeface(null, article.unread ? Typeface.BOLD : Typeface.NORMAL);
+ holder.titleView.setTextColor(article.unread ? m_colorOnSurface : m_colorPrimary);
}
- if (holder.attachmentsView != null) {
- if (article.attachments != null && !article.attachments.isEmpty()) {
- holder.attachmentsView.setVisibility(View.VISIBLE);
+ updateActiveView(article, holder);
+ }
- holder.attachmentsView.setOnClickListener(v -> m_activity.displayAttachments(article));
+ private void updateActiveView(final Article article, final ArticleViewHolder holder) {
+ if (article.id == m_activeArticleId) {
+ holder.view.setBackgroundColor(m_colorTertiaryContainer);
- } else {
- holder.attachmentsView.setVisibility(View.GONE);
+ if (holder.titleView != null) {
+ holder.titleView.setTextColor(m_colorOnTertiaryContainer);
}
}
if (holder.excerptView != null) {
- if (!m_prefs.getBoolean("headlines_show_content", true)) {
- holder.excerptView.setVisibility(View.GONE);
- } else {
- String excerpt = "";
+ holder.excerptView.setTextColor(article.id == m_activeArticleId ? m_colorOnTertiaryContainer : m_colorOnSurface);
+ }
- try {
- if (article.excerpt != null) {
- excerpt = article.excerpt;
- } else if (article.articleDoc != null) {
- excerpt = article.articleDoc.text();
+ if (holder.feedTitleView != null) {
+ holder.feedTitleView.setTextColor(article.id == m_activeArticleId ? m_colorOnTertiaryContainer : m_colorSecondary);
+ }
- if (excerpt.length() > CommonActivity.EXCERPT_MAX_LENGTH)
- excerpt = excerpt.substring(0, CommonActivity.EXCERPT_MAX_LENGTH) + "…";
- }
- } catch (Exception e) {
- e.printStackTrace();
- excerpt = "";
- }
+ }
- holder.excerptView.setTextSize(TypedValue.COMPLEX_UNIT_SP, headlineFontSize);
- holder.excerptView.setText(excerpt);
+ @Override
+ public void onBindViewHolder(@NonNull final ArticleViewHolder holder, int position) {
+ Article article = getItem(position);
- if (!excerpt.isEmpty()) {
- holder.excerptView.setVisibility(View.VISIBLE);
- } else {
- holder.excerptView.setVisibility(View.GONE);
- }
+ if (article.id == Article.TYPE_AMR_FOOTER && m_prefs.getBoolean("headlines_mark_read_scroll", false)) {
+ WindowManager wm = (WindowManager) m_activity.getSystemService(Context.WINDOW_SERVICE);
+ Display display = wm.getDefaultDisplay();
+ int screenHeight = (int)(display.getHeight() * 1.5);
- if (!canShowFlavorImage()) {
- holder.excerptView.setPadding(holder.excerptView.getPaddingLeft(),
- 0,
- holder.excerptView.getPaddingRight(),
- holder.excerptView.getPaddingBottom());
- }
- }
+ holder.view.setLayoutParams(new ListView.LayoutParams(ListView.LayoutParams.MATCH_PARENT, screenHeight));
}
+ // nothing else of interest for those below anyway
+ if (article.id < 0) return;
+
+ updateUnreadView(article, holder);
+ updateTextImage(article, holder);
+ updateTitleView(article, holder);
+ updateMarkedView(article, holder);
+ updateScoreView(article, holder);
+ updatePublishedView(article, holder);
+ updateAttachmentsView(article, holder);
+ updateLinkHost(article, holder);
+ updateExcerptView(article, holder);
+ updateAuthorView(article, holder);
+ updateDateView(article, holder);
+ updateSelectedView(article, holder);
+
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.clear(holder.flavorImageView);
-
- // 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) {
- 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);
- holder.flavorImageView.setVisibility(View.VISIBLE);
- holder.flavorImageView.setMaxHeight((int)(m_screenHeight * 0.6f));
+ // we also downsample below using glide to save RAM
+ holder.flavorImageView.setMaxHeight(maxImageHeight);
- // only show holder if we're about to display a picture
- holder.flavorImageHolder.setVisibility(View.VISIBLE);
+ if (m_headlinesFragmentModel.getFlavorImageSizes().containsKey(article.flavorImageUri)) {
+ Size size = m_headlinesFragmentModel.getFlavorImageSizes().get(article.flavorImageUri);
- // 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);
+ Log.d(TAG, "using cached resource size for " + article.flavorImageUri + " " + size.getWidth() + "x" + size.getHeight());
- if (cachedHeight > 0) {
- FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) holder.flavorImageView.getLayoutParams();
- lp.height = cachedHeight;
+ if (size.getWidth() > FLAVOR_IMG_MIN_SIZE && size.getHeight() > FLAVOR_IMG_MIN_SIZE) {
+ loadFlavorImage(article, holder, maxImageHeight);
}
- }
-
- holder.flavorProgressTarget.setModel(article.flavorImageUri);
-
- try {
-
- Glide.with(getContext())
- .load(article.flavorImageUri)
- //.dontTransform()
- .diskCacheStrategy(DiskCacheStrategy.ALL)
- .skipMemoryCache(false)
- .listener(new RequestListener<String, GlideDrawable>() {
- @Override
- public boolean onException(Exception e, String model, Target<GlideDrawable> target, boolean isFirstResource) {
- holder.flavorImageLoadingBar.setVisibility(View.GONE);
- holder.flavorImageView.setVisibility(View.GONE);
-
- return false;
- }
-
- @Override
- public boolean onResourceReady(GlideDrawable resource, String model, Target<GlideDrawable> target, boolean isFromMemoryCache, boolean isFirstResource) {
-
- holder.flavorImageLoadingBar.setVisibility(View.GONE);
-
- if (resource.getIntrinsicWidth() > FLAVOR_IMG_MIN_SIZE && resource.getIntrinsicHeight() > FLAVOR_IMG_MIN_SIZE) {
-
- holder.flavorImageView.setVisibility(View.VISIBLE);
- holder.flavorImageOverflow.setVisibility(View.VISIBLE);
-
- adjustVideoKindView(holder, article);
-
- return false;
- } else {
-
- holder.flavorImageOverflow.setVisibility(View.GONE);
- holder.flavorImageView.setVisibility(View.GONE);
-
- return true;
- }
- }
- })
- .into(holder.flavorProgressTarget);
- } catch (OutOfMemoryError e) {
- e.printStackTrace();
+ } 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);
@@ -1233,23 +1261,29 @@ 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 : "";
-
- if (holder.authorView != null) {
- holder.authorView.setTextSize(TypedValue.COMPLEX_UNIT_SP, headlineSmallFontSize);
+ private void updateTitleView(final Article article, final ArticleViewHolder holder) {
+ if (holder.titleView != null) {
+ holder.titleView.setText(Html.fromHtml(article.title));
+ holder.titleView.setTextSize(TypedValue.COMPLEX_UNIT_SP, Math.min(21, m_headlineFontSize + 3));
+ }
- if (!articleAuthor.isEmpty()) {
- holder.authorView.setText(getString(R.string.author_formatted, articleAuthor));
+ if (holder.feedTitleView != null) {
+ if (article.feed_title != null && m_feed != null && (m_feed.is_cat || m_feed.id < 0)) {
+ holder.feedTitleView.setTextSize(TypedValue.COMPLEX_UNIT_SP, m_headlineSmallFontSize);
+ holder.feedTitleView.setText(article.feed_title);
} else {
- holder.authorView.setText("");
+ holder.feedTitleView.setVisibility(View.GONE);
}
}
+ }
+ private void updateDateView(final Article article, final ArticleViewHolder holder) {
if (holder.dateView != null) {
- holder.dateView.setTextSize(TypedValue.COMPLEX_UNIT_SP, headlineSmallFontSize);
+ holder.dateView.setTextSize(TypedValue.COMPLEX_UNIT_SP, m_headlineSmallFontSize);
Date d = new Date((long)article.updated * 1000);
Date now = new Date();
@@ -1268,40 +1302,189 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment {
df.setTimeZone(TimeZone.getDefault());
holder.dateView.setText(df.format(d));
}
+ }
+
+ private void updateAuthorView(final Article article, final ArticleViewHolder holder) {
+ String articleAuthor = article.author != null ? article.author : "";
+
+ if (holder.authorView != null) {
+ holder.authorView.setTextSize(TypedValue.COMPLEX_UNIT_SP, m_headlineSmallFontSize);
+
+ if (!articleAuthor.isEmpty()) {
+ holder.authorView.setText(getString(R.string.author_formatted, articleAuthor));
+ } else {
+ holder.authorView.setText("");
+ }
+ }
+ }
+
+ private void updateExcerptView(final Article article, final ArticleViewHolder holder) {
+ if (holder.excerptView != null) {
+ if (!m_prefs.getBoolean("headlines_show_content", true)) {
+ holder.excerptView.setVisibility(View.GONE);
+ } else {
+ String excerpt = "";
+
+ try {
+ if (article.excerpt != null) {
+ excerpt = article.excerpt;
+ } else if (article.articleDoc != null) {
+ excerpt = article.articleDoc.text();
+
+ if (excerpt.length() > CommonActivity.EXCERPT_MAX_LENGTH)
+ excerpt = excerpt.substring(0, CommonActivity.EXCERPT_MAX_LENGTH) + "…";
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ excerpt = "";
+ }
+ holder.excerptView.setTextSize(TypedValue.COMPLEX_UNIT_SP, m_headlineFontSize);
+ holder.excerptView.setText(excerpt);
+ if (!excerpt.isEmpty()) {
+ holder.excerptView.setVisibility(View.VISIBLE);
+ } else {
+ holder.excerptView.setVisibility(View.GONE);
+ }
+ }
+ }
+ }
+
+ private void updateLinkHost(final Article article, final ArticleViewHolder holder) {
+ if (holder.linkHost != null) {
+ if (article.isHostDistinct()) {
+ holder.linkHost.setTextSize(TypedValue.COMPLEX_UNIT_SP, m_headlineSmallFontSize);
+ holder.linkHost.setText(article.getLinkHost());
+ holder.linkHost.setVisibility(View.VISIBLE);
+ } else {
+ holder.linkHost.setVisibility(View.GONE);
+ }
+ }
+ }
+
+ private void updateAttachmentsView(final Article article, final ArticleViewHolder holder) {
+ if (holder.attachmentsView != null) {
+ if (article.attachments != null && !article.attachments.isEmpty()) {
+ holder.attachmentsView.setVisibility(View.VISIBLE);
+ } else {
+ holder.attachmentsView.setVisibility(View.GONE);
+ }
+ }
+ }
+
+ private void updateMarkedView(final Article article, final ArticleViewHolder holder) {
+ if (holder.markedView != null) {
+ holder.markedView.setIconResource(article.marked ? R.drawable.baseline_star_24 : R.drawable.baseline_star_outline_24);
+ holder.markedView.setIconTint(article.marked ? m_cslTertiary : m_cslPrimary);
+ }
+ }
+
+ private void updateTextImage(final Article article, final ArticleViewHolder holder) {
+ if (holder.textImage != null) {
+ updateTextCheckedState(article, holder);
+
+ ViewCompat.setTransitionName(holder.textImage,
+ "gallery:" + article.flavorImageUri);
+ }
+ }
+
+ private void updateSelectedView(final Article article, final ArticleViewHolder holder) {
if (holder.selectionBoxView != null) {
holder.selectionBoxView.setChecked(article.selected);
- holder.selectionBoxView.setOnClickListener(view -> {
- Article currentArticle = getItem(position);
+ }
+ }
- Log.d(TAG, "selectionCb onClick pos=" + position + " article=" + article);
+ private void updateScoreView(final Article article, final ArticleViewHolder holder) {
+ if (holder.scoreView != null) {
+ int scoreDrawable = R.drawable.baseline_trending_flat_24;
- CheckBox cb = (CheckBox)view;
+ if (article.score > 0)
+ scoreDrawable = R.drawable.baseline_trending_up_24;
+ else if (article.score < 0)
+ scoreDrawable = R.drawable.baseline_trending_down_24;
- currentArticle.selected = cb.isChecked();
+ holder.scoreView.setIconResource(scoreDrawable);
- m_listener.onArticleListSelectionChange();
- });
+ if (article.score > Article.SCORE_HIGH)
+ holder.scoreView.setIconTint(m_cslTertiary);
+ else
+ holder.scoreView.setIconTint(m_cslPrimary);
}
+ }
- if (holder.menuButtonView != null) {
- holder.menuButtonView.setOnClickListener(v -> {
+ private void updatePublishedView(final Article article, final ArticleViewHolder holder) {
+ if (holder.publishedView != null) {
+ // otherwise we just use tinting in actionbar
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
+ holder.publishedView.setIconResource(article.published ? R.drawable.rss_box : R.drawable.rss);
+ }
+
+ holder.publishedView.setIconTint(article.published ? m_cslTertiary : m_cslPrimary);
+ }
+ }
- PopupMenu popup = new PopupMenu(getActivity(), v);
- MenuInflater inflater = popup.getMenuInflater();
- inflater.inflate(R.menu.context_headlines, popup.getMenu());
+ private void loadFlavorImage(final Article article, final ArticleViewHolder holder, final int maxImageHeight) {
+ Glide.with(HeadlinesFragment.this)
+ .load(article.flavorImageUri)
+ .transition(DrawableTransitionOptions.withCrossFade())
+ .override(m_screenWidth, maxImageHeight)
+ .diskCacheStrategy(DiskCacheStrategy.DATA)
+ .skipMemoryCache(false)
+ .listener(new RequestListener<Drawable>() {
+ @Override
+ public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
+ holder.flavorImageHolder.setVisibility(View.GONE);
+
+ holder.flavorImageView.setVisibility(View.GONE);
+ holder.flavorImageOverflow.setVisibility(View.VISIBLE);
+
+ return false;
+ }
- 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);
+ @Override
+ public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
+ holder.flavorImageHolder.setVisibility(View.VISIBLE);
- popup.setOnMenuItemClickListener(item -> onArticleMenuItemSelected(item,
- getItem(position),
- m_list.getChildAdapterPosition(holder.view)));
+ holder.flavorImageView.setVisibility(View.VISIBLE);
+ holder.flavorImageOverflow.setVisibility(View.VISIBLE);
- popup.show();
- });
- }
+ adjustVideoKindView(holder, article);
+
+ return false;
+ }
+ })
+ .into(new DrawableImageViewTarget(holder.flavorImageView));
+ }
+
+ private void checkImageAndLoad(final Article article, final ArticleViewHolder holder, final int maxImageHeight) {
+ FlavorProgressTarget<Size> flavorProgressTarget = new FlavorProgressTarget<>(new SimpleTarget<Size>() {
+ @Override
+ public void onResourceReady(@NonNull Size resource, @Nullable com.bumptech.glide.request.transition.Transition<? super Size> 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
@@ -1310,20 +1493,12 @@ 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;
}
}
- 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) {
@@ -1336,27 +1511,15 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment {
if (!canShowFlavorImage() || article.flavorImage == null) {
holder.textImage.setImageDrawable(textDrawable);
-
} else {
- Glide.with(getContext())
+ Glide.with(HeadlinesFragment.this)
.load(article.flavorImageUri)
+ .transition(DrawableTransitionOptions.withCrossFade())
.placeholder(textDrawable)
.thumbnail(0.5f)
- .bitmapTransform(new CropCircleTransformation(getActivity()))
+ .apply(RequestOptions.circleCropTransform())
.diskCacheStrategy(DiskCacheStrategy.ALL)
.skipMemoryCache(false)
- .listener(new RequestListener<String, GlideDrawable>() {
- @Override
- public boolean onException(Exception e, String model, Target<GlideDrawable> target, boolean isFirstResource) {
- return false;
- }
-
- @Override
- public boolean onResourceReady(GlideDrawable resource, String model, Target<GlideDrawable> target, boolean isFromMemoryCache, boolean isFirstResource) {
-
- return resource.getIntrinsicWidth() < THUMB_IMG_MIN_SIZE || resource.getIntrinsicHeight() < THUMB_IMG_MIN_SIZE;
- }
- })
.into(holder.textImage);
}
@@ -1364,7 +1527,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment {
}
}
- private void openGalleryForType(Article article, ArticleViewHolder holder, View transitionView) {
+ private void openGalleryForType(final Article article, final ArticleViewHolder holder, final View transitionView) {
//Log.d(TAG, "openGalleryForType: " + article + " " + holder + " " + transitionView);
if ("iframe".equalsIgnoreCase(article.flavorImage.tagName())) {
@@ -1399,17 +1562,19 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment {
intent.putExtra("content", tempContent);
- ActivityOptionsCompat options =
+ /* ActivityOptionsCompat options =
ActivityOptionsCompat.makeSceneTransitionAnimation(m_activity,
transitionView != null ? transitionView : holder.flavorImageView,
"gallery:" + (article.flavorStreamUri != null ? article.flavorStreamUri : article.flavorImageUri));
- ActivityCompat.startActivity(m_activity, intent, options.toBundle());
+ ActivityCompat.startActivity(m_activity, intent, options.toBundle()); */
+
+ startActivity(intent);
}
}
- private void adjustVideoKindView(ArticleViewHolder holder, Article article) {
+ private void adjustVideoKindView(final ArticleViewHolder holder, final Article article) {
if (article.flavorImage != null) {
if (article.flavor_kind == Article.FLAVOR_KIND_YOUTUBE || "iframe".equalsIgnoreCase(article.flavorImage.tagName())) {
holder.flavorVideoKindView.setImageResource(R.drawable.baseline_play_circle_outline_24);
@@ -1424,20 +1589,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() {
@@ -1482,36 +1633,34 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment {
if (oldPosition != -1)
m_adapter.notifyItemChanged(oldPosition);
- m_adapter.notifyItemChanged(newPosition);
+ if (newPosition != -1) {
+ m_adapter.notifyItemChanged(newPosition);
- scrollToArticleId(articleId);
+ scrollToArticleId(articleId);
- if (newPosition >= articles.size() - 5)
- new Handler().postDelayed(() -> refresh(true), 0);
+ if (newPosition >= articles.size() - 5)
+ new Handler().postDelayed(() -> refresh(true), 0);
+ }
}
}
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/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<String, Size> m_flavorImageSizes = new HashMap<>();
+
+ public HashMap<String, Size> getFlavorImageSizes() {
+ return m_flavorImageSizes;
+ }
+
+ public HeadlinesFragmentModel(@NonNull Application application) {
+ super(application);
+ }
+}
diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/MasterActivity.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/MasterActivity.java
index f2ef3aa0..19cfbe0c 100755
--- a/org.fox.ttrss/src/main/java/org/fox/ttrss/MasterActivity.java
+++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/MasterActivity.java
@@ -417,26 +417,28 @@ public class MasterActivity extends OnlineActivity implements HeadlinesEventList
}
public void onArticleSelected(Article article, boolean open) {
+ Article articleClone = new Article(article);
+
+ if (articleClone.unread) {
+ articleClone.unread = false;
+ saveArticleUnread(articleClone);
+
+ Application.getArticlesModel().updateById(articleClone);
+ }
+
if (open) {
- boolean alwaysOpenUri = m_prefs.getBoolean("always_open_uri", false);
- if (alwaysOpenUri) {
- if (article.unread) {
- article.unread = false;
- saveArticleUnread(article);
- }
+ HeadlinesFragment hf = (HeadlinesFragment) getSupportFragmentManager().findFragmentByTag(FRAG_HEADLINES);
- HeadlinesFragment hf = (HeadlinesFragment) getSupportFragmentManager().findFragmentByTag(FRAG_HEADLINES);
+ if (m_prefs.getBoolean("always_open_uri", false)) {
- if (hf != null) {
+ if (hf != null) {
hf.setActiveArticleId(article.id);
}
openUri(Uri.parse(article.link));
- }
- else {
- HeadlinesFragment hf = (HeadlinesFragment) getSupportFragmentManager().findFragmentByTag(FRAG_HEADLINES);
+ } else if (hf != null) {
- Intent intent = new Intent(MasterActivity.this, DetailActivity.class);
+ Intent intent = new Intent(MasterActivity.this, DetailActivity.class);
intent.putExtra("feed", hf.getFeed());
intent.putExtra("searchQuery", hf.getSearchQuery());
intent.putExtra("openedArticleId", article.id);
@@ -446,12 +448,7 @@ public class MasterActivity extends OnlineActivity implements HeadlinesEventList
}
} else {
invalidateOptionsMenu();
-
- if (article.unread) {
- article.unread = false;
- saveArticleUnread(article);
- }
- }
+ }
}
@Override
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..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
@@ -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);
+
+ ft.commit();
- finish();
+ invalidateOptionsMenu();
- startActivity(intent);
- overridePendingTransition(0, 0);
});
Dialog dialog = builder.create();
@@ -570,8 +570,6 @@ public class OnlineActivity extends CommonActivity {
if (selectedArticle != null) {
setArticleScore(selectedArticle);
-
- hf.notifyItemChanged(Application.getArticles().indexOf(selectedArticle));
}
}
return true;
@@ -580,11 +578,13 @@ public class OnlineActivity extends CommonActivity {
Article selectedArticle = Application.getArticles().getById(ap.getSelectedArticleId());
if (selectedArticle != null) {
- selectedArticle.marked = !selectedArticle.marked;
+ Article articleClone = new Article(selectedArticle);
+
+ articleClone.marked = !articleClone.marked;
saveArticleMarked(selectedArticle);
- hf.notifyItemChanged(Application.getArticles().indexOf(selectedArticle));
+ Application.getArticlesModel().updateById(articleClone);
}
}
return true;
@@ -593,11 +593,13 @@ public class OnlineActivity extends CommonActivity {
Article selectedArticle = Application.getArticles().getById(ap.getSelectedArticleId());
if (selectedArticle != null) {
- selectedArticle.unread = !selectedArticle.unread;
+ Article articleClone = new Article(selectedArticle);
+
+ articleClone.unread = !articleClone.unread;
saveArticleUnread(selectedArticle);
- hf.notifyItemChanged(Application.getArticles().indexOf(selectedArticle));
+ Application.getArticlesModel().updateById(articleClone);
}
}
return true;
@@ -607,9 +609,11 @@ public class OnlineActivity extends CommonActivity {
if (!selected.isEmpty()) {
for (Article a : selected) {
- a.unread = !a.unread;
+ Article articleClone = new Article(a);
- hf.notifyItemChanged(Application.getArticles().indexOf(a));
+ articleClone.unread = !articleClone.unread;
+
+ Application.getArticlesModel().updateById(articleClone);
}
toggleArticlesUnread(selected);
@@ -623,9 +627,11 @@ public class OnlineActivity extends CommonActivity {
if (!selected.isEmpty()) {
for (Article a : selected) {
- a.marked = !a.marked;
+ Article articleClone = new Article(a);
+
+ articleClone.marked = !articleClone.marked;
- hf.notifyItemChanged(Application.getArticles().indexOf(a));
+ Application.getArticlesModel().updateById(articleClone);
}
toggleArticlesMarked(selected);
@@ -639,9 +645,11 @@ public class OnlineActivity extends CommonActivity {
if (!selected.isEmpty()) {
for (Article a : selected) {
- a.published = !a.published;
+ Article articleClone = new Article(a);
+
+ articleClone.published = !articleClone.published;
- hf.notifyItemChanged(Application.getArticles().indexOf(a));
+ Application.getArticlesModel().updateById(articleClone);
}
toggleArticlesPublished(selected);
@@ -654,10 +662,12 @@ public class OnlineActivity extends CommonActivity {
Article selectedArticle = Application.getArticles().getById(ap.getSelectedArticleId());
if (selectedArticle != null) {
- selectedArticle.published = !selectedArticle.published;
- saveArticlePublished(selectedArticle);
+ Article articleClone = new Article(selectedArticle);
- hf.notifyItemChanged(Application.getArticles().indexOf(selectedArticle));
+ articleClone.published = !articleClone.published;
+ saveArticlePublished(articleClone);
+
+ Application.getArticlesModel().updateById(articleClone);
}
}
return true;
@@ -707,15 +717,14 @@ public class OnlineActivity extends CommonActivity {
if (a.id == selectedArticleId)
break;
- if (a.unread) {
- a.unread = false;
- tmp.add(a);
+ Article articleClone = new Article(a);
- if (hf != null) {
- int position = Application.getArticles().indexOf(a);
+ if (articleClone.unread) {
+ articleClone.unread = false;
- hf.notifyItemChanged(position);
- }
+ tmp.add(articleClone);
+
+ Application.getArticlesModel().updateById(articleClone);
}
}
@@ -739,16 +748,6 @@ public class OnlineActivity extends CommonActivity {
String note = topicEdit.getText().toString().trim();
saveArticleNote(article, note);
-
- int position = Application.getArticles().getPositionById(article.id);
-
- if (position != -1) {
- HeadlinesFragment hf = (HeadlinesFragment) getSupportFragmentManager().findFragmentByTag(FRAG_HEADLINES);
- if (hf != null) hf.notifyItemChanged(position);
-
- ArticlePager ap = (ArticlePager) getSupportFragmentManager().findFragmentByTag(FRAG_ARTICLE);
- if (ap != null) ap.notifyItemChanged(position);
- }
});
builder.setNegativeButton(R.string.dialog_cancel, (dialog, which) -> {
@@ -952,7 +951,11 @@ public class OnlineActivity extends CommonActivity {
public void saveArticleNote(final Article article, final String note) {
ApiRequest req = new ApiRequest(getApplicationContext()) {
protected void onPostExecute(JsonElement result) {
- article.note = note;
+ Article articleClone = new Article(article);
+
+ articleClone.note = note;
+
+ Application.getArticlesModel().updateById(articleClone);
}
};
@@ -983,9 +986,17 @@ public class OnlineActivity extends CommonActivity {
(dialog, which) -> {
try {
-article.score = Integer.parseInt(edit.getText().toString());
+ Article articleClone = new Article(article);
+
+ articleClone.score = Integer.parseInt(edit.getText().toString());
+
+ saveArticleScore(articleClone);
+
+ int position = Application.getArticles().getPositionById(articleClone.id);
+
+ if (position != -1)
+ Application.getArticlesModel().updateById(articleClone);
- saveArticleScore(article);
} catch (NumberFormatException e) {
toast(R.string.score_invalid);
e.printStackTrace();
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<File, BitmapFactory.Options> {
+ @Override
+ public Resource<BitmapFactory.Options> 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 bb868f8e..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,15 +1,21 @@
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;
+import com.bumptech.glide.Registry;
+import com.bumptech.glide.annotation.GlideModule;
import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader;
import com.bumptech.glide.load.model.GlideUrl;
-import com.bumptech.glide.module.GlideModule;
+import com.bumptech.glide.module.AppGlideModule;
+import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
@@ -28,21 +34,25 @@ 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) {
+@GlideModule
+public class OkHttpProgressGlideModule extends AppGlideModule {
+ @Override public void registerComponents(Context context, Glide glide, Registry registry) {
OkHttpClient client = new OkHttpClient.Builder()
.addNetworkInterceptor(createInterceptor(new DispatchingProgressListener()))
.build();
- glide.register(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory(client));
+
+ // 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) {
return chain -> {
Request request = chain.request();
Response response = chain.proceed(request);
+
return response.newBuilder()
.body(new OkHttpProgressResponseBody(request.url(), response.body(), listener))
.build();
@@ -88,6 +98,7 @@ public class OkHttpProgressGlideModule implements GlideModule {
@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<BitmapFactory.Options, Size> {
+ @Override
+ public Resource<Size> transcode(Resource<BitmapFactory.Options> 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/java/org/fox/ttrss/glide/ProgressTarget.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/glide/ProgressTarget.java
index 977d1954..172dd28a 100644
--- 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
@@ -2,10 +2,15 @@ package org.fox.ttrss.glide;
import android.graphics.drawable.Drawable;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import com.bumptech.glide.Glide;
-import com.bumptech.glide.request.animation.GlideAnimation;
+import com.bumptech.glide.load.engine.Resource;
import com.bumptech.glide.request.target.Target;
+import com.bumptech.glide.request.transition.Transition;
public abstract class ProgressTarget<T, Z> extends WrappingTarget<Z> implements OkHttpProgressGlideModule.UIProgressListener {
private T model;
@@ -22,7 +27,6 @@ public abstract class ProgressTarget<T, Z> extends WrappingTarget<Z> implements
return model;
}
public final void setModel(T model) {
- Glide.clear(this); // indirectly calls cleanup
this.model = model;
}
/**
@@ -95,13 +99,14 @@ public abstract class ProgressTarget<T, Z> extends WrappingTarget<Z> implements
super.onLoadStarted(placeholder);
start();
}
- @Override public void onResourceReady(Z resource, GlideAnimation<? super Z> animation) {
+ /** @noinspection unchecked*/
+ public void onResourceReady(@NonNull Z resource, @Nullable Transition<? super Z> transition) {
cleanup();
- super.onResourceReady(resource, animation);
+ super.onResourceReady(resource, (Transition)transition);
}
- @Override public void onLoadFailed(Exception e, Drawable errorDrawable) {
+ @Override public void onLoadFailed(Drawable errorDrawable) {
cleanup();
- super.onLoadFailed(e, errorDrawable);
+ super.onLoadFailed(errorDrawable);
}
@Override public void onLoadCleared(Drawable placeholder) {
cleanup();
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
index 235acc3c..314800d9 100755
--- 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
@@ -3,11 +3,12 @@ package org.fox.ttrss.glide;
import android.graphics.drawable.Drawable;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import com.bumptech.glide.request.Request;
-import com.bumptech.glide.request.animation.GlideAnimation;
import com.bumptech.glide.request.target.SizeReadyCallback;
import com.bumptech.glide.request.target.Target;
+import com.bumptech.glide.request.transition.Transition;
public class WrappingTarget<Z> implements Target<Z> {
protected final @NonNull Target<? super Z> target;
@@ -21,16 +22,24 @@ public class WrappingTarget<Z> implements Target<Z> {
target.getSize(cb);
}
+ @Override
+ public void removeCallback(@NonNull SizeReadyCallback cb) {
+
+ }
+
@Override public void onLoadStarted(Drawable placeholder) {
target.onLoadStarted(placeholder);
}
- @Override public void onLoadFailed(Exception e, Drawable errorDrawable) {
- target.onLoadFailed(e, errorDrawable);
+ @Override public void onLoadFailed(Drawable errorDrawable) {
+ target.onLoadFailed(errorDrawable);
}
- @SuppressWarnings("unchecked")
- @Override public void onResourceReady(Z resource, GlideAnimation<? super Z> glideAnimation) {
- target.onResourceReady(resource, (GlideAnimation)glideAnimation);
+
+ /** @noinspection unchecked*/
+ @Override
+ public void onResourceReady(@NonNull Z resource, @Nullable Transition<? super Z> transition) {
+ target.onResourceReady(resource, (Transition)transition);
}
+
@Override public void onLoadCleared(Drawable placeholder) {
target.onLoadCleared(placeholder);
}
diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/util/ArticleDiffItemCallback.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/util/ArticleDiffItemCallback.java
index b037eea0..576048b3 100644
--- a/org.fox.ttrss/src/main/java/org/fox/ttrss/util/ArticleDiffItemCallback.java
+++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/util/ArticleDiffItemCallback.java
@@ -7,15 +7,37 @@ import org.fox.ttrss.types.Article;
public class ArticleDiffItemCallback extends DiffUtil.ItemCallback<Article> {
private final String TAG = this.getClass().getSimpleName();
+
+ public enum ChangePayload { UNREAD, MARKED, SELECTED, PUBLISHED, NOTE, SCORE };
+
@Override
- public boolean areItemsTheSame(@NonNull Article a1, @NonNull Article a2) {
- return a1.id == a2.id;
+ public boolean areItemsTheSame(@NonNull Article oldItem, @NonNull Article newItem) {
+ return oldItem.id == newItem.id;
+ }
+
+ @Override
+ public Object getChangePayload(@NonNull Article oldItem, @NonNull Article newItem) {
+
+ if (oldItem.unread != newItem.unread)
+ return ChangePayload.UNREAD;
+ else if (oldItem.marked != newItem.marked)
+ return ChangePayload.MARKED;
+ else if (oldItem.selected != newItem.selected)
+ return ChangePayload.SELECTED;
+ else if (oldItem.published != newItem.published)
+ return ChangePayload.PUBLISHED;
+ else if (!oldItem.note.equals(newItem.note))
+ return ChangePayload.NOTE;
+ else if (oldItem.score != newItem.score)
+ return ChangePayload.SCORE;
+
+ return null;
}
@Override
- public boolean areContentsTheSame(@NonNull Article a1, @NonNull Article a2) {
- return a1.id == a2.id && a1.unread == a2.unread && a1.marked == a2.marked
- && a1.selected == a2.selected && a1.published == a2.published
- && a1.note.equals(a2.note);
+ public boolean areContentsTheSame(@NonNull Article oldItem, @NonNull Article newItem) {
+ return oldItem.id == newItem.id && oldItem.unread == newItem.unread && oldItem.marked == newItem.marked
+ && oldItem.selected == newItem.selected && oldItem.published == newItem.published
+ && oldItem.score == newItem.score && oldItem.note.equals(newItem.note);
}
}
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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- https://github.com/ongakuer/CircleIndicator/issues/160 -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:padding="10dp"
+ android:shape="oval">
+ <solid android:color="?colorTertiary" />
+ <corners android:radius="25dp" />
+</shape> \ 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 b0f67761..f1ad3f8c 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">
- <com.bogdwellers.pinchtozoom.view.ImageViewPager
+ <androidx.viewpager2.widget.ViewPager2
android:id="@+id/gallery_pager"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
@@ -38,13 +38,14 @@
android:layout_marginLeft="@dimen/activity_horizontal_margin"
android:layout_marginRight="@dimen/activity_horizontal_margin" />
- <me.relex.circleindicator.CircleIndicator
+ <me.relex.circleindicator.CircleIndicator3
android:id="@+id/gallery_pager_indicator"
android:layout_width="fill_parent"
android:layout_height="32dp"
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" />
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 @@
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/flavorImageHolder"
+ android:id="@+id/flavor_image_holder"
android:layout_width="match_parent"
android:layout_height="match_parent">
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..85a93f33 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">
@@ -100,17 +99,20 @@
android:layout_height="match_parent" >
<FrameLayout
- android:id="@+id/flavorImageHolder"
+ android:id="@+id/flavor_image_holder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:layout_marginBottom="16dp"
android:layout_span="2">
<ProgressBar
- android:id="@+id/flavorImageLoadingBar"
+ android:id="@+id/flavor_image_progressbar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
+ android:layout_marginStart="16dp"
+ android:layout_marginEnd="16dp"
android:indeterminate="false"
android:max="100"
android:visibility="visible" />
@@ -121,7 +123,7 @@
android:layout_height="wrap_content"
android:layout_gravity="center"
android:adjustViewBounds="true"
- android:background="@android:color/transparent"
+ android:background="@null"
android:cropToPadding="true"
android:scaleType="centerCrop"
tools:src="@drawable/ic_launcher_background"
@@ -178,7 +180,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" />
</TableRow>
@@ -193,8 +196,7 @@
android:layout_height="wrap_content"
android:layout_span="2"
android:gravity="center_vertical"
- android:paddingBottom="8dp"
- android:paddingLeft="8dp">
+ android:paddingBottom="8dp">
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/selected"
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 @@
-<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout
- xmlns:tools="http://schemas.android.com/tools"
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- android:id="@+id/headlines_row"
- android:paddingTop="16dp"
- android:paddingBottom="16dp"
- android:paddingStart="16dp"
- android:paddingEnd="8dp"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:background="?colorTertiaryContainer"
- android:orientation="horizontal">
-
- <FrameLayout
- android:layout_gravity="center_vertical|start"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginEnd="16dp">
-
- <ImageView
- android:clickable="true"
- android:focusable="true"
- android:layout_width="48dp"
- android:layout_height="48dp"
- android:layout_gravity="center"
- android:id="@+id/text_image"/>
-
- <ImageView
- android:layout_width="48dp"
- android:layout_height="48dp"
- android:src="@drawable/check_sm"
- android:id="@+id/text_checked"
- android:layout_gravity="center" />
-
- </FrameLayout>
-
- <LinearLayout
- android:layout_weight="1"
- android:orientation="vertical"
- android:layout_width="match_parent"
- android:layout_height="wrap_content">
-
- <TextView
- android:id="@+id/title"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:maxLines="2"
- android:ellipsize="end"
- tools:text="Sample entry title"
- android:textColor="?colorOnTertiaryContainer"
- android:textSize="18sp" />
-
- <TextView
- android:id="@+id/feed_title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:ellipsize="middle"
- android:fontFamily="sans-serif-light"
- android:singleLine="true"
- tools:text="Example Feed AAA AAA AAAAAA AAAA AAAAA AA A A AA AA"
- android:textColor="?colorOnTertiaryContainer"
- android:textSize="12sp"
- android:layout_marginTop="4dp" />
-
- <TextView
- android:id="@+id/excerpt"
- android:maxLines="2"
- android:ellipsize="end"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- tools:text="Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
- android:textColor="?colorOnTertiaryContainer"
- android:textSize="13sp"
- android:layout_marginTop="4dp" />
- </LinearLayout>
-
- <LinearLayout
- android:orientation="vertical"
- android:layout_weight="0"
- android:layout_width="48dp"
- android:layout_height="match_parent">
-
- <TextView
- android:id="@+id/date"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:fontFamily="sans-serif-light"
- android:singleLine="true"
- android:ellipsize="none"
- android:textAlignment="viewEnd"
- android:layout_weight="0.5"
- tools:text="Jan 01"
- android:textColor="?colorSecondary"
- android:textSize="12sp"
- />
-
- <com.google.android.material.button.MaterialButton
- android:id="@+id/marked"
- style="?attr/materialIconButtonStyle"
- android:layout_width="wrap_content"
- android:paddingEnd="0dp"
- android:layout_gravity="end"
- android:layout_height="24dp"
- android:layout_weight="0.5"
- app:icon="@drawable/baseline_star_outline_24" />
- </LinearLayout>
-</LinearLayout> \ 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 @@
-<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout
- xmlns:tools="http://schemas.android.com/tools"
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- android:id="@+id/headlines_row"
- android:paddingTop="16dp"
- android:paddingBottom="16dp"
- android:paddingStart="16dp"
- android:paddingEnd="8dp"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:background="?colorTertiaryContainer"
- android:orientation="horizontal">
-
- <FrameLayout
- android:layout_gravity="center_vertical|start"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginEnd="16dp">
-
- <ImageView
- android:clickable="true"
- android:focusable="true"
- android:layout_width="48dp"
- android:layout_height="48dp"
- android:layout_gravity="center"
- android:id="@+id/text_image"/>
-
- <ImageView
- android:layout_width="48dp"
- android:layout_height="48dp"
- android:src="@drawable/check_sm"
- android:id="@+id/text_checked"
- android:layout_gravity="center" />
-
- </FrameLayout>
-
- <LinearLayout
- android:layout_weight="1"
- android:orientation="vertical"
- android:layout_width="match_parent"
- android:layout_height="wrap_content">
-
- <TextView
- android:id="@+id/title"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:maxLines="2"
- android:ellipsize="end"
- tools:text="Sample entry title"
- android:textColor="?colorOnTertiaryContainer"
- android:textStyle="bold"
- android:textSize="18sp" />
-
- <TextView
- android:id="@+id/feed_title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:ellipsize="middle"
- android:fontFamily="sans-serif-light"
- android:singleLine="true"
- tools:text="Example Feed AAA AAA AAAAAA AAAA AAAAA AA A A AA AA"
- android:textColor="?colorOnTertiaryContainer"
- android:textSize="12sp"
- android:layout_marginTop="4dp" />
-
- <TextView
- android:id="@+id/excerpt"
- android:maxLines="2"
- android:ellipsize="end"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- tools:text="Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
- android:textColor="?colorOnTertiaryContainer"
- android:textSize="13sp"
- android:layout_marginTop="4dp" />
- </LinearLayout>
-
- <LinearLayout
- android:orientation="vertical"
- android:layout_weight="0"
- android:layout_width="48dp"
- android:layout_height="match_parent">
-
- <TextView
- android:id="@+id/date"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:fontFamily="sans-serif-light"
- android:singleLine="true"
- android:ellipsize="none"
- android:textAlignment="viewEnd"
- android:layout_weight="0.5"
- tools:text="Jan 01"
- android:textColor="?colorSecondary"
- android:textSize="12sp"
- />
-
- <com.google.android.material.button.MaterialButton
- android:id="@+id/marked"
- style="?attr/materialIconButtonStyle"
- android:layout_width="wrap_content"
- android:paddingEnd="0dp"
- android:layout_gravity="end"
- android:layout_height="24dp"
- android:layout_weight="0.5"
- app:icon="@drawable/baseline_star_outline_24" />
- </LinearLayout>
-</LinearLayout> \ 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 @@
-<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout
- xmlns:tools="http://schemas.android.com/tools"
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- android:id="@+id/headlines_row"
- android:paddingTop="16dp"
- android:paddingBottom="16dp"
- android:paddingStart="16dp"
- android:paddingEnd="8dp"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:background="?colorSurfaceContainerLowest"
- android:orientation="horizontal">
-
- <FrameLayout
- android:layout_gravity="center_vertical|start"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginEnd="16dp">
-
- <ImageView
- android:clickable="true"
- android:focusable="true"
- android:layout_width="48dp"
- android:layout_height="48dp"
- android:layout_gravity="center"
- android:id="@+id/text_image"/>
-
- <ImageView
- android:layout_width="48dp"
- android:layout_height="48dp"
- android:src="@drawable/check_sm"
- android:id="@+id/text_checked"
- android:layout_gravity="center" />
-
- </FrameLayout>
-
- <LinearLayout
- android:layout_weight="1"
- android:orientation="vertical"
- android:layout_width="match_parent"
- android:layout_height="wrap_content">
-
- <TextView
- android:id="@+id/title"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:maxLines="2"
- android:ellipsize="end"
- tools:text="Sample entry title"
- android:textStyle="bold"
- android:textSize="18sp" />
-
- <TextView
- android:id="@+id/feed_title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:ellipsize="middle"
- android:fontFamily="sans-serif-light"
- android:singleLine="true"
- tools:text="Example Feed AAA AAA AAAAAA AAAA AAAAA AA A A AA AA"
- android:textColor="?colorSecondary"
- android:textSize="12sp"
- android:layout_marginTop="4dp" />
-
- <TextView
- android:id="@+id/excerpt"
- android:maxLines="2"
- android:ellipsize="end"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- tools:text="Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
- android:textColor="?colorSecondary"
- android:textSize="13sp"
- android:layout_marginTop="4dp" />
-
- </LinearLayout>
-
- <LinearLayout
- android:orientation="vertical"
- android:layout_weight="0"
- android:layout_width="48dp"
- android:layout_height="match_parent">
-
- <TextView
- android:id="@+id/date"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:fontFamily="sans-serif-light"
- android:singleLine="true"
- android:ellipsize="none"
- android:textAlignment="viewEnd"
- android:layout_weight="0.5"
- tools:text="Jan 01"
- android:textColor="?colorSecondary"
- android:textSize="12sp"
- />
-
- <com.google.android.material.button.MaterialButton
- android:id="@+id/marked"
- style="?attr/materialIconButtonStyle"
- android:layout_width="wrap_content"
- android:paddingEnd="0dp"
- android:layout_gravity="end"
- android:layout_height="24dp"
- android:layout_weight="0.5"
- app:icon="@drawable/baseline_star_outline_24" />
- </LinearLayout>
-</LinearLayout> \ 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 7cae0fb1..00000000
--- a/org.fox.ttrss/src/main/res/layout/headlines_row_unread.xml
+++ /dev/null
@@ -1,273 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- android:id="@+id/headlines_row"
- app:cardBackgroundColor="?colorSurfaceContainerLowest"
- android:layout_marginStart="8dp"
- android:layout_marginEnd="8dp"
- android:layout_marginTop="8dp"
- app:strokeWidth="0dp"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content">
-
- <TableLayout
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:baselineAligned="false"
- android:shrinkColumns="0,1"
- android:stretchColumns="0,1">
-
- <TableRow
- android:layout_width="fill_parent"
- android:layout_height="wrap_content">
-
- <RelativeLayout
- 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">
-
- <TextView
- android:id="@+id/title"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:ellipsize="end"
- android:maxLines="3"
- android:paddingBottom="8dp"
- android:singleLine="false"
- android:textColor="?colorOnSurface"
- android:textSize="18sp"
- android:textStyle="bold"
- tools:text="Sample entry title which is overwhelmingly long blah blah blah" />
-
- <LinearLayout
- android:id="@+id/linearLayout2"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_below="@+id/title"
- android:orientation="horizontal">
-
- <TextView
- android:id="@+id/feed_title"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_weight="0.5"
- android:ellipsize="middle"
- android:fontFamily="sans-serif-light"
- android:singleLine="true"
- android:textAlignment="viewStart"
- android:textColor="?colorSecondary"
- android:textSize="12sp"
- tools:text="Example Feed AAA AAA AAAAAA AAAA AAAAA AA A A AA AA" />
-
- <TextView
- android:id="@+id/date"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_weight="0.5"
- android:ellipsize="none"
- android:fontFamily="sans-serif-light"
- android:singleLine="true"
- android:textAlignment="viewEnd"
- android:textColor="?colorSecondary"
- android:textSize="12sp"
- tools:text="Jan 01, 12:00, 1970" />
-
- </LinearLayout>
-
- <TextView
- android:id="@+id/link_host"
- android:layout_width="match_parent"
- android:layout_marginTop="8dp"
- android:layout_below="@+id/linearLayout2"
- android:layout_height="wrap_content"
- android:ellipsize="middle"
- android:fontFamily="sans-serif-light"
- android:singleLine="true"
- android:textAlignment="viewStart"
- android:textColor="?colorSecondary"
- android:textSize="12sp"
- tools:text="example.com" />
-
- </RelativeLayout>
-
- </TableRow>
-
- <TableRow
- android:layout_width="match_parent"
- android:layout_height="match_parent" >
-
- <FrameLayout
- android:id="@+id/flavorImageHolder"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_span="2">
-
- <ProgressBar
- android:id="@+id/flavorImageLoadingBar"
- style="?android:attr/progressBarStyleHorizontal"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:indeterminate="false"
- android:max="100"
- android:visibility="visible" />
-
- <ImageView
- android:id="@+id/flavor_image"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:adjustViewBounds="true"
- android:background="@android:color/transparent"
- android:cropToPadding="true"
- android:scaleType="centerCrop"
- tools:src="@drawable/ic_launcher_background"
- android:visibility="visible" />
-
- <TextureView
- android:id="@+id/flavor_video"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:adjustViewBounds="true"
- android:background="@null"
- android:cropToPadding="true"
- android:foreground="@null"
- android:scaleType="fitCenter"
- android:visibility="gone" />
-
- <ImageView
- android:id="@+id/flavor_video_kind"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="start|bottom"
- android:layout_marginStart="8dp"
- android:layout_marginBottom="10dp"
- android:scaleType="fitXY"
- android:src="@drawable/baseline_play_circle_24"
- android:visibility="visible"
- app:tint="?colorTertiary" />
-
- <com.google.android.material.button.MaterialButton
- android:id="@+id/gallery_overflow"
- style="?attr/materialIconButtonStyle"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="bottom|end"
- android:layout_marginEnd="-8dp"
- app:icon="@drawable/baseline_more_vert_24"
- app:iconTint="?colorTertiary"/>
-
- </FrameLayout>
- </TableRow>
-
- <TableRow
- android:layout_width="fill_parent"
- android:layout_height="wrap_content">
-
- <TextView
- android:id="@+id/excerpt"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_span="2"
- android:ellipsize="end"
- android:textAlignment="viewStart"
- android:lineSpacingExtra="2sp"
- android:maxLines="5"
- android:padding="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" />
- </TableRow>
-
- <TableRow
- android:layout_width="fill_parent"
- android:layout_height="wrap_content">
-
- <LinearLayout
- android:id="@+id/headline_footer"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_span="2"
- android:gravity="center_vertical"
- android:paddingBottom="8dp"
- android:paddingLeft="8dp">
-
- <com.google.android.material.checkbox.MaterialCheckBox
- android:id="@+id/selected"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_weight="0"
- android:focusable="false" />
-
- <TextView
- android:id="@+id/author"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:layout_weight="1"
- android:ellipsize="middle"
- android:fontFamily="sans-serif-light"
- android:gravity="center_vertical"
- android:singleLine="true"
- tools:text="by Author"
- android:textAlignment="viewStart"
- android:textColor="?colorSecondary"
- android:textSize="12sp"
- android:textStyle="italic" />
-
- <com.google.android.material.button.MaterialButton
- style="?attr/materialIconButtonStyle"
- android:id="@+id/score"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_weight="0"
- android:paddingLeft="4dp"
- android:paddingRight="4dp"
- app:icon="@drawable/baseline_trending_flat_24" />
-
- <com.google.android.material.button.MaterialButton
- style="?attr/materialIconButtonStyle"
- android:id="@+id/attachments"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_weight="0"
- android:paddingLeft="4dp"
- android:paddingRight="4dp"
- app:icon="@drawable/baseline_attachment_24" />
-
- <com.google.android.material.button.MaterialButton
- style="?attr/materialIconButtonStyle"
- android:id="@+id/marked"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_weight="0"
- android:paddingLeft="4dp"
- android:paddingRight="4dp"
- app:icon="@drawable/baseline_star_outline_24" />
-
- <com.google.android.material.button.MaterialButton
- style="?attr/materialIconButtonStyle"
- android:id="@+id/published"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_weight="0"
- android:paddingLeft="4dp"
- android:paddingRight="4dp"
- app:icon="@drawable/rss" />
-
- <com.google.android.material.button.MaterialButton
- style="?attr/materialIconButtonStyle"
- android:id="@+id/article_menu_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_weight="0"
- android:paddingLeft="4dp"
- android:paddingRight="4dp"
- app:icon="@drawable/baseline_more_vert_24" />
- </LinearLayout>
- </TableRow>
- </TableLayout>
-</com.google.android.material.card.MaterialCardView> \ No newline at end of file
diff --git a/org.fox.ttrss/src/main/res/values/strings.xml b/org.fox.ttrss/src/main/res/values/strings.xml
index df82688b..6f34b705 100755
--- a/org.fox.ttrss/src/main/res/values/strings.xml
+++ b/org.fox.ttrss/src/main/res/values/strings.xml
@@ -305,4 +305,7 @@
<string name="open_on_startup">Open on startup</string>
<string name="error_success">Operation completed successfully</string>
<string name="error_bad_request">Error: 400 bad request</string>
+ <string name="headlines_set_display_mode">Set display mode</string>
+ <string name="window_secure_mode_summary">Disables screenshots and hides window contents on non-secure displays</string>
+ <string name="window_secure_mode">Secure window mode</string>
</resources>
diff --git a/org.fox.ttrss/src/main/res/xml/preferences.xml b/org.fox.ttrss/src/main/res/xml/preferences.xml
index 8d064587..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" />
+
+ <SwitchPreferenceCompat
+ android:defaultValue="false"
+ android:key="window_secure_mode"
+ android:summary="@string/window_secure_mode_summary"
+ android:title="@string/window_secure_mode" />
</PreferenceCategory>
<PreferenceCategory
@@ -134,6 +140,7 @@
android:summary="@string/prefs_always_downsample_images_long" />
<SwitchPreferenceCompat
+ android:enabled="false"
android:defaultValue="false"
android:key="inline_video_player"
android:summary="@string/prefs_inline_video_player"