summaryrefslogtreecommitdiff
path: root/org.fox.ttrss/src
diff options
context:
space:
mode:
Diffstat (limited to 'org.fox.ttrss/src')
-rwxr-xr-xorg.fox.ttrss/src/main/AndroidManifest.xml4
-rw-r--r--org.fox.ttrss/src/main/java/org/fox/ttrss/ApiCommon.java8
-rwxr-xr-xorg.fox.ttrss/src/main/java/org/fox/ttrss/ApiLoader.java2
-rw-r--r--org.fox.ttrss/src/main/java/org/fox/ttrss/ApiRequest.java2
-rwxr-xr-xorg.fox.ttrss/src/main/java/org/fox/ttrss/Application.java31
-rwxr-xr-xorg.fox.ttrss/src/main/java/org/fox/ttrss/ArticleFragment.java4
-rw-r--r--org.fox.ttrss/src/main/java/org/fox/ttrss/ArticleModel.java315
-rwxr-xr-xorg.fox.ttrss/src/main/java/org/fox/ttrss/ArticlePager.java262
-rwxr-xr-xorg.fox.ttrss/src/main/java/org/fox/ttrss/BaseFeedlistFragment.java2
-rwxr-xr-xorg.fox.ttrss/src/main/java/org/fox/ttrss/CommonActivity.java20
-rwxr-xr-xorg.fox.ttrss/src/main/java/org/fox/ttrss/DetailActivity.java50
-rwxr-xr-xorg.fox.ttrss/src/main/java/org/fox/ttrss/FeedCategoriesFragment.java12
-rwxr-xr-xorg.fox.ttrss/src/main/java/org/fox/ttrss/FeedsFragment.java6
-rw-r--r--org.fox.ttrss/src/main/java/org/fox/ttrss/HeadlinesEventListener.java2
-rwxr-xr-xorg.fox.ttrss/src/main/java/org/fox/ttrss/HeadlinesFragment.java520
-rwxr-xr-xorg.fox.ttrss/src/main/java/org/fox/ttrss/MasterActivity.java22
-rwxr-xr-xorg.fox.ttrss/src/main/java/org/fox/ttrss/OnlineActivity.java118
-rw-r--r--org.fox.ttrss/src/main/java/org/fox/ttrss/share/CommonActivity.java14
-rw-r--r--org.fox.ttrss/src/main/java/org/fox/ttrss/share/ShareActivity.java2
-rwxr-xr-xorg.fox.ttrss/src/main/java/org/fox/ttrss/share/SubscribeActivity.java4
-rwxr-xr-xorg.fox.ttrss/src/main/java/org/fox/ttrss/types/Article.java62
-rwxr-xr-xorg.fox.ttrss/src/main/java/org/fox/ttrss/types/ArticleList.java23
-rw-r--r--org.fox.ttrss/src/main/java/org/fox/ttrss/util/ArticleDiffItemCallback.java21
-rw-r--r--org.fox.ttrss/src/main/java/org/fox/ttrss/util/DiffFragmentStateAdapter.java54
-rwxr-xr-xorg.fox.ttrss/src/main/java/org/fox/ttrss/util/HeadlinesRequest.java106
-rw-r--r--org.fox.ttrss/src/main/res/drawable/ic_launcher_background_variant.xml42
-rw-r--r--org.fox.ttrss/src/main/res/layout-sw600dp-land/activity_detail.xml2
-rw-r--r--org.fox.ttrss/src/main/res/layout-sw600dp-land/activity_master.xml2
-rwxr-xr-xorg.fox.ttrss/src/main/res/layout/fragment_headlines.xml1
-rwxr-xr-xorg.fox.ttrss/src/main/res/layout/headlines_row_compact_active.xml1
-rw-r--r--org.fox.ttrss/src/main/res/layout/layout_detail_phone.xml2
-rw-r--r--org.fox.ttrss/src/main/res/layout/layout_master_phone.xml2
-rw-r--r--org.fox.ttrss/src/main/res/mipmap-anydpi-v26/ic_launcher_variant.xml5
-rwxr-xr-xorg.fox.ttrss/src/main/res/values/strings.xml1
-rwxr-xr-xorg.fox.ttrss/src/main/res/xml/preferences.xml3
35 files changed, 925 insertions, 802 deletions
diff --git a/org.fox.ttrss/src/main/AndroidManifest.xml b/org.fox.ttrss/src/main/AndroidManifest.xml
index feb8a2e1..3388d515 100755
--- a/org.fox.ttrss/src/main/AndroidManifest.xml
+++ b/org.fox.ttrss/src/main/AndroidManifest.xml
@@ -17,7 +17,7 @@
android:name=".Application"
android:allowBackup="true"
android:hardwareAccelerated="true"
- android:icon="@mipmap/ic_launcher"
+ android:icon="${appIcon}"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config" >
@@ -217,7 +217,7 @@
<provider
android:name="androidx.core.content.FileProvider"
- android:authorities="org.fox.ttrss.SharedFileProvider"
+ android:authorities="${applicationId}.SharedFileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
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 75963c96..adc0881d 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
@@ -41,13 +41,16 @@ public class ApiCommon {
void setLastErrorMessage(String message);
}
- public enum ApiError { NO_ERROR, HTTP_UNAUTHORIZED, HTTP_FORBIDDEN, HTTP_NOT_FOUND,
+ public enum ApiError {
+ SUCCESS, UNKNOWN_ERROR, HTTP_UNAUTHORIZED, HTTP_FORBIDDEN, HTTP_NOT_FOUND,
HTTP_SERVER_ERROR, HTTP_OTHER_ERROR, SSL_REJECTED, SSL_HOSTNAME_REJECTED, PARSE_ERROR, IO_ERROR, OTHER_ERROR, API_DISABLED,
API_UNKNOWN, LOGIN_FAILED, INVALID_URL, API_INCORRECT_USAGE, NETWORK_UNAVAILABLE, API_UNKNOWN_METHOD }
public static int getErrorMessage(ApiError error) {
switch (error) {
- case NO_ERROR:
+ case SUCCESS:
+ return R.string.error_success;
+ case UNKNOWN_ERROR:
return R.string.error_unknown;
case HTTP_UNAUTHORIZED:
return R.string.error_http_unauthorized;
@@ -154,6 +157,7 @@ public class ApiCommon {
switch (statusCode) {
case API_STATUS_OK:
+ caller.setLastError(ApiError.SUCCESS);
return result.getAsJsonObject().get("content");
case API_STATUS_ERR:
JsonObject contentObj = resultObj.get("content").getAsJsonObject();
diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/ApiLoader.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/ApiLoader.java
index 259d7a28..8d83e7bd 100755
--- a/org.fox.ttrss/src/main/java/org/fox/ttrss/ApiLoader.java
+++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/ApiLoader.java
@@ -27,7 +27,7 @@ public class ApiLoader extends AsyncTaskLoader<JsonElement> implements ApiCommon
super(context);
m_context = context;
- m_lastError = ApiError.NO_ERROR;
+ m_lastError = ApiError.UNKNOWN_ERROR;
m_params = params;
}
diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/ApiRequest.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/ApiRequest.java
index 409047f0..ba8dad20 100644
--- a/org.fox.ttrss/src/main/java/org/fox/ttrss/ApiRequest.java
+++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/ApiRequest.java
@@ -21,7 +21,7 @@ public class ApiRequest extends AsyncTask<HashMap<String,String>, Integer, JsonE
public ApiRequest(Context context) {
m_context = context;
- m_lastError = ApiError.NO_ERROR;
+ m_lastError = ApiError.UNKNOWN_ERROR;
}
@SuppressLint("NewApi")
diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/Application.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/Application.java
index 07f1a3b5..75542e3c 100755
--- a/org.fox.ttrss/src/main/java/org/fox/ttrss/Application.java
+++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/Application.java
@@ -1,5 +1,8 @@
package org.fox.ttrss;
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
import android.os.Bundle;
import org.fox.ttrss.types.ArticleList;
@@ -8,22 +11,28 @@ import java.util.HashMap;
import java.util.LinkedHashMap;
public class Application extends android.app.Application {
- private static Application m_singleton;
+ public static final int LOADER_HEADLINES = 0;
+ public static final int LOADER_FEEDS = 1;
+ public static final int LOADER_CATS = 2;
- // this is the only instance of a (large) object which contains all currently loaded articles and is
- // used by all fragments and activities concurrently
- private final ArticleList m_articles = new ArticleList();
+ private static Application m_singleton;
private String m_sessionId;
private int m_apiLevel;
public LinkedHashMap<String, String> m_customSortModes = new LinkedHashMap<>();
+ ConnectivityManager m_cmgr;
+ ArticleModel m_articleModel;
public static Application getInstance(){
return m_singleton;
}
public static ArticleList getArticles() {
- return getInstance().m_articles;
+ return getInstance().m_articleModel.getArticles().getValue();
+ }
+
+ public static ArticleModel getArticlesModel() {
+ return getInstance().m_articleModel;
}
@Override
@@ -31,6 +40,8 @@ public class Application extends android.app.Application {
super.onCreate();
m_singleton = this;
+ m_cmgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
+ m_articleModel = new ArticleModel(this);
}
public String getSessionId() {
@@ -68,6 +79,14 @@ public class Application extends android.app.Application {
m_customSortModes.clear();
m_customSortModes.putAll(tmp);
}
-
+ }
+
+ public boolean isWifiConnected() {
+ NetworkInfo wifi = m_cmgr.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
+
+ if (wifi != null)
+ return wifi.isConnected();
+
+ return false;
}
}
diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/ArticleFragment.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/ArticleFragment.java
index 3ee42976..8794e653 100755
--- a/org.fox.ttrss/src/main/java/org/fox/ttrss/ArticleFragment.java
+++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/ArticleFragment.java
@@ -157,7 +157,7 @@ public class ArticleFragment extends androidx.fragment.app.Fragment {
View noteContainer = view.findViewById(R.id.note_container);
if (note != null && noteContainer != null) {
- if (m_article.note != null && !m_article.note.isEmpty()) {
+ if (!m_article.note.isEmpty()) {
note.setTextSize(TypedValue.COMPLEX_UNIT_SP, m_articleSmallFontSize);
note.setText(m_article.note);
noteContainer.setVisibility(View.VISIBLE);
@@ -259,7 +259,7 @@ public class ArticleFragment extends androidx.fragment.app.Fragment {
String linkHexColor = String.format("#%06X", (0xFFFFFF & tvColorPrimary.data));
cssOverride += " a:link {color: "+linkHexColor+";} a:visited { color: "+linkHexColor+";}";
- String articleContent = m_article.content != null ? m_article.content : "";
+ String articleContent = m_article.content;
ws.setJavaScriptEnabled(false);
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
new file mode 100644
index 00000000..80467f63
--- /dev/null
+++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/ArticleModel.java
@@ -0,0 +1,315 @@
+package org.fox.ttrss;
+
+import android.app.Application;
+import android.content.SharedPreferences;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.AndroidViewModel;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
+import androidx.preference.PreferenceManager;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.reflect.TypeToken;
+
+import org.fox.ttrss.types.Article;
+import org.fox.ttrss.types.ArticleList;
+import org.fox.ttrss.types.Feed;
+
+import java.lang.reflect.Type;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+public class ArticleModel extends AndroidViewModel implements ApiCommon.ApiCaller {
+ private final String TAG = this.getClass().getSimpleName();
+ private final MutableLiveData<ArticleList> m_articles = new MutableLiveData<>(new ArticleList());
+ private SharedPreferences m_prefs;
+ private final int m_responseCode = 0;
+ protected String m_responseMessage;
+ private int m_apiStatusCode = 0;
+
+ private String m_lastErrorMessage;
+ private ApiCommon.ApiError m_lastError;
+ private Feed m_feed;
+ private int m_firstId;
+ private String m_searchQuery = "";
+ private boolean m_firstIdChanged;
+ private int m_offset;
+ private int m_amountLoaded;
+ private int m_resizeWidth;
+ private boolean m_append;
+ private boolean m_lazyLoadEnabled = true;
+ private boolean m_loadingInProgress;
+ private ExecutorService m_executor;
+ private Handler m_mainHandler = new Handler(Looper.getMainLooper());
+ private MutableLiveData<Long> m_lastUpdate = new MutableLiveData<>(Long.valueOf(0));
+
+ public ArticleModel(@NonNull Application application) {
+ super(application);
+
+ m_prefs = PreferenceManager.getDefaultSharedPreferences(application);
+
+ // do we need concurrency or not?
+ m_executor = Executors.newSingleThreadExecutor();
+ }
+
+ public LiveData<Long> getUpdatesData() {
+ return m_lastUpdate;
+ }
+
+ public LiveData<ArticleList> getArticles() {
+ return m_articles;
+ }
+
+
+ public void update(int position, Article article) {
+ m_articles.getValue().set(position, article);
+ m_articles.postValue(m_articles.getValue());
+ }
+
+ public void update(ArticleList articles) {
+ m_articles.postValue(articles);
+ }
+
+ public void startLoading(boolean append, @NonNull Feed feed, int resizeWidth) {
+ Log.d(TAG, "startLoading append=" + append);
+
+ m_resizeWidth = resizeWidth;
+
+ if (!append) {
+ m_append = false;
+ m_lazyLoadEnabled = true;
+ m_feed = feed;
+
+ loadInBackground();
+ } else if (feed != m_feed || m_lazyLoadEnabled && !m_loadingInProgress) {
+ m_append = true;
+ m_feed = feed;
+
+ loadInBackground();
+ } else {
+ m_articles.postValue(m_articles.getValue());
+ }
+ }
+
+ private void loadInBackground() {
+ Log.d(TAG, this + " loadInBackground append=" + m_append + " offset=" + m_offset);
+
+ ArticleList articlesWork = new ArticleList(m_articles.getValue());
+
+ m_loadingInProgress = true;
+
+ final int skip = getSkip(m_append, articlesWork);
+ final boolean allowForceUpdate = org.fox.ttrss.Application.getInstance().getApiLevel() >= 9 &&
+ !m_feed.is_cat && m_feed.id > 0 && !m_append && skip == 0;
+
+ HashMap<String,String> params = new HashMap<>();
+
+ params.put("op", "getHeadlines");
+ params.put("sid", org.fox.ttrss.Application.getInstance().getSessionId());
+ params.put("feed_id", String.valueOf(m_feed.id));
+ params.put("show_excerpt", "true");
+ params.put("excerpt_length", String.valueOf(CommonActivity.EXCERPT_MAX_LENGTH));
+ params.put("show_content", "true");
+ params.put("include_attachments", "true");
+ params.put("view_mode", m_prefs.getString("view_mode", "adaptive"));
+ params.put("limit", m_prefs.getString("headlines_request_size", "15"));
+ params.put("skip", String.valueOf(skip));
+ params.put("include_nested", "true");
+ params.put("has_sandbox", "true");
+ params.put("order_by", m_prefs.getString("headlines_sort_mode", "default"));
+
+ if (m_prefs.getBoolean("enable_image_downsampling", false)) {
+ if (m_prefs.getBoolean("always_downsample_images", false) || !org.fox.ttrss.Application.getInstance().isWifiConnected()) {
+ params.put("resize_width", String.valueOf(m_resizeWidth));
+ }
+ }
+
+ if (m_feed.is_cat)
+ params.put("is_cat", "true");
+
+ if (allowForceUpdate) {
+ params.put("force_update", "true");
+ }
+
+ if (m_searchQuery != null && !m_searchQuery.isEmpty()) {
+ params.put("search", m_searchQuery);
+ params.put("search_mode", "");
+ params.put("match_on", "both");
+ }
+
+ if (m_firstId > 0)
+ params.put("check_first_id", String.valueOf(m_firstId));
+
+ if (org.fox.ttrss.Application.getInstance().getApiLevel() >= 12) {
+ params.put("include_header", "true");
+ }
+
+ Log.d(TAG, "firstId=" + m_firstId + " append=" + m_append + " skip=" + skip + " localSize=" + articlesWork.size());
+
+ m_executor.execute(() -> {
+ JsonElement result = ApiCommon.performRequest(getApplication(), params, this);
+
+ Log.d(TAG, "got result=" + result);
+
+ if (result != null) {
+ try {
+ JsonArray content = result.getAsJsonArray();
+ if (content != null) {
+ final List<Article> articlesJson;
+ final JsonObject header;
+
+ if (org.fox.ttrss.Application.getInstance().getApiLevel() >= 12) {
+ header = content.get(0).getAsJsonObject();
+
+ m_firstIdChanged = header.get("first_id_changed") != null;
+
+ try {
+ m_firstId = header.get("first_id").getAsInt();
+ } catch (NumberFormatException e) {
+ m_firstId = 0;
+ }
+
+ Log.d(TAG, this + " firstID=" + m_firstId + " firstIdChanged=" + m_firstIdChanged);
+
+ Type listType = new TypeToken<List<Article>>() {}.getType();
+ articlesJson = new Gson().fromJson(content.get(1), listType);
+ } else {
+ Type listType = new TypeToken<List<Article>>() {}.getType();
+ articlesJson = new Gson().fromJson(content, listType);
+ }
+
+ if (!m_append)
+ articlesWork.clear();
+
+ m_amountLoaded = articlesJson.size();
+
+ for (Article article : articlesJson)
+ if (!articlesWork.containsId(article.id)) {
+ article.collectMediaInfo();
+ article.cleanupExcerpt();
+ article.fixNullFields();
+ articlesWork.add(article);
+ }
+
+ if (m_firstIdChanged) {
+ Log.d(TAG, "first id changed, disabling lazy load");
+ m_lazyLoadEnabled = false;
+ }
+
+ if (m_amountLoaded < Integer.parseInt(m_prefs.getString("headlines_request_size", "15"))) {
+ Log.d(TAG, this + " amount loaded "+m_amountLoaded+" < request size, disabling lazy load");
+ m_lazyLoadEnabled = false;
+ }
+
+ m_offset += m_amountLoaded;
+ m_loadingInProgress = false;
+
+ Log.d(TAG, this + " loaded headlines=" + m_amountLoaded + " resultingLocalSize=" + articlesWork.size());
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ m_mainHandler.post(() -> {
+ m_articles.setValue(articlesWork);
+ m_lastUpdate.setValue(System.currentTimeMillis());
+ });
+ });
+
+ m_loadingInProgress = false;
+
+ }
+
+ private int getSkip(boolean append, ArticleList articles) {
+ int skip = 0;
+
+ if (append) {
+ // adaptive, all_articles, marked, published, unread
+ String viewMode = m_prefs.getString("view_mode", "adaptive");
+
+ int numUnread = Math.toIntExact(articles.getUnreadCount());
+ int numAll = Math.toIntExact(articles.size());
+
+ if ("marked".equals(viewMode)) {
+ skip = numAll;
+ } else if ("published".equals(viewMode)) {
+ skip = numAll;
+ } else if ("unread".equals(viewMode)) {
+ skip = numUnread;
+ } else if (m_searchQuery != null && !m_searchQuery.isEmpty()) {
+ skip = numAll;
+ } else if ("adaptive".equals(viewMode)) {
+ skip = numUnread > 0 ? numUnread : numAll;
+ } else {
+ skip = numAll;
+ }
+ }
+
+ return skip;
+ }
+
+ @Override
+ public void setStatusCode(int statusCode) {
+ m_apiStatusCode = statusCode;
+ }
+
+ @Override
+ public void setLastError(ApiCommon.ApiError lastError) {
+ m_lastError = lastError;
+ }
+
+ @Override
+ public void setLastErrorMessage(String message) {
+ m_lastErrorMessage = message;
+ }
+
+ public boolean getFirstIdChanged() {
+ return m_firstIdChanged;
+ }
+
+ public boolean getAppend() {
+ return m_append;
+ }
+
+ public void setSearchQuery(String searchQuery) {
+ m_searchQuery = searchQuery;
+ }
+
+ public String getSearchQuery() {
+ return m_searchQuery;
+ }
+
+ public int getOffset() {
+ return m_offset;
+ }
+
+ public boolean lazyLoadEnabled() {
+ return m_lazyLoadEnabled;
+ }
+
+ public int getErrorMessage() {
+ return ApiCommon.getErrorMessage(m_lastError);
+ }
+
+ ApiCommon.ApiError getLastError() {
+ return m_lastError;
+ }
+
+ String getLastErrorMessage() {
+ return m_lastErrorMessage;
+ }
+
+ public boolean isLoading() {
+ return m_loadingInProgress;
+ }
+}
diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/ArticlePager.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/ArticlePager.java
index da9dc8f3..cf43f4f0 100755
--- a/org.fox.ttrss/src/main/java/org/fox/ttrss/ArticlePager.java
+++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/ArticlePager.java
@@ -2,10 +2,7 @@ package org.fox.ttrss;
import android.annotation.SuppressLint;
import android.app.Activity;
-import android.content.SharedPreferences;
-import android.os.BadParcelableException;
import android.os.Bundle;
-import android.os.Handler;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -13,65 +10,44 @@ import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentActivity;
-import androidx.preference.PreferenceManager;
-import androidx.viewpager2.adapter.FragmentStateAdapter;
import androidx.viewpager2.widget.ViewPager2;
-import com.google.android.material.snackbar.Snackbar;
-import com.google.gson.JsonElement;
-
import org.fox.ttrss.types.Article;
+import org.fox.ttrss.types.ArticleList;
import org.fox.ttrss.types.Feed;
-import org.fox.ttrss.util.HeadlinesRequest;
-
-import java.util.HashMap;
+import org.fox.ttrss.util.DiffFragmentStateAdapter;
+import org.fox.ttrss.util.ArticleDiffItemCallback;
public class ArticlePager extends androidx.fragment.app.Fragment {
- private final String TAG = "ArticlePager";
+ private final String TAG = this.getClass().getSimpleName();
private PagerAdapter m_adapter;
private HeadlinesEventListener m_listener;
private int m_articleId;
private OnlineActivity m_activity;
- private String m_searchQuery = "";
private Feed m_feed;
- private SharedPreferences m_prefs;
- private int m_firstId = 0;
- private boolean m_refreshInProgress;
- private boolean m_lazyLoadDisabled;
private ViewPager2 m_pager;
- private static class PagerAdapter extends FragmentStateAdapter {
-
- public PagerAdapter(FragmentActivity fa) {
- super(fa);
+ private static class PagerAdapter extends DiffFragmentStateAdapter<Article> {
+
+ public PagerAdapter(@NonNull Fragment fragment) {
+ super(fragment, new ArticleDiffItemCallback());
+ }
+
+ private void syncToSharedArticles() {
+ submitList(new ArticleList(Application.getArticles()));
}
@Override
@NonNull
public Fragment createFragment(int position) {
- try {
- Article article = Application.getArticles().get(position);
-
- if (article != null) {
- ArticleFragment af = new ArticleFragment();
- af.initialize(article);
-
- return af;
- }
- } catch (IndexOutOfBoundsException e) {
- e.printStackTrace();
- }
+ Article article = getItem(position);
- return null;
- }
+ ArticleFragment af = new ArticleFragment();
+ af.initialize(article);
- @Override
- public int getItemCount() {
- return Application.getArticles().size();
+ return af;
}
-
}
public void initialize(int articleId, Feed feed) {
@@ -79,17 +55,12 @@ public class ArticlePager extends androidx.fragment.app.Fragment {
m_feed = feed;
}
- public void setSearchQuery(String searchQuery) {
- m_searchQuery = searchQuery;
- }
-
@Override
public void onSaveInstanceState(Bundle out) {
super.onSaveInstanceState(out);
out.putInt("m_articleId", m_articleId);
out.putParcelable("m_feed", m_feed);
- out.putInt("m_firstId", m_firstId);
}
@Override
@@ -99,7 +70,6 @@ public class ArticlePager extends androidx.fragment.app.Fragment {
if (savedInstanceState != null) {
m_articleId = savedInstanceState.getInt("m_articleId");
m_feed = savedInstanceState.getParcelable("m_feed");
- m_firstId = savedInstanceState.getInt("m_firstId");
}
setRetainInstance(true);
@@ -109,36 +79,42 @@ public class ArticlePager extends androidx.fragment.app.Fragment {
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_article_pager, container, false);
- m_adapter = new PagerAdapter(getActivity());
+ m_adapter = new PagerAdapter(this);
+ m_adapter.submitList(Application.getArticles());
+
+ ArticleModel model = Application.getArticlesModel();
+
+ // deal with further updates
+ model.getArticles().observe(getActivity(), articles -> {
+ Log.d(TAG, "observed article list size=" + articles.size());
+ m_adapter.submitList(articles);
+ });
m_pager = view.findViewById(R.id.article_pager);
- int position = Application.getArticles().getPositionById(m_articleId);
-
m_listener.onArticleSelected(Application.getArticles().getById(m_articleId), false);
m_pager.setAdapter(m_adapter);
m_pager.setOffscreenPageLimit(3);
- m_pager.setCurrentItem(position, false);
+ int position = Application.getArticles().getPositionById(m_articleId);
+
+ if (position != -1)
+ m_pager.setCurrentItem(position, false);
+
m_pager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
@Override
public void onPageSelected(int position) {
Log.d(TAG, "onPageSelected: " + position);
- final Article article = Application.getArticles().get(position);
-
- if (article != null) {
- m_articleId = article.id;
-
- new Handler().postDelayed(() -> m_listener.onArticleSelected(article, false), 250);
+ // wtf
+ if (position != -1) {
+ Article article = Application.getArticles().get(position);
- //Log.d(TAG, "Page #" + position + "/" + m_adapter.getCount());
+ if (article != null) {
+ m_articleId = article.id;
- if (!m_refreshInProgress && !m_lazyLoadDisabled && (m_activity.isSmallScreen() || m_activity.isPortrait()) && position >= m_adapter.getItemCount() - 5) {
- Log.d(TAG, "loading more articles...");
-
- new Handler().postDelayed(() -> refresh(true), 100);
+ m_listener.onArticleSelected(article, false);
}
}
}
@@ -146,170 +122,19 @@ public class ArticlePager extends androidx.fragment.app.Fragment {
return view;
}
-
- protected void refresh(final boolean append) {
-
- if (!append) {
- m_lazyLoadDisabled = false;
- }
-
- m_refreshInProgress = true;
-
- @SuppressLint("StaticFieldLeak") HeadlinesRequest req = new HeadlinesRequest(getActivity().getApplicationContext(), m_activity, Application.getArticles()) {
- @Override
- protected void onPostExecute(JsonElement result) {
- if (isDetached() || !isAdded()) return;
-
- if (!append) {
- m_pager.setCurrentItem(0, false);
- Application.getArticles().clear();
- }
-
- super.onPostExecute(result);
-
- m_refreshInProgress = false;
-
- if (result != null) {
-
- if (m_firstIdChanged) {
- m_lazyLoadDisabled = true;
- }
-
- if (m_firstIdChanged && !(m_activity instanceof DetailActivity && !m_activity.isPortrait())) {
- //m_activity.toast(R.string.headlines_row_top_changed);
-
- Snackbar.make(getView(), R.string.headlines_row_top_changed, Snackbar.LENGTH_LONG)
- .setAction(R.string.reload, v -> refresh(false)).show();
- }
-
- if (m_amountLoaded < Integer.parseInt(m_prefs.getString("headlines_request_size", "15"))) {
- m_lazyLoadDisabled = true;
- }
-
- ArticlePager.this.m_firstId = m_firstId;
-
- try {
- m_adapter.notifyDataSetChanged();
- } catch (BadParcelableException e) {
- if (getActivity() != null) {
- getActivity().finish();
- return;
- }
- }
-
- if (!Application.getArticles().isEmpty()) {
- if (Application.getArticles().getById(m_articleId) == null) {
- Article article = Application.getArticles().get(0);
-
- m_articleId = article.id;
- m_listener.onArticleSelected(article, false);
- }
- }
-
- } else {
- m_lazyLoadDisabled = true;
-
- if (m_lastError == ApiCommon.ApiError.LOGIN_FAILED) {
- m_activity.login(true);
- } else {
- m_activity.toast(getErrorMessage());
- }
- }
- }
- };
-
- final Feed feed = m_feed;
-
- final String sessionId = m_activity.getSessionId();
- int skip = 0;
-
- if (append) {
- // adaptive, all_articles, marked, published, unread
- String viewMode = m_activity.getViewMode();
- int numUnread = 0;
- int numAll = Application.getArticles().size();
-
- for (Article a : Application.getArticles()) {
- if (a.unread) ++numUnread;
- }
-
- if ("marked".equals(viewMode)) {
- skip = numAll;
- } else if ("published".equals(viewMode)) {
- skip = numAll;
- } else if ("unread".equals(viewMode)) {
- skip = numUnread;
- } else if (m_searchQuery != null && !m_searchQuery.isEmpty()) {
- skip = numAll;
- } else if ("adaptive".equals(viewMode)) {
- skip = numUnread > 0 ? numUnread : numAll;
- } else {
- skip = numAll;
- }
- }
-
- final int fskip = skip;
-
- req.setOffset(skip);
-
- HashMap<String,String> map = new HashMap<>();
- map.put("op", "getHeadlines");
- map.put("sid", sessionId);
- map.put("feed_id", String.valueOf(feed.id));
- map.put("show_excerpt", "true");
- map.put("excerpt_length", String.valueOf(CommonActivity.EXCERPT_MAX_LENGTH));
- map.put("show_content", "true");
- map.put("include_attachments", "true");
- map.put("limit", m_prefs.getString("headlines_request_size", "15"));
- map.put("offset", String.valueOf(0));
- map.put("view_mode", m_activity.getViewMode());
- map.put("skip", String.valueOf(fskip));
- map.put("include_nested", "true");
- map.put("has_sandbox", "true");
- map.put("order_by", m_activity.getSortMode());
-
- if (feed.is_cat) map.put("is_cat", "true");
-
- if (m_searchQuery != null && !m_searchQuery.isEmpty()) {
- map.put("search", m_searchQuery);
- map.put("search_mode", "");
- map.put("match_on", "both");
- }
-
- if (m_firstId > 0) map.put("check_first_id", String.valueOf(m_firstId));
-
- if (m_activity.getApiLevel() >= 12) {
- map.put("include_header", "true");
- }
-
- if (m_prefs.getBoolean("enable_image_downsampling", false)) {
- if (m_prefs.getBoolean("always_downsample_images", false) || !m_activity.isWifiConnected()) {
- map.put("resize_width", String.valueOf(m_activity.getResizeWidth()));
- }
- }
-
- Log.d(TAG, "[AP] request more headlines, firstId=" + m_firstId);
- req.execute(map);
- }
-
@Override
public void onAttach(@NonNull Activity activity) {
super.onAttach(activity);
m_listener = (HeadlinesEventListener)activity;
m_activity = (OnlineActivity)activity;
-
- m_prefs = PreferenceManager.getDefaultSharedPreferences(getActivity().getApplicationContext());
}
@SuppressLint("NewApi")
@Override
public void onResume() {
super.onResume();
-
- //if (m_adapter != null) m_adapter.notifyDataSetChanged();
-
m_activity.invalidateOptionsMenu();
}
@@ -317,7 +142,8 @@ public class ArticlePager extends androidx.fragment.app.Fragment {
if (m_pager != null && articleId != m_articleId) {
int position = Application.getArticles().getPositionById(articleId);
- m_pager.setCurrentItem(position, false);
+ if (position != -1)
+ m_pager.setCurrentItem(position, false);
}
}
@@ -345,7 +171,13 @@ public class ArticlePager extends androidx.fragment.app.Fragment {
return m_articleId;
}
- public void notifyUpdated() {
- m_adapter.notifyDataSetChanged();
+ public void notifyItemChanged(int position) {
+ if (m_adapter != null)
+ m_adapter.notifyItemChanged(position);
+ }
+
+ public void syncToSharedArticles() {
+ if (m_adapter != null)
+ m_adapter.syncToSharedArticles();
}
}
diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/BaseFeedlistFragment.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/BaseFeedlistFragment.java
index 86a31465..def61209 100755
--- a/org.fox.ttrss/src/main/java/org/fox/ttrss/BaseFeedlistFragment.java
+++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/BaseFeedlistFragment.java
@@ -17,7 +17,7 @@ import java.net.URL;
public abstract class BaseFeedlistFragment extends androidx.fragment.app.Fragment {
abstract public void refresh();
- public void initDrawerHeader(LayoutInflater inflater, View view, ListView list, final CommonActivity activity, final SharedPreferences prefs, boolean isRoot) {
+ public void initDrawerHeader(LayoutInflater inflater, View view, ListView list, final CommonActivity activity, final SharedPreferences prefs) {
View layout = inflater.inflate(R.layout.drawer_header, list, false);
list.addHeaderView(layout, null, false);
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 cdc261b7..97e356c4 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
@@ -15,8 +15,6 @@ import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
@@ -66,7 +64,6 @@ public class CommonActivity extends AppCompatActivity implements SharedPreferenc
public final static String FRAG_ARTICLE = "article";
public final static String FRAG_FEEDS = "feeds";
public final static String FRAG_CATS = "cats";
- public final static String FRAG_DIALOG = "dialog";
public final static String THEME_DEFAULT = "THEME_FOLLOW_DEVICE";
@@ -74,7 +71,6 @@ public class CommonActivity extends AppCompatActivity implements SharedPreferenc
public final static String NOTIFICATION_CHANNEL_PRIORITY = "channel_priority";
public static final int EXCERPT_MAX_LENGTH = 256;
- public static final int EXCERPT_MAX_QUERY_LENGTH = 2048;
public static final int LABEL_BASE_INDEX = -1024;
public static final int PENDING_INTENT_CHROME_SHARE = 1;
@@ -330,22 +326,6 @@ public class CommonActivity extends AppCompatActivity implements SharedPreferenc
});
}
- protected void preloadUriIfAllowed(Uri uri) {
- boolean enableCustomTabs = m_prefs.getBoolean("enable_custom_tabs", true);
-
- if (m_customTabClient != null && enableCustomTabs) {
- ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
- NetworkInfo info = cm.getActiveNetworkInfo();
-
- if (info != null && info.isConnected() && info.getType() == ConnectivityManager.TYPE_WIFI) {
- CustomTabsSession session = getCustomTabSession();
- session.mayLaunchUrl(uri, null, null);
-
- //toast("Preloading: " + uri.toString());
- }
- }
- }
-
protected Intent getShareIntent(String text, String subject) {
Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.setType("text/plain");
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 6558c2d1..3995014b 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
@@ -6,7 +6,6 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
-import android.os.Handler;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
@@ -94,7 +93,7 @@ public class DetailActivity extends OnlineActivity implements HeadlinesEventList
saveArticleUnread(article);
if (hf != null) {
- hf.notifyUpdated();
+ hf.notifyItemChanged(Application.getArticles().indexOf(article));
}
}
}
@@ -149,17 +148,16 @@ public class DetailActivity extends OnlineActivity implements HeadlinesEventList
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
- final HeadlinesFragment hf = new HeadlinesFragment();
+ HeadlinesFragment hf = new HeadlinesFragment();
hf.initialize(feed, openedArticleId, true);
hf.setSearchQuery(searchQuery);
ft.replace(R.id.headlines_fragment, hf, FRAG_HEADLINES);
- ArticlePager af = new ArticlePager();
- af.initialize(openedArticleId, feed);
- af.setSearchQuery(searchQuery);
+ ArticlePager ap = new ArticlePager();
+ ap.initialize(openedArticleId, feed);
- ft.replace(R.id.article_fragment, af, FRAG_ARTICLE);
+ ft.replace(R.id.article_fragment, ap, FRAG_ARTICLE);
ft.commit();
@@ -261,7 +259,7 @@ public class DetailActivity extends OnlineActivity implements HeadlinesEventList
}
@Override
- public void onArticleListSelectionChange(ArticleList m_selectedArticles) {
+ public void onArticleListSelectionChange() {
invalidateOptionsMenu();
}
@@ -271,35 +269,23 @@ public class DetailActivity extends OnlineActivity implements HeadlinesEventList
}
@Override
- public void onArticleSelected(final Article article, boolean open) {
-
- if (article == null) return;
-
+ public void onArticleSelected(Article article, boolean open) {
+
if (article.unread) {
article.unread = false;
saveArticleUnread(article);
}
- try {
- preloadUriIfAllowed(Uri.parse(article.link));
- } catch (Exception e) {
- e.printStackTrace();
- }
-
if (!getSupportActionBar().isShowing()) getSupportActionBar().show();
- if (open) {
-
- new Handler().postDelayed(() -> {
- ArticlePager ap = (ArticlePager) DetailActivity.this.getSupportFragmentManager().findFragmentByTag(FRAG_ARTICLE);
-
- if (ap != null) {
- ap.setActiveArticleId(article.id);
- }
- }, 250);
+ ArticlePager ap = (ArticlePager) DetailActivity.this.getSupportFragmentManager().findFragmentByTag(FRAG_ARTICLE);
+ HeadlinesFragment hf = (HeadlinesFragment) getSupportFragmentManager().findFragmentByTag(FRAG_HEADLINES);
+ if (open) {
+ if (ap != null) {
+ ap.setActiveArticleId(article.id);
+ }
} else {
- HeadlinesFragment hf = (HeadlinesFragment) getSupportFragmentManager().findFragmentByTag(FRAG_HEADLINES);
if (hf != null) {
hf.setActiveArticleId(article.id);
}
@@ -313,11 +299,11 @@ public class DetailActivity extends OnlineActivity implements HeadlinesEventList
HeadlinesFragment hf = (HeadlinesFragment) getSupportFragmentManager().findFragmentByTag(FRAG_HEADLINES);
ArticlePager ap = (ArticlePager) getSupportFragmentManager().findFragmentByTag(FRAG_ARTICLE);
- if (ap != null) {
- ap.notifyUpdated();
+ if (ap != null) {
+ ap.syncToSharedArticles();
}
- if (hf != null) {
+ /* if (hf != null) {
Article article = Application.getArticles().getById(hf.getActiveArticleId());
if (article == null && !Application.getArticles().isEmpty()) {
@@ -335,7 +321,7 @@ public class DetailActivity extends OnlineActivity implements HeadlinesEventList
ft.replace(R.id.article_fragment, af, FRAG_ARTICLE);
ft.commitAllowingStateLoss();
}
- }
+ } */
}
@Override
diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/FeedCategoriesFragment.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/FeedCategoriesFragment.java
index 08a656e4..1d735490 100755
--- a/org.fox.ttrss/src/main/java/org/fox/ttrss/FeedCategoriesFragment.java
+++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/FeedCategoriesFragment.java
@@ -62,9 +62,9 @@ public class FeedCategoriesFragment extends BaseFeedlistFragment implements OnIt
params.put("op", "getCategories");
params.put("sid", sessionId);
params.put("enable_nested", "true");
- if (unreadOnly) {
- params.put("unread_only", String.valueOf(unreadOnly));
- }
+
+ if (unreadOnly)
+ params.put("unread_only", "true");
return new ApiLoader(getContext(), params);
}
@@ -288,7 +288,7 @@ public class FeedCategoriesFragment extends BaseFeedlistFragment implements OnIt
m_list = view.findViewById(R.id.feeds);
m_adapter = new FeedCategoryListAdapter(getActivity(), R.layout.feeds_row, m_cats);
- initDrawerHeader(inflater, view, m_list, m_activity, m_prefs, true);
+ initDrawerHeader(inflater, view, m_list, m_activity, m_prefs);
m_list.setAdapter(m_adapter);
m_list.setOnItemClickListener(this);
@@ -312,7 +312,7 @@ public class FeedCategoriesFragment extends BaseFeedlistFragment implements OnIt
public void onResume() {
super.onResume();
- LoaderManager.getInstance(this).initLoader(0, null, this).forceLoad();
+ LoaderManager.getInstance(this).initLoader(Application.LOADER_CATS, null, this).forceLoad();
m_activity.invalidateOptionsMenu();
}
@@ -324,7 +324,7 @@ public class FeedCategoriesFragment extends BaseFeedlistFragment implements OnIt
if (m_swipeLayout != null)
m_swipeLayout.setRefreshing(true);
- LoaderManager.getInstance(this).restartLoader(0, null, this).forceLoad();
+ LoaderManager.getInstance(this).restartLoader(Application.LOADER_CATS, null, this).forceLoad();
}
private class FeedCategoryListAdapter extends ArrayAdapter<FeedCategory> {
diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/FeedsFragment.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/FeedsFragment.java
index 495180bb..f618b5b1 100755
--- a/org.fox.ttrss/src/main/java/org/fox/ttrss/FeedsFragment.java
+++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/FeedsFragment.java
@@ -327,7 +327,7 @@ public class FeedsFragment extends BaseFeedlistFragment implements OnItemClickLi
m_list = view.findViewById(R.id.feeds);
- initDrawerHeader(inflater, view, m_list, m_activity, m_prefs, !m_enableParentBtn);
+ initDrawerHeader(inflater, view, m_list, m_activity, m_prefs);
if (m_enableParentBtn) {
View layout = inflater.inflate(R.layout.feeds_goback, m_list, false);
@@ -366,7 +366,7 @@ public class FeedsFragment extends BaseFeedlistFragment implements OnItemClickLi
public void onResume() {
super.onResume();
- LoaderManager.getInstance(this).initLoader(0, null, this).forceLoad();
+ LoaderManager.getInstance(this).initLoader(Application.LOADER_FEEDS, null, this).forceLoad();
m_activity.invalidateOptionsMenu();
}
@@ -404,7 +404,7 @@ public class FeedsFragment extends BaseFeedlistFragment implements OnItemClickLi
m_swipeLayout.setRefreshing(true);
}
- LoaderManager.getInstance(this).restartLoader(0, null, this).forceLoad();
+ LoaderManager.getInstance(this).restartLoader(Application.LOADER_FEEDS, null, this).forceLoad();
}
private class FeedListAdapter extends ArrayAdapter<Feed> {
diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/HeadlinesEventListener.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/HeadlinesEventListener.java
index 5494bb2b..27e00bbf 100644
--- a/org.fox.ttrss/src/main/java/org/fox/ttrss/HeadlinesEventListener.java
+++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/HeadlinesEventListener.java
@@ -4,7 +4,7 @@ import org.fox.ttrss.types.Article;
import org.fox.ttrss.types.ArticleList;
public interface HeadlinesEventListener {
- void onArticleListSelectionChange(ArticleList m_selectedArticles);
+ void onArticleListSelectionChange();
void onArticleSelected(Article article);
void onArticleSelected(Article article, boolean open);
void onHeadlinesLoaded(boolean appended);
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 ad023a7e..d39ee038 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
@@ -1,6 +1,5 @@
package org.fox.ttrss;
-import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
@@ -51,6 +50,7 @@ import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.DefaultItemAnimator;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.ListAdapter;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
@@ -65,14 +65,13 @@ import com.bumptech.glide.request.target.Target;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.snackbar.Snackbar;
-import com.google.gson.JsonElement;
import org.fox.ttrss.glide.ProgressTarget;
import org.fox.ttrss.types.Article;
import org.fox.ttrss.types.ArticleList;
import org.fox.ttrss.types.Attachment;
import org.fox.ttrss.types.Feed;
-import org.fox.ttrss.util.HeadlinesRequest;
+import org.fox.ttrss.util.ArticleDiffItemCallback;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
@@ -81,11 +80,19 @@ import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.TimeZone;
+import java.util.stream.Collectors;
import jp.wasabeef.glide.transformations.CropCircleTransformation;
public class HeadlinesFragment extends androidx.fragment.app.Fragment {
+ private boolean m_isLazyLoading;
+
+ public void notifyItemChanged(int position) {
+ if (m_adapter != null)
+ m_adapter.notifyItemChanged(position);
+ }
+
public enum ArticlesSelection { ALL, NONE, UNREAD }
public static final int FLAVOR_IMG_MIN_SIZE = 128;
@@ -96,9 +103,6 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment {
private Feed m_feed;
private int m_activeArticleId;
private String m_searchQuery = "";
- private boolean m_refreshInProgress = false;
- private int m_firstId = 0;
- private boolean m_lazyLoadDisabled = false;
private SharedPreferences m_prefs;
@@ -114,14 +118,12 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment {
private MediaPlayer m_mediaPlayer;
private TextureView m_activeTexture;
- public ArticleList getSelectedArticles() {
- ArticleList tmp = new ArticleList();
+ protected static HashMap<Integer, Integer> m_flavorHeightsCache = new HashMap<>();
- for (Article a : Application.getArticles()) {
- if (a.selected) tmp.add(a);
- }
-
- return tmp;
+ public ArticleList getSelectedArticles() {
+ return Application.getArticles()
+ .stream()
+ .filter(a -> a.selected).collect(Collectors.toCollection(ArticleList::new));
}
public void initialize(Feed feed) {
@@ -186,19 +188,24 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment {
private void catchupAbove(Article article) {
ArticleList tmp = new ArticleList();
- for (Article a : Application.getArticles()) {
+ ArticleList articles = Application.getArticles();
+ for (Article a : articles) {
if (article.equalsById(a))
break;
if (a.unread) {
a.unread = false;
tmp.add(a);
+
+ int position = articles.getPositionById(a.id);
+
+ if (position != -1)
+ m_adapter.notifyItemChanged(position);
}
}
if (!tmp.isEmpty()) {
m_activity.setArticlesUnread(tmp, Article.UPDATE_SET_FALSE);
- m_adapter.notifyDataSetChanged();
}
}
@@ -250,8 +257,6 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment {
m_feed = savedInstanceState.getParcelable("m_feed");
m_activeArticleId = savedInstanceState.getInt("m_activeArticleId");
m_searchQuery = savedInstanceState.getString("m_searchQuery");
- m_firstId = savedInstanceState.getInt("m_firstId");
- m_lazyLoadDisabled = savedInstanceState.getBoolean("m_lazyLoadDisabled");
m_compactLayoutMode = savedInstanceState.getBoolean("m_compactLayoutMode");
}
@@ -267,13 +272,12 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment {
out.putParcelable("m_feed", m_feed);
out.putInt("m_activeArticleId", m_activeArticleId);
out.putString("m_searchQuery", m_searchQuery);
- out.putInt("m_firstId", m_firstId);
- out.putBoolean("m_lazyLoadDisabled", m_lazyLoadDisabled);
out.putBoolean("m_compactLayoutMode", m_compactLayoutMode);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ Log.d(TAG, "onCreateView");
String headlineMode = m_prefs.getString("headline_mode", "HL_DEFAULT");
@@ -287,7 +291,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment {
m_swipeLayout = view.findViewById(R.id.headlines_swipe_container);
- m_swipeLayout.setOnRefreshListener(() -> refresh(false, true));
+ m_swipeLayout.setOnRefreshListener(() -> refresh(false));
m_list = view.findViewById(R.id.headlines_list);
registerForContextMenu(m_list);
@@ -296,11 +300,14 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment {
m_list.setLayoutManager(m_layoutManager);
m_list.setItemAnimator(new DefaultItemAnimator());
- m_adapter = new ArticleListAdapter(Application.getArticles());
-
+ m_adapter = new ArticleListAdapter();
m_list.setAdapter(m_adapter);
- if (m_prefs.getBoolean("headlines_swipe_to_dismiss", true) && !m_prefs.getBoolean("headlines_mark_read_scroll", false) ) {
+ if (savedInstanceState == null && Application.getArticles().isEmpty()) {
+ refresh(false);
+ }
+
+ if (m_prefs.getBoolean("headlines_swipe_to_dismiss", true) /*&& !m_prefs.getBoolean("headlines_mark_read_scroll", false) */) {
ItemTouchHelper swipeHelper = new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.RIGHT) {
@@ -345,8 +352,10 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment {
wasUnread = false;
}
- Application.getArticles().remove(adapterPosition);
- m_adapter.notifyItemRemoved(adapterPosition);
+ ArticleList tmpRemove = new ArticleList(Application.getArticles());
+ tmpRemove.remove(adapterPosition);
+
+ Application.getArticlesModel().update(tmpRemove);
Snackbar.make(m_list, R.string.headline_undo_row_prompt, Snackbar.LENGTH_LONG)
.setAction(getString(R.string.headline_undo_row_button), v -> {
@@ -356,9 +365,10 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment {
m_activity.saveArticleUnread(article);
}
- Application.getArticles().add(adapterPosition, article);
- m_adapter.notifyItemInserted(adapterPosition);
- m_adapter.notifyItemRangeChanged(adapterPosition, 1);
+ ArticleList tmpInsert = new ArticleList(Application.getArticles());
+ tmpInsert.add(adapterPosition, article);
+
+ Application.getArticlesModel().update(tmpInsert);
}).show();
}
@@ -378,17 +388,22 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment {
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
- if (newState == RecyclerView.SCROLL_STATE_IDLE && m_prefs.getBoolean("headlines_mark_read_scroll", false)) {
- if (!m_readArticles.isEmpty()) {
- m_activity.toggleArticlesUnread(m_readArticles);
+ ArticleModel model = Application.getArticlesModel();
- for (Article a : m_readArticles)
+ if (newState == RecyclerView.SCROLL_STATE_IDLE) {
+ if (!m_readArticles.isEmpty() && !m_isLazyLoading && !model.isLoading() && m_prefs.getBoolean("headlines_mark_read_scroll", false)) {
+ Log.d(TAG, "marking articles as read, count=" + m_readArticles.size());
+
+ m_activity.setArticlesUnread(m_readArticles, Article.UPDATE_SET_FALSE);
+
+ for (Article a : m_readArticles) {
a.unread = false;
- if (m_feed != null)
- m_feed.unread -= m_readArticles.size();
+ int position = Application.getArticles().getPositionById(a.id);
- m_adapter.notifyDataSetChanged();
+ if (position != -1)
+ m_adapter.notifyItemChanged(position);
+ }
m_readArticles.clear();
@@ -407,27 +422,33 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment {
// Log.d(TAG, "onScrolled: FVI=" + firstVisibleItem + " LVI=" + lastVisibleItem);
if (m_prefs.getBoolean("headlines_mark_read_scroll", false)) {
-
for (int i = 0; i < firstVisibleItem; i++) {
try {
Article article = Application.getArticles().get(i);
- if (article.unread && !m_readArticles.contains(article)) {
- Log.d(TAG, "adding to mark read=" + article.title);
-
+ if (article.unread && !m_readArticles.contains(article))
m_readArticles.add(article);
- }
+
} catch (IndexOutOfBoundsException e) {
e.printStackTrace();
}
}
- }
- if (!m_refreshInProgress && !m_lazyLoadDisabled && lastVisibleItem >= Application.getArticles().size() - 5) {
- m_refreshInProgress = true;
- new Handler().postDelayed(() -> refresh(true), 100);
+ // Log.d(TAG, "pending to auto mark as read count=" + m_readArticles.size());
}
+ ArticleModel model = Application.getArticlesModel();
+
+ if (dy > 0 && !m_isLazyLoading && !model.isLoading() && model.lazyLoadEnabled() &&
+ lastVisibleItem >= Application.getArticles().size() - 5) {
+
+ Log.d(TAG, "attempting to lazy load more articles...");
+
+ m_isLazyLoading = true;
+
+ // this has to be dispatched delayed, consequent adapter updates are forbidden in scroll handler
+ new Handler().postDelayed(() -> refresh(true), 250);
+ }
}
});
@@ -435,212 +456,105 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment {
m_activity.setTitle(m_feed.title);
}
- Log.d(TAG, "onCreateView, feed=" + m_feed);
+ ArticleModel model = Application.getArticlesModel();
- return view;
- }
+ // this gets notified on network update
+ model.getUpdatesData().observe(getActivity(), lastUpdate -> {
+ ArticleList tmp = new ArticleList(model.getArticles().getValue());
- @Override
- public void onResume() {
- super.onResume();
-
- if (Application.getArticles().isEmpty()) {
- refresh(false);
- } else {
- Article activeArticle = Application.getArticles().getById(m_activeArticleId);
-
- if (activeArticle != null)
- scrollToArticle(activeArticle);
- }
+ Log.d(TAG, "observed last update=" + lastUpdate + " article count=" + tmp.size());
- m_activity.invalidateOptionsMenu();
- }
+ if (m_prefs.getBoolean("headlines_mark_read_scroll", false))
+ tmp.add(new Article(Article.TYPE_AMR_FOOTER));
- @Override
- public void onAttach(Activity activity) {
- super.onAttach(activity);
- m_prefs = PreferenceManager.getDefaultSharedPreferences(getActivity().getApplicationContext());
- m_activity = (OnlineActivity) activity;
- m_listener = (HeadlinesEventListener) activity;
- }
+ final boolean appended = model.getAppend();
- public void refresh(boolean append) {
- refresh(append, false);
- }
+ m_adapter.submitList(tmp, () -> {
+ if (!appended)
+ m_list.scrollToPosition(0);
- public void refresh(final boolean append, boolean userInitiated) {
- Application.getArticles().stripFooters();
- m_adapter.notifyDataSetChanged();
+ if (m_swipeLayout != null)
+ m_swipeLayout.setRefreshing(false);
- if (!append) m_lazyLoadDisabled = false;
+ m_isLazyLoading = false;
- if (m_activity != null && isAdded() && m_feed != null) {
- m_refreshInProgress = true;
+ m_listener.onHeadlinesLoaded(appended);
+ m_listener.onArticleListSelectionChange();
+ });
- if (m_swipeLayout != null) m_swipeLayout.setRefreshing(true);
+ if (model.getFirstIdChanged())
+ Snackbar.make(getView(), R.string.headlines_row_top_changed, Snackbar.LENGTH_LONG)
+ .setAction(R.string.reload, v -> refresh(false)).show();
- if (!append) {
- m_activity.getSupportActionBar().show();
- Application.getArticles().clear();
- m_adapter.notifyDataSetChanged();
- } else if (!(m_activity instanceof DetailActivity)) {
- // detail activity does not use footers because it would break 1-to-1 mapping with pager view
- // pager will need to work on a footerless subset of shared article view before this is possible
- Application.getArticles().add(new Article(Article.TYPE_LOADMORE));
- m_adapter.notifyDataSetChanged();
+ if (model.getLastError() == ApiCommon.ApiError.LOGIN_FAILED) {
+ m_activity.login();
+ } else if (model.getLastError() != null && model.getLastError() != ApiCommon.ApiError.SUCCESS) {
+ if (model.getLastErrorMessage() != null) {
+ m_activity.toast(m_activity.getString(model.getErrorMessage()) + "\n" + model.getLastErrorMessage());
+ } else {
+ m_activity.toast(model.getErrorMessage());
+ }
}
- final String sessionId = m_activity.getSessionId();
- final boolean isCat = m_feed.is_cat;
-
- @SuppressLint("StaticFieldLeak") HeadlinesRequest req = new HeadlinesRequest(getActivity().getApplicationContext(), m_activity, Application.getArticles()) {
- @Override
- protected void onPostExecute(JsonElement result) {
- if (isDetached() || !isAdded()) return;
-
- super.onPostExecute(result);
-
- if (m_swipeLayout != null) m_swipeLayout.setRefreshing(false);
-
- m_refreshInProgress = false;
-
- if (result != null) {
-
- // is this needed?
- if (!Application.getArticles().containsId(m_activeArticleId))
- m_activeArticleId = 0;
-
- if (m_firstIdChanged) {
- m_lazyLoadDisabled = true;
-
- Log.d(TAG, "first id changed, disabling lazy load");
+ });
- // article pager deals with this in tablet landscape view
- if (m_activity.isSmallScreen() || !m_activity.isPortrait()) {
- Snackbar.make(getView(), R.string.headlines_row_top_changed, Snackbar.LENGTH_LONG)
- .setAction(R.string.reload, v -> refresh(false)).show();
- }
- }
+ // loaded articles might get modified for all sorts of reasons
+ model.getArticles().observe(getActivity(), articles -> {
+ Log.d(TAG, "observed article list size=" + articles.size());
- if (m_amountLoaded < Integer.parseInt(m_prefs.getString("headlines_request_size", "15"))) {
- // Log.d(TAG, "amount loaded "+m_amountLoaded+" < request size, disabling lazy load");
- m_lazyLoadDisabled = true;
- }
+ ArticleList tmp = new ArticleList(articles);
- HeadlinesFragment.this.m_firstId = m_firstId;
+ if (m_prefs.getBoolean("headlines_mark_read_scroll", false))
+ tmp.add(new Article(Article.TYPE_AMR_FOOTER));
- m_adapter.notifyDataSetChanged();
- m_listener.onHeadlinesLoaded(append);
+ m_adapter.submitList(tmp);
+ });
- } else {
- m_lazyLoadDisabled = true;
+ return view;
+ }
- if (m_lastError == ApiCommon.ApiError.LOGIN_FAILED) {
- m_activity.login(true);
- } else {
- if (m_lastErrorMessage != null) {
- m_activity.toast(getString(getErrorMessage()) + "\n" + m_lastErrorMessage);
- } else {
- m_activity.toast(getErrorMessage());
- }
- }
- }
+ @Override
+ public void onResume() {
+ super.onResume();
- // detail activity does not use footers (see above)
- if (!(m_activity instanceof DetailActivity)) {
- Application.getArticles().add(new Article(Article.TYPE_AMR_FOOTER));
- m_adapter.notifyDataSetChanged();
- }
- }
- };
-
- final int skip = getSkip(append);
-
- final boolean allowForceUpdate = m_activity.getApiLevel() >= 9 &&
- !m_feed.is_cat && m_feed.id > 0 && !append && userInitiated &&
- skip == 0;
-
- Log.d(TAG, "allowForceUpdate=" + allowForceUpdate + " userInitiated=" + userInitiated + " skip=" + skip);
-
- req.setOffset(skip);
-
- HashMap<String,String> map = new HashMap<>();
- map.put("op", "getHeadlines");
- map.put("sid", sessionId);
- map.put("feed_id", String.valueOf(m_feed.id));
- map.put("show_excerpt", "true");
- map.put("excerpt_length", String.valueOf(CommonActivity.EXCERPT_MAX_LENGTH));
- map.put("show_content", "true");
- map.put("include_attachments", "true");
- map.put("view_mode", m_activity.getViewMode());
- map.put("limit", m_prefs.getString("headlines_request_size", "15"));
- map.put("offset", String.valueOf(0));
- map.put("skip", String.valueOf(skip));
- map.put("include_nested", "true");
- map.put("has_sandbox", "true");
- map.put("order_by", m_activity.getSortMode());
-
- if (m_prefs.getBoolean("enable_image_downsampling", false)) {
- if (m_prefs.getBoolean("always_downsample_images", false) || !m_activity.isWifiConnected()) {
- map.put("resize_width", String.valueOf(m_activity.getResizeWidth()));
- }
- }
+ Log.d(TAG, "onResume");
- if (isCat) map.put("is_cat", "true");
+ syncToSharedArticles();
- if (allowForceUpdate) {
- map.put("force_update", "true");
- }
+ // we only set this in detail activity
+ if (m_activeArticleId > 0) {
+ Article activeArticle = Application.getArticles().getById(m_activeArticleId);
- if (m_searchQuery != null && !m_searchQuery.isEmpty()) {
- map.put("search", m_searchQuery);
- map.put("search_mode", "");
- map.put("match_on", "both");
- }
+ if (activeArticle != null)
+ scrollToArticle(activeArticle);
+ }
- if (m_firstId > 0) map.put("check_first_id", String.valueOf(m_firstId));
+ m_activity.invalidateOptionsMenu();
+ }
- if (m_activity.getApiLevel() >= 12) {
- map.put("include_header", "true");
- }
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+ m_prefs = PreferenceManager.getDefaultSharedPreferences(getActivity().getApplicationContext());
+ m_activity = (OnlineActivity) activity;
+ m_listener = (HeadlinesEventListener) activity;
+ }
- Log.d(TAG, "[HP] request more headlines, firstId=" + m_firstId);
+ public void refresh(final boolean append) {
+ ArticleModel model = Application.getArticlesModel();
- req.execute(map);
- }
- }
+ if (!append)
+ m_activeArticleId = -1;
- private int getSkip(boolean append) {
- int skip = 0;
-
- if (append) {
- // adaptive, all_articles, marked, published, unread
- String viewMode = m_activity.getViewMode();
-
- int numUnread = Math.toIntExact(Application.getArticles().getUnreadCount());
- int numAll = Math.toIntExact(Application.getArticles().getSizeWithoutFooters());
-
- if ("marked".equals(viewMode)) {
- skip = numAll;
- } else if ("published".equals(viewMode)) {
- skip = numAll;
- } else if ("unread".equals(viewMode)) {
- skip = numUnread;
- } else if (m_searchQuery != null && !m_searchQuery.isEmpty()) {
- skip = numAll;
- } else if ("adaptive".equals(viewMode)) {
- skip = numUnread > 0 ? numUnread : numAll;
- } else {
- skip = numAll;
- }
- }
+ if (m_swipeLayout != null)
+ m_swipeLayout.setRefreshing(true);
- return skip;
+ model.setSearchQuery(getSearchQuery());
+ model.startLoading(append, m_feed, m_activity.getResizeWidth());
}
static class ArticleViewHolder extends RecyclerView.ViewHolder {
public View view;
- public Article article;
public TextView titleView;
public TextView feedTitleView;
@@ -664,6 +578,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment {
public TextureView flavorVideoView;
public MaterialButton attachmentsView;
public ProgressTarget<String, GlideDrawable> flavorProgressTarget;
+ int articleId;
public ArticleViewHolder(View v) {
super(v);
@@ -674,7 +589,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment {
View flavorImage = view.findViewById(R.id.flavor_image);
if (flavorImage != null) {
- article.flavorViewHeight = flavorImage.getMeasuredHeight();
+ HeadlinesFragment.m_flavorHeightsCache.put(articleId, flavorImage.getMeasuredHeight());
}
return true;
@@ -705,6 +620,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment {
if (flavorImageView != null && flavorImageLoadingBar != null) {
flavorProgressTarget = new FlavorProgressTarget<>(new GlideDrawableImageViewTarget(flavorImageView), flavorImageLoadingBar);
}
+
}
public void clearAnimation() {
@@ -739,15 +655,12 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment {
}
}
- private class ArticleListAdapter extends RecyclerView.Adapter<ArticleViewHolder> {
- private final ArticleList items;
-
+ 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_LOADMORE = 4;
- public static final int VIEW_AMR_FOOTER = 5;
+ public static final int VIEW_AMR_FOOTER = 4;
public static final int VIEW_COUNT = VIEW_AMR_FOOTER + 1;
@@ -779,9 +692,8 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment {
return false;
}
- public ArticleListAdapter(ArticleList items) {
- super();
- this.items = items;
+ public ArticleListAdapter() {
+ super(new ArticleDiffItemCallback());
Display display = m_activity.getWindowManager().getDefaultDisplay();
Point size = new Point();
@@ -803,9 +715,6 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment {
case VIEW_AMR_FOOTER:
layoutId = R.layout.headlines_footer;
break;
- case VIEW_LOADMORE:
- layoutId = R.layout.headlines_row_loadmore;
- break;
case VIEW_UNREAD:
layoutId = m_compactLayoutMode ? R.layout.headlines_row_compact_unread : R.layout.headlines_row_unread;
break;
@@ -826,12 +735,12 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment {
@Override
public void onBindViewHolder(final ArticleViewHolder holder, int position) {
- holder.article = items.get(position);
-
int headlineFontSize = m_prefs.getInt("headlines_font_size_sp_int", 13);
int headlineSmallFontSize = Math.max(10, Math.min(18, headlineFontSize - 2));
- final Article article = holder.article;
+ Article article = getItem(position);
+
+ holder.articleId = article.id;
if (article.id == Article.TYPE_AMR_FOOTER && m_prefs.getBoolean("headlines_mark_read_scroll", false)) {
WindowManager wm = (WindowManager) m_activity.getSystemService(Context.WINDOW_SERVICE);
@@ -854,9 +763,10 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment {
// only set active article when it makes sense (in DetailActivity)
if (getActivity() instanceof DetailActivity) {
- m_activeArticleId = article.id;
- m_adapter.notifyDataSetChanged();
- }
+ m_activeArticleId = article.id;
+
+ m_adapter.notifyItemChanged(position);
+ }
});
// block footer clicks to make button/selection clicking easier
@@ -867,18 +777,18 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment {
}
if (holder.textImage != null) {
- updateTextCheckedState(holder, article);
+ updateTextCheckedState(holder, position);
holder.textImage.setOnClickListener(view -> {
- Log.d(TAG, "textImage : onclicked");
+ Article selectedArticle = getItem(position);
- article.selected = !article.selected;
+ Log.d(TAG, "textImage onClick pos=" + position + " article=" + article);
- updateTextCheckedState(holder, article);
+ selectedArticle.selected = !selectedArticle.selected;
- m_listener.onArticleListSelectionChange(getSelectedArticles());
+ updateTextCheckedState(holder, position);
- Log.d(TAG, "num selected: " + getSelectedArticles().size());
+ m_listener.onArticleListSelectionChange();
});
ViewCompat.setTransitionName(holder.textImage, "gallery:" + article.flavorImageUri);
@@ -931,11 +841,11 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment {
holder.markedView.setIconTint(ColorStateList.valueOf(tvPrimary.data));
holder.markedView.setOnClickListener(v -> {
- article.marked = !article.marked;
+ Article selectedArticle = new Article(getItem(position));
+ selectedArticle.marked = !selectedArticle.marked;
- m_adapter.notifyItemChanged(m_list.getChildAdapterPosition(holder.view));
-
- m_activity.saveArticleMarked(article);
+ m_activity.saveArticleMarked(selectedArticle);
+ Application.getArticlesModel().update(position, selectedArticle);
});
}
@@ -996,10 +906,12 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment {
holder.publishedView.setIconTint(ColorStateList.valueOf(tvPrimary.data));
holder.publishedView.setOnClickListener(v -> {
- article.published = !article.published;
- m_adapter.notifyItemChanged(m_list.getChildAdapterPosition(holder.view));
+ Article selectedArticle = new Article(getItem(position));
+ selectedArticle.published = !selectedArticle.published;
+
+ m_activity.saveArticlePublished(selectedArticle);
- m_activity.saveArticlePublished(article);
+ Application.getArticlesModel().update(position, selectedArticle);
});
}
@@ -1061,6 +973,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment {
holder.flavorVideoKindView.setVisibility(View.GONE);
holder.flavorImageOverflow.setVisibility(View.GONE);
holder.flavorVideoView.setVisibility(View.GONE);
+ holder.flavorImageHolder.setVisibility(View.GONE);
Glide.clear(holder.flavorImageView);
@@ -1124,10 +1037,17 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment {
holder.flavorImageView.setVisibility(View.VISIBLE);
holder.flavorImageView.setMaxHeight((int)(m_screenHeight * 0.6f));
+ // only show holder if we're about to display a picture
+ holder.flavorImageHolder.setVisibility(View.VISIBLE);
+
// prevent lower listiew entries from jumping around if this row is modified
- if (article.flavorViewHeight > 0) {
- FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) holder.flavorImageView.getLayoutParams();
- lp.height = article.flavorViewHeight;
+ if (m_flavorHeightsCache.containsKey(article.id)) {
+ int cachedHeight = m_flavorHeightsCache.get(article.id);
+
+ if (cachedHeight > 0) {
+ FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) holder.flavorImageView.getLayoutParams();
+ lp.height = cachedHeight;
+ }
}
holder.flavorProgressTarget.setModel(article.flavorImageUri);
@@ -1320,18 +1240,19 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment {
if (holder.selectionBoxView != null) {
holder.selectionBoxView.setChecked(article.selected);
holder.selectionBoxView.setOnClickListener(view -> {
- CheckBox cb = (CheckBox)view;
+ Article currentArticle = getItem(position);
+
+ Log.d(TAG, "selectionCb onClick pos=" + position + " article=" + article);
- article.selected = cb.isChecked();
+ CheckBox cb = (CheckBox)view;
- m_listener.onArticleListSelectionChange(getSelectedArticles());
+ currentArticle.selected = cb.isChecked();
- Log.d(TAG, "num selected: " + getSelectedArticles().size());
+ m_listener.onArticleListSelectionChange();
});
}
if (holder.menuButtonView != null) {
-
holder.menuButtonView.setOnClickListener(v -> {
PopupMenu popup = new PopupMenu(getActivity(), v);
@@ -1341,7 +1262,8 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment {
popup.getMenu().findItem(R.id.article_set_labels).setEnabled(m_activity.getApiLevel() >= 1);
popup.getMenu().findItem(R.id.article_edit_note).setEnabled(m_activity.getApiLevel() >= 1);
- popup.setOnMenuItemClickListener(item -> onArticleMenuItemSelected(item, article,
+ popup.setOnMenuItemClickListener(item -> onArticleMenuItemSelected(item,
+ getItem(position),
m_list.getChildAdapterPosition(holder.view)));
popup.show();
@@ -1351,12 +1273,10 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment {
@Override
public int getItemViewType(int position) {
- Article a = items.get(position);
+ Article a = getItem(position);
if (a.id == Article.TYPE_AMR_FOOTER) {
return VIEW_AMR_FOOTER;
- } else if (a.id == Article.TYPE_LOADMORE) {
- return VIEW_LOADMORE;
} else if (a.id == m_activeArticleId && a.unread) {
return VIEW_ACTIVE_UNREAD;
} else if (a.id == m_activeArticleId) {
@@ -1368,12 +1288,9 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment {
}
}
- @Override
- public int getItemCount() {
- return items.size();
- }
+ private void updateTextCheckedState(final ArticleViewHolder holder, int position) {
+ Article article = getItem(position);
- private void updateTextCheckedState(final ArticleViewHolder holder, final Article article) {
String tmp = !article.title.isEmpty() ? article.title.substring(0, 1).toUpperCase() : "?";
if (article.selected) {
@@ -1508,47 +1425,60 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment {
}
}
- public void notifyUpdated() {
- m_adapter.notifyDataSetChanged();
- }
-
public void scrollToArticle(Article article) {
scrollToArticleId(article.id);
}
public void scrollToArticleId(int id) {
- m_list.scrollToPosition(Application.getArticles().getPositionById(id));
+ int position = Application.getArticles().getPositionById(id);
+
+ if (position != -1)
+ m_list.scrollToPosition(position);
}
public void setActiveArticleId(int articleId) {
if (m_list != null && articleId != m_activeArticleId) {
+ ArticleList articles = Application.getArticles();
+
+ int oldPosition = articles.getPositionById(m_activeArticleId);
+ int newPosition = articles.getPositionById(articleId);
+
m_activeArticleId = articleId;
- m_adapter.notifyDataSetChanged();
+
+ if (oldPosition != -1)
+ m_adapter.notifyItemChanged(oldPosition);
+
+ m_adapter.notifyItemChanged(newPosition);
scrollToArticleId(articleId);
+
+ if (newPosition >= articles.size() - 5)
+ new Handler().postDelayed(() -> refresh(true), 0);
}
}
public void setSelection(ArticlesSelection select) {
- for (Article a : Application.getArticles())
- a.selected = false;
+ ArticleList articlesWithoutFooters = Application.getArticles().getWithoutFooters();
- if (select != ArticlesSelection.NONE) {
- for (Article a : Application.getArticles()) {
- if (select == ArticlesSelection.ALL || select == ArticlesSelection.UNREAD && a.unread) {
- a.selected = true;
- }
- }
- }
+ for (Article a : articlesWithoutFooters) {
+ if (select == ArticlesSelection.ALL || select == ArticlesSelection.UNREAD && a.unread) {
+ a.selected = true;
- if (m_adapter != null) {
- m_adapter.notifyDataSetChanged();
- }
- }
+ int position = Application.getArticles().getPositionById(a.id);
+
+ if (position != -1)
+ m_adapter.notifyItemChanged(position);
+
+ } else if (a.selected) {
+ a.selected = false;
+
+ int position = Application.getArticles().getPositionById(a.id);
- public int getActiveArticleId() {
- return m_activeArticleId;
+ if (position != -1)
+ m_adapter.notifyItemChanged(position);
+ }
+ }
}
public String getSearchQuery() {
@@ -1559,10 +1489,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment {
if (!m_searchQuery.equals(query)) {
m_searchQuery = query;
- // could be called before fragment view has been initialized
- if (m_list != null) {
- refresh(false);
- }
+ refresh(false);
}
}
@@ -1577,4 +1504,15 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment {
releaseSurface();
}
+ private void syncToSharedArticles() {
+ ArticleList tmp = new ArticleList();
+
+ tmp.addAll(Application.getArticles());
+
+ if (m_prefs.getBoolean("headlines_mark_read_scroll", false))
+ tmp.add(new Article(Article.TYPE_AMR_FOOTER));
+
+ m_adapter.submitList(tmp);
+ }
+
}
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 9430ddc2..458f1f76 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
@@ -164,7 +164,7 @@ public class MasterActivity extends OnlineActivity implements HeadlinesEventList
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
- if (m_prefs.getBoolean("enable_cats", false)) {
+ if (m_prefs.getBoolean("enable_cats", true)) {
ft.replace(R.id.feeds_fragment, new FeedCategoriesFragment(), FRAG_CATS);
} else {
ft.replace(R.id.feeds_fragment, new FeedsFragment(), FRAG_FEEDS);
@@ -445,7 +445,7 @@ public class MasterActivity extends OnlineActivity implements HeadlinesEventList
}
@Override
- public void onArticleListSelectionChange(ArticleList m_selectedArticles) {
+ public void onArticleListSelectionChange() {
invalidateOptionsMenu();
}
@@ -476,7 +476,7 @@ public class MasterActivity extends OnlineActivity implements HeadlinesEventList
// we use shared article list, but detail activity does not use special footers
// we will append those back (if needed) in onActivityResult()
- Application.getArticles().stripFooters();
+ // Application.getArticles().stripFooters();
startActivityForResult(intent, HEADLINES_REQUEST);
overridePendingTransition(R.anim.slide_in_right, R.anim.slide_out_left);
@@ -520,19 +520,19 @@ public class MasterActivity extends OnlineActivity implements HeadlinesEventList
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
- if (requestCode == HEADLINES_REQUEST) {
-
- // we add back footers stripped when this was passed to DetailActivity
- Application.getArticles().add(new Article(Article.TYPE_AMR_FOOTER));
+ Log.d(TAG, "onActivityResult:" + requestCode + " "+ resultCode + " " + data);
+ if (requestCode == HEADLINES_REQUEST) {
HeadlinesFragment hf = (HeadlinesFragment) getSupportFragmentManager().findFragmentByTag(FRAG_HEADLINES);
if (hf != null) {
- hf.notifyUpdated();
+ // data might be null if detailactivity crashed
+ if (data != null) {
+ int activeArticleId = data.getIntExtra("activeArticleId", 0);
- // this makes position in headlines in master activity (not quite) randomly jump around when returning
- // even if active article hasn't been changed, i guess keeping it as-is is a lesser evil?
- hf.scrollToArticleId(data.getIntExtra("activeArticleId", 0));
+ Log.d(TAG, "got back from detail activity, scrolling to id=" + activeArticleId);
+ hf.scrollToArticleId(activeArticleId);
+ }
}
}
}
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 92a3f219..26dab50d 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
@@ -391,6 +391,7 @@ public class OnlineActivity extends CommonActivity {
} else if (itemId == R.id.search) {
if (hf != null) {
final EditText edit = new EditText(this);
+ edit.setText(hf.getSearchQuery());
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this)
.setTitle(R.string.search)
@@ -561,28 +562,37 @@ public class OnlineActivity extends CommonActivity {
if (ap != null) {
Article selectedArticle = Application.getArticles().getById(ap.getSelectedArticleId());
- if (selectedArticle != null)
- setArticleScore(selectedArticle);
+ if (selectedArticle != null) {
+ setArticleScore(selectedArticle);
+
+ hf.notifyItemChanged(Application.getArticles().indexOf(selectedArticle));
+ }
}
return true;
} else if (itemId == R.id.toggle_marked) {
if (ap != null) {
Article selectedArticle = Application.getArticles().getById(ap.getSelectedArticleId());
- selectedArticle.marked = !selectedArticle.marked;
- saveArticleMarked(selectedArticle);
+ if (selectedArticle != null) {
+ selectedArticle.marked = !selectedArticle.marked;
+
+ saveArticleMarked(selectedArticle);
- if (hf != null) hf.notifyUpdated();
+ hf.notifyItemChanged(Application.getArticles().indexOf(selectedArticle));
+ }
}
return true;
} else if (itemId == R.id.toggle_unread) {
if (ap != null) {
Article selectedArticle = Application.getArticles().getById(ap.getSelectedArticleId());
- selectedArticle.unread = !selectedArticle.unread;
- saveArticleUnread(selectedArticle);
+ if (selectedArticle != null) {
+ selectedArticle.unread = !selectedArticle.unread;
+
+ saveArticleUnread(selectedArticle);
- if (hf != null) hf.notifyUpdated();
+ hf.notifyItemChanged(Application.getArticles().indexOf(selectedArticle));
+ }
}
return true;
} else if (itemId == R.id.selection_toggle_unread) {
@@ -590,11 +600,13 @@ public class OnlineActivity extends CommonActivity {
ArticleList selected = hf.getSelectedArticles();
if (!selected.isEmpty()) {
- for (Article a : selected)
+ for (Article a : selected) {
a.unread = !a.unread;
+ hf.notifyItemChanged(Application.getArticles().indexOf(a));
+ }
+
toggleArticlesUnread(selected);
- hf.notifyUpdated();
invalidateOptionsMenu();
}
}
@@ -604,11 +616,13 @@ public class OnlineActivity extends CommonActivity {
ArticleList selected = hf.getSelectedArticles();
if (!selected.isEmpty()) {
- for (Article a : selected)
+ for (Article a : selected) {
a.marked = !a.marked;
+ hf.notifyItemChanged(Application.getArticles().indexOf(a));
+ }
+
toggleArticlesMarked(selected);
- hf.notifyUpdated();
invalidateOptionsMenu();
}
}
@@ -618,24 +632,26 @@ public class OnlineActivity extends CommonActivity {
ArticleList selected = hf.getSelectedArticles();
if (!selected.isEmpty()) {
- for (Article a : selected)
+ for (Article a : selected) {
a.published = !a.published;
+ hf.notifyItemChanged(Application.getArticles().indexOf(a));
+ }
+
toggleArticlesPublished(selected);
- hf.notifyUpdated();
invalidateOptionsMenu();
}
}
return true;
} else if (itemId == R.id.toggle_published) {
- if (ap != null) {
+ if (ap != null && hf != null) {
Article selectedArticle = Application.getArticles().getById(ap.getSelectedArticleId());
if (selectedArticle != null) {
selectedArticle.published = !selectedArticle.published;
saveArticlePublished(selectedArticle);
- if (hf != null) hf.notifyUpdated();
+ hf.notifyItemChanged(Application.getArticles().indexOf(selectedArticle));
}
}
return true;
@@ -688,12 +704,17 @@ public class OnlineActivity extends CommonActivity {
if (a.unread) {
a.unread = false;
tmp.add(a);
+
+ if (hf != null) {
+ int position = Application.getArticles().indexOf(a);
+
+ hf.notifyItemChanged(position);
+ }
}
}
if (!tmp.isEmpty()) {
setArticlesUnread(tmp, Article.UPDATE_SET_FALSE);
- hf.notifyUpdated();
invalidateOptionsMenu();
}
}
@@ -713,11 +734,15 @@ public class OnlineActivity extends CommonActivity {
saveArticleNote(article, note);
- HeadlinesFragment hf = (HeadlinesFragment) getSupportFragmentManager().findFragmentByTag(FRAG_HEADLINES);
- if (hf != null) hf.notifyUpdated();
+ int position = Application.getArticles().getPositionById(article.id);
- ArticlePager ap = (ArticlePager) getSupportFragmentManager().findFragmentByTag(FRAG_ARTICLE);
- if (ap != null) ap.notifyUpdated();
+ 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) -> {
@@ -839,7 +864,8 @@ public class OnlineActivity extends CommonActivity {
protected void setApiLevel(int apiLevel) {
Application.getInstance().setApiLevel(apiLevel);
}
-
+
+ // TODO switch to setArticleField()
public void saveArticleUnread(final Article article) {
ApiRequest req = new ApiRequest(getApplicationContext()) {
protected void onPostExecute(JsonElement result) {
@@ -853,11 +879,12 @@ public class OnlineActivity extends CommonActivity {
map.put("op", "updateArticle");
map.put("article_ids", String.valueOf(article.id));
map.put("mode", article.unread ? "1" : "0");
- map.put("field", "2");
+ map.put("field", String.valueOf(Article.UPDATE_FIELD_UNREAD));
req.execute(map);
}
+ // TODO switch to setArticleField()
public void saveArticleScore(final Article article) {
ApiRequest req = new ApiRequest(getApplicationContext()) {
protected void onPostExecute(JsonElement result) {
@@ -871,11 +898,12 @@ public class OnlineActivity extends CommonActivity {
map.put("op", "updateArticle");
map.put("article_ids", String.valueOf(article.id));
map.put("data", String.valueOf(article.score));
- map.put("field", "4");
+ map.put("field", String.valueOf(Article.UPDATE_FIELD_SCORE));
req.execute(map);
}
+ // TODO switch to setArticleField()
public void saveArticleMarked(final Article article) {
ApiRequest req = new ApiRequest(getApplicationContext()) {
protected void onPostExecute(JsonElement result) {
@@ -889,11 +917,12 @@ public class OnlineActivity extends CommonActivity {
map.put("op", "updateArticle");
map.put("article_ids", String.valueOf(article.id));
map.put("mode", article.marked ? "1" : "0");
- map.put("field", "0");
+ map.put("field", String.valueOf(Article.UPDATE_FIELD_MARKED));
req.execute(map);
}
+ // TODO switch to setArticleField()
public void saveArticlePublished(final Article article) {
ApiRequest req = new ApiRequest(getApplicationContext()) {
@@ -908,11 +937,12 @@ public class OnlineActivity extends CommonActivity {
map.put("op", "updateArticle");
map.put("article_ids", String.valueOf(article.id));
map.put("mode", article.published ? "1" : "0");
- map.put("field", "1");
+ map.put("field", String.valueOf(Article.UPDATE_FIELD_PUBLISHED));
req.execute(map);
}
+ // TODO switch to setArticleField()
public void saveArticleNote(final Article article, final String note) {
ApiRequest req = new ApiRequest(getApplicationContext()) {
protected void onPostExecute(JsonElement result) {
@@ -926,7 +956,7 @@ public class OnlineActivity extends CommonActivity {
map.put("article_ids", String.valueOf(article.id));
map.put("mode", "1");
map.put("data", note);
- map.put("field", "3");
+ map.put("field", String.valueOf(Article.UPDATE_FIELD_NOTE));
req.execute(map);
}
@@ -1005,7 +1035,8 @@ article.score = Integer.parseInt(edit.getText().toString());
if (selectedArticle != null) {
selectedArticle.unread = !selectedArticle.unread;
saveArticleUnread(selectedArticle);
- if (hf != null) hf.notifyUpdated();
+
+ hf.notifyItemChanged(Application.getArticles().indexOf(selectedArticle));
}
}
return true;
@@ -1081,8 +1112,6 @@ article.score = Integer.parseInt(edit.getText().toString());
}
public void setArticlesUnread(final ArticleList articles, int mode) {
- ApiRequest req = new ApiRequest(getApplicationContext());
-
setArticleField(articles, Article.UPDATE_FIELD_UNREAD, mode);
}
@@ -1099,11 +1128,19 @@ article.score = Integer.parseInt(edit.getText().toString());
protected void onPostExecute(JsonElement result) {
Log.d(TAG, "setArticleField operation complete");
- HeadlinesFragment hf = (HeadlinesFragment) getSupportFragmentManager().findFragmentByTag(FRAG_HEADLINES);
- if (hf != null) hf.notifyUpdated();
+ // currently this is generally handled before operation completes (but after POJO is modified)
+ /* HeadlinesFragment hf = (HeadlinesFragment) getSupportFragmentManager().findFragmentByTag(FRAG_HEADLINES);
ArticlePager ap = (ArticlePager) getSupportFragmentManager().findFragmentByTag(FRAG_ARTICLE);
- if (ap != null) ap.notifyUpdated();
+
+ for (Article a : articles) {
+ int position = Application.getArticles().getPositionById(a.id);
+
+ if (position != -1) {
+ if (hf != null) hf.notifyItemChanged(position);
+ if (ap != null) ap.notifyItemChanged(position);
+ }
+ } */
}
};
@@ -1189,12 +1226,6 @@ article.score = Integer.parseInt(edit.getText().toString());
if (hf != null) {
hf.refresh(false);
}
-
- ArticlePager af = (ArticlePager) getSupportFragmentManager().findFragmentByTag(FRAG_ARTICLE);
-
- if (af != null) {
- af.refresh(false);
- }
}
}
@@ -1363,15 +1394,6 @@ article.score = Integer.parseInt(edit.getText().toString());
return m_lastImageHitTestUrl;
}
- public boolean isWifiConnected() {
- NetworkInfo wifi = m_cmgr.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
-
- if (wifi != null)
- return wifi.isConnected();
-
- return false;
- }
-
public int getResizeWidth() {
Display display = getWindowManager().getDefaultDisplay();
Point size = new Point();
diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/share/CommonActivity.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/share/CommonActivity.java
index 63458532..9c9187d9 100644
--- a/org.fox.ttrss/src/main/java/org/fox/ttrss/share/CommonActivity.java
+++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/share/CommonActivity.java
@@ -10,7 +10,6 @@ public class CommonActivity extends Activity {
private final String TAG = this.getClass().getSimpleName();
private boolean m_smallScreenMode = true;
- private boolean m_compatMode = false;
protected void setSmallScreen(boolean smallScreen) {
Log.d(TAG, "m_smallScreenMode=" + smallScreen);
@@ -27,23 +26,10 @@ public class CommonActivity extends Activity {
toast.show();
}
- @Override
- public void onCreate(Bundle savedInstanceState) {
- m_compatMode = android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB;
-
- Log.d(TAG, "m_compatMode=" + m_compatMode);
-
- super.onCreate(savedInstanceState);
- }
-
public boolean isSmallScreen() {
return m_smallScreenMode;
}
- public boolean isCompatMode() {
- return m_compatMode;
- }
-
public boolean isPortrait() {
Display display = getWindowManager().getDefaultDisplay();
diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/share/ShareActivity.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/share/ShareActivity.java
index 3a1553b6..66a77aef 100644
--- a/org.fox.ttrss/src/main/java/org/fox/ttrss/share/ShareActivity.java
+++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/share/ShareActivity.java
@@ -86,7 +86,7 @@ public class ShareActivity extends CommonShareActivity {
protected void onPostExecute(JsonElement result) {
setProgressBarIndeterminateVisibility(false);
- if (m_lastError != ApiCommon.ApiError.NO_ERROR) {
+ if (m_lastError != ApiCommon.ApiError.UNKNOWN_ERROR) {
toast(getErrorMessage());
} else {
toast(R.string.share_article_posted);
diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/share/SubscribeActivity.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/share/SubscribeActivity.java
index e71583d3..82e2d52d 100755
--- a/org.fox.ttrss/src/main/java/org/fox/ttrss/share/SubscribeActivity.java
+++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/share/SubscribeActivity.java
@@ -157,7 +157,7 @@ public class SubscribeActivity extends CommonShareActivity {
protected void onPostExecute(JsonElement result) {
m_progressBar.setVisibility(View.INVISIBLE);
- if (m_lastError != ApiCommon.ApiError.NO_ERROR) {
+ if (m_lastError != ApiCommon.ApiError.UNKNOWN_ERROR) {
toast(getErrorMessage());
} else {
try {
@@ -259,7 +259,7 @@ public class SubscribeActivity extends CommonShareActivity {
protected void onPostExecute(JsonElement result) {
m_progressBar.setVisibility(View.INVISIBLE);
- if (m_lastError != ApiCommon.ApiError.NO_ERROR) {
+ if (m_lastError != ApiCommon.ApiError.UNKNOWN_ERROR) {
toast(getErrorMessage());
} else {
JsonArray content = result.getAsJsonArray();
diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/types/Article.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/types/Article.java
index 7d476ad6..f0795dd8 100755
--- a/org.fox.ttrss/src/main/java/org/fox/ttrss/types/Article.java
+++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/types/Article.java
@@ -14,7 +14,6 @@ import java.util.regex.Pattern;
// TODO: serialize Labels
public class Article implements Parcelable {
- public static final int TYPE_LOADMORE = -1;
public static final int TYPE_AMR_FOOTER = -2;
public static final int FLAVOR_KIND_ALBUM = 1;
@@ -69,9 +68,8 @@ public class Article implements Parcelable {
transient public String flavorStreamUri;
transient public String youtubeVid;
transient public List<Element> mediaList = new ArrayList<>();
- transient public int flavorViewHeight;
- public Article(Parcel in) {
+ public Article(Parcel in) {
readFromParcel(in);
}
@@ -194,8 +192,44 @@ public class Article implements Parcelable {
public Article(int id) {
this.id = id;
this.title = "ID:" + id;
- this.link = "";
- this.tags = new ArrayList<>();
+ fixNullFields();
+ }
+
+ public Article(Article clone) {
+ id = clone.id;
+ unread = clone.unread;
+ marked = clone.marked;
+ published = clone.published;
+ score = clone.score;
+ updated = clone.updated;
+ is_updated = clone.is_updated;
+ title = clone.title;
+ link = clone.link;
+ feed_id = clone.feed_id;
+ tags = clone.tags;
+ attachments = clone.attachments;
+ content = clone.content;
+ excerpt = clone.excerpt;
+ labels = clone.labels;
+ feed_title = clone.feed_title;
+ comments_count = clone.comments_count;
+ comments_link = clone.comments_link;
+ always_display_attachments = clone.always_display_attachments;
+ author = clone.author;
+ note = clone.note;
+ selected = clone.selected;
+ flavor_image = clone.flavor_image;
+ flavor_stream = clone.flavor_stream;
+ flavor_kind = clone.flavor_kind;
+ site_url = clone.site_url;
+
+ articleDoc = clone.articleDoc;
+ flavorImage = clone.flavorImage;
+
+ flavorImageUri = clone.flavorImageUri;
+ flavorStreamUri = clone.flavorStreamUri;
+ youtubeVid = clone.youtubeVid;
+ mediaList = new ArrayList<>(clone.mediaList);
}
@Override
@@ -262,13 +296,9 @@ public class Article implements Parcelable {
}
public boolean equalsById(Article article) {
- if (article != null && id == article.id) {
- return true;
- } else {
- return false;
- }
+ return article != null && id == article.id;
}
-
+
@SuppressWarnings("rawtypes")
public static final Parcelable.Creator CREATOR =
new Parcelable.Creator() {
@@ -280,4 +310,14 @@ public class Article implements Parcelable {
return new Article[size];
}
};
+
+ /** set fields which might be missing during JSON deserialization to sane values */
+ public void fixNullFields() {
+ if (note == null) note = "";
+ if (link == null) link = "";
+ if (tags == null) tags = new ArrayList<>();
+ if (note == null) note = "";
+ if (excerpt == null) excerpt = "";
+ if (content == null) content = "";
+ }
}
diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/types/ArticleList.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/types/ArticleList.java
index 873b311b..6724acb0 100755
--- a/org.fox.ttrss/src/main/java/org/fox/ttrss/types/ArticleList.java
+++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/types/ArticleList.java
@@ -1,8 +1,5 @@
package org.fox.ttrss.types;
-import android.os.Parcel;
-import android.os.Parcelable;
-
import java.util.ListIterator;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors;
@@ -26,6 +23,10 @@ public class ArticleList extends CopyOnWriteArrayList<Article> {
public ArticleList() { }
+ public ArticleList(ArticleList clone) {
+ this.addAll(clone);
+ }
+
public ArticleList getWithoutFooters() {
return this.stream().filter(a -> { return a.id > 0; }).collect(Collectors.toCollection(ArticleList::new));
}
@@ -34,22 +35,6 @@ public class ArticleList extends CopyOnWriteArrayList<Article> {
return this.stream().filter(a -> { return a.unread; }).count();
}
- public long getSizeWithoutFooters() {
- return this.stream().filter(a -> { return a.id > 0; }).count();
- }
-
- /** strips all trailing items with negative IDs (Article.TYPE_LOADMORE, Article.TYPE_AMR_FOOTER) */
- public void stripFooters() {
- for (ListIterator<Article> iterator = this.listIterator(size()); iterator.hasPrevious();) {
- final Article article = iterator.previous();
-
- if (article.id < 0)
- this.remove(article);
- else
- break;
- }
- }
-
public int getPositionById(int id) {
for (int i = 0; i < size(); i++) {
if (get(i).id == id) {
diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/util/ArticleDiffItemCallback.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/util/ArticleDiffItemCallback.java
new file mode 100644
index 00000000..b037eea0
--- /dev/null
+++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/util/ArticleDiffItemCallback.java
@@ -0,0 +1,21 @@
+package org.fox.ttrss.util;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.DiffUtil;
+
+import org.fox.ttrss.types.Article;
+
+public class ArticleDiffItemCallback extends DiffUtil.ItemCallback<Article> {
+ private final String TAG = this.getClass().getSimpleName();
+ @Override
+ public boolean areItemsTheSame(@NonNull Article a1, @NonNull Article a2) {
+ return a1.id == a2.id;
+ }
+
+ @Override
+ public boolean areContentsTheSame(@NonNull Article a1, @NonNull Article a2) {
+ return a1.id == a2.id && a1.unread == a2.unread && a1.marked == a2.marked
+ && a1.selected == a2.selected && a1.published == a2.published
+ && a1.note.equals(a2.note);
+ }
+}
diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/util/DiffFragmentStateAdapter.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/util/DiffFragmentStateAdapter.java
new file mode 100644
index 00000000..c423b02d
--- /dev/null
+++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/util/DiffFragmentStateAdapter.java
@@ -0,0 +1,54 @@
+package org.fox.ttrss.util;
+
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
+import androidx.fragment.app.FragmentManager;
+import androidx.lifecycle.Lifecycle;
+import androidx.recyclerview.widget.AsyncListDiffer;
+import androidx.recyclerview.widget.DiffUtil;
+import androidx.viewpager2.adapter.FragmentStateAdapter;
+
+import java.util.List;
+
+// https://gist.github.com/Gnzlt/7e8a23ba0c3b046ed33c824b284d7270
+public abstract class DiffFragmentStateAdapter<T> extends FragmentStateAdapter {
+
+ private final AsyncListDiffer<T> differ;
+
+ protected DiffFragmentStateAdapter(FragmentActivity fragmentActivity, DiffUtil.ItemCallback<T> diffCallback) {
+ super(fragmentActivity);
+ differ = new AsyncListDiffer<>(this, diffCallback);
+ }
+
+ protected DiffFragmentStateAdapter(Fragment fragment, DiffUtil.ItemCallback<T> diffCallback) {
+ super(fragment);
+ differ = new AsyncListDiffer<>(this, diffCallback);
+ }
+
+ protected DiffFragmentStateAdapter(FragmentManager fragmentManager, Lifecycle lifecycle, DiffUtil.ItemCallback<T> diffCallback) {
+ super(fragmentManager, lifecycle);
+ differ = new AsyncListDiffer<>(this, diffCallback);
+ }
+
+ public void submitList(List<T> list, Runnable commitCallback) {
+ differ.submitList(list, commitCallback);
+ }
+
+ public void submitList(List<T> list) {
+ differ.submitList(list, null);
+ }
+
+ public List<T> getCurrentList() {
+ return differ.getCurrentList();
+ }
+
+ protected T getItem(int position) {
+ return differ.getCurrentList().get(position);
+ }
+
+ @Override
+ public int getItemCount() {
+ return differ.getCurrentList().size();
+ }
+}
+
diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/util/HeadlinesRequest.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/util/HeadlinesRequest.java
deleted file mode 100755
index 82698ffb..00000000
--- a/org.fox.ttrss/src/main/java/org/fox/ttrss/util/HeadlinesRequest.java
+++ /dev/null
@@ -1,106 +0,0 @@
-package org.fox.ttrss.util;
-
-import android.content.Context;
-import android.util.Log;
-
-import com.google.gson.Gson;
-import com.google.gson.JsonArray;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
-import com.google.gson.reflect.TypeToken;
-
-import org.fox.ttrss.ApiCommon;
-import org.fox.ttrss.ApiRequest;
-import org.fox.ttrss.Application;
-import org.fox.ttrss.OnlineActivity;
-import org.fox.ttrss.types.Article;
-import org.fox.ttrss.types.ArticleList;
-
-import java.lang.reflect.Type;
-import java.util.List;
-
-public class HeadlinesRequest extends ApiRequest {
- private final String TAG = this.getClass().getSimpleName();
-
- private int m_offset = 0;
- private final OnlineActivity m_activity;
- protected boolean m_firstIdChanged = false;
- protected int m_firstId = 0;
- protected int m_amountLoaded = 0;
-
- public HeadlinesRequest(Context context, OnlineActivity activity, ArticleList articles) {
- super(context);
-
- m_activity = activity;
- }
-
- protected void onPostExecute(JsonElement result) {
- if (result != null) {
- try {
- JsonArray content = result.getAsJsonArray();
- if (content != null) {
- final List<Article> articlesJson;
- final JsonObject header;
-
- if (m_activity.getApiLevel() >= 12) {
- header = content.get(0).getAsJsonObject();
-
- //Log.d(TAG, "headerID:" + header.get("top_id_changed"));
-
- m_firstIdChanged = header.get("first_id_changed") != null;
- try {
- m_firstId = header.get("first_id").getAsInt();
- } catch (NumberFormatException e) {
- m_firstId = 0;
- }
-
- Log.d(TAG, "firstID=" + m_firstId + " firstIdChanged=" + m_firstIdChanged);
-
- Type listType = new TypeToken<List<Article>>() {}.getType();
- articlesJson = new Gson().fromJson(content.get(1), listType);
- } else {
- Type listType = new TypeToken<List<Article>>() {}.getType();
- articlesJson = new Gson().fromJson(content, listType);
- }
-
- ArticleList articles = Application.getArticles();
-
- if (m_offset == 0)
- articles.clear();
- else
- articles.stripFooters();
-
- m_amountLoaded = articlesJson.size();
-
- for (Article f : articlesJson)
- if (!articles.containsId(f.id)) {
- f.collectMediaInfo();
- f.cleanupExcerpt();
- articles.add(f);
- }
-
- return;
- }
-
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-
- if (m_lastError == ApiCommon.ApiError.LOGIN_FAILED) {
- m_activity.login();
- } else {
-
- if (m_lastErrorMessage != null) {
- m_activity.toast(m_activity.getString(getErrorMessage()) + "\n" + m_lastErrorMessage);
- } else {
- m_activity.toast(getErrorMessage());
- }
- //m_activity.setLoadingStatus(getErrorMessage(), false);
- }
- }
-
- public void setOffset(int skip) {
- m_offset = skip;
- }
-}
diff --git a/org.fox.ttrss/src/main/res/drawable/ic_launcher_background_variant.xml b/org.fox.ttrss/src/main/res/drawable/ic_launcher_background_variant.xml
new file mode 100644
index 00000000..749ffa26
--- /dev/null
+++ b/org.fox.ttrss/src/main/res/drawable/ic_launcher_background_variant.xml
@@ -0,0 +1,42 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="108dp"
+ android:height="108dp"
+ android:viewportWidth="51.06383"
+ android:viewportHeight="51.06383">
+ <group android:translateX="-10.468085"
+ android:translateY="-10.468085">
+ <path
+ android:pathData="M-9.8995,-7.7102h94.3666v92.4381h-94.3666z"
+ android:fillColor="#5c5c5c"
+ android:strokeColor="#00000000"
+ android:fillAlpha="1"/>
+ <path
+ android:pathData="M5.668,58.0128a8.3196,8.3198 0,1 0,16.6393 0a8.3196,8.3198 0,1 0,-16.6393 0z"
+ android:strokeAlpha="1"
+ android:strokeWidth="0.69765961"
+ android:fillColor="#808080"
+ android:strokeColor="#00000000"
+ android:fillAlpha="1"/>
+ <path
+ android:pathData="m45.8796,66.3326 l-11.7862,0A28.4254,28.426 0,0 0,5.668 37.9066l0,-11.7864A40.2116,40.2124 0,0 1,45.8796 66.3326Z"
+ android:strokeAlpha="1"
+ android:strokeWidth="0.69765961"
+ android:fillColor="#808080"
+ android:strokeColor="#00000000"
+ android:fillAlpha="1"/>
+ <path
+ android:pathData="M54.1992,66.3326A48.5312,48.5322 0,0 0,5.668 17.8004l0,-12.1331a60.6653,60.6641 90,0 1,60.6641 60.6653z"
+ android:strokeAlpha="1"
+ android:strokeWidth="2.01254654"
+ android:fillColor="#808080"
+ android:strokeColor="#00000000"
+ android:fillAlpha="1"/>
+ <path
+ android:pathData="M54.1992,66.3326A48.5312,48.5322 0,0 0,5.668 17.8004l0,-12.1331a60.6653,60.6641 90,0 1,60.6641 60.6653z"
+ android:strokeAlpha="1"
+ android:strokeWidth="2.01254654"
+ android:fillColor="#808080"
+ android:strokeColor="#00000000"
+ android:fillAlpha="1"/>
+ </group>
+</vector>
diff --git a/org.fox.ttrss/src/main/res/layout-sw600dp-land/activity_detail.xml b/org.fox.ttrss/src/main/res/layout-sw600dp-land/activity_detail.xml
index 59c1b086..823afb9b 100644
--- a/org.fox.ttrss/src/main/res/layout-sw600dp-land/activity_detail.xml
+++ b/org.fox.ttrss/src/main/res/layout-sw600dp-land/activity_detail.xml
@@ -54,7 +54,7 @@
<FrameLayout
android:layout_width="match_parent"
android:id="@+id/article_fragment"
- app:layout_behavior=".util.DetailActivityScrollingViewBehavior"
+ app:layout_behavior="org.fox.ttrss.util.DetailActivityScrollingViewBehavior"
android:layout_height="match_parent"/>
<com.google.android.material.bottomappbar.BottomAppBar
diff --git a/org.fox.ttrss/src/main/res/layout-sw600dp-land/activity_master.xml b/org.fox.ttrss/src/main/res/layout-sw600dp-land/activity_master.xml
index b6a6f19d..63b8c3e2 100644
--- a/org.fox.ttrss/src/main/res/layout-sw600dp-land/activity_master.xml
+++ b/org.fox.ttrss/src/main/res/layout-sw600dp-land/activity_master.xml
@@ -61,7 +61,7 @@
android:id="@+id/headlines_fragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- app:layout_behavior=".util.FabAwareScrollingViewBehavior" />
+ app:layout_behavior="org.fox.ttrss.util.FabAwareScrollingViewBehavior" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/master_fab"
diff --git a/org.fox.ttrss/src/main/res/layout/fragment_headlines.xml b/org.fox.ttrss/src/main/res/layout/fragment_headlines.xml
index 2783a1e4..647c3ae9 100755
--- a/org.fox.ttrss/src/main/res/layout/fragment_headlines.xml
+++ b/org.fox.ttrss/src/main/res/layout/fragment_headlines.xml
@@ -12,7 +12,6 @@
<org.fox.ttrss.util.ContextMenuRecyclerView
android:id="@+id/headlines_list"
android:background="?colorSurfaceContainer"
- android:drawSelectorOnTop="true"
android:scrollbars="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" />
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
index 5721dcb8..f6483350 100755
--- 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
@@ -101,6 +101,7 @@
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="?ic_star_outline" />
diff --git a/org.fox.ttrss/src/main/res/layout/layout_detail_phone.xml b/org.fox.ttrss/src/main/res/layout/layout_detail_phone.xml
index 55a6ba77..4e75d44b 100644
--- a/org.fox.ttrss/src/main/res/layout/layout_detail_phone.xml
+++ b/org.fox.ttrss/src/main/res/layout/layout_detail_phone.xml
@@ -14,7 +14,7 @@
<FrameLayout
android:id="@+id/article_fragment"
- app:layout_behavior=".util.DetailActivityScrollingViewBehavior"
+ app:layout_behavior="org.fox.ttrss.util.DetailActivityScrollingViewBehavior"
android:layout_width="match_parent"
android:layout_height="match_parent">
</FrameLayout>
diff --git a/org.fox.ttrss/src/main/res/layout/layout_master_phone.xml b/org.fox.ttrss/src/main/res/layout/layout_master_phone.xml
index b4cb6b59..e32d5fbe 100644
--- a/org.fox.ttrss/src/main/res/layout/layout_master_phone.xml
+++ b/org.fox.ttrss/src/main/res/layout/layout_master_phone.xml
@@ -29,7 +29,7 @@
<FrameLayout
android:id="@+id/headlines_fragment"
- app:layout_behavior=".util.FabAwareScrollingViewBehavior"
+ app:layout_behavior="org.fox.ttrss.util.FabAwareScrollingViewBehavior"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
diff --git a/org.fox.ttrss/src/main/res/mipmap-anydpi-v26/ic_launcher_variant.xml b/org.fox.ttrss/src/main/res/mipmap-anydpi-v26/ic_launcher_variant.xml
new file mode 100644
index 00000000..d49922f9
--- /dev/null
+++ b/org.fox.ttrss/src/main/res/mipmap-anydpi-v26/ic_launcher_variant.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@drawable/ic_launcher_background_variant"/>
+ <foreground android:drawable="@drawable/ic_launcher_foreground"/>
+</adaptive-icon> \ 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 6a67013a..fb7e6309 100755
--- a/org.fox.ttrss/src/main/res/values/strings.xml
+++ b/org.fox.ttrss/src/main/res/values/strings.xml
@@ -303,4 +303,5 @@
<string name="check_for_updates">Check for updates</string>
<string name="dont_open_anything">Nothing</string>
<string name="open_on_startup">Open on startup</string>
+ <string name="error_success">Operation completed successfully</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 81fbd5f1..8d064587 100755
--- a/org.fox.ttrss/src/main/res/xml/preferences.xml
+++ b/org.fox.ttrss/src/main/res/xml/preferences.xml
@@ -44,7 +44,7 @@
android:title="@string/sort_feeds_by_unread" />
<SwitchPreferenceCompat
- android:defaultValue="false"
+ android:defaultValue="true"
android:key="enable_cats"
android:title="@string/enable_cats" />
@@ -108,7 +108,6 @@
<SwitchPreferenceCompat
android:defaultValue="true"
- android:dependency="headlines_mark_read_scroll"
android:key="headlines_swipe_to_dismiss"
android:summary="@string/pref_headlines_swipe_to_dismiss_long"
android:title="@string/pref_headlines_swipe_to_dismiss" />