summaryrefslogtreecommitdiff
path: root/org.fox.ttrss
diff options
context:
space:
mode:
authorAndrew Dolgov <fox@fakecake.org>2025-05-14 13:10:27 +0300
committerAndrew Dolgov <fox@fakecake.org>2025-05-14 13:10:27 +0300
commit5dac45ba85a786b4a95038d11f5925ce09594898 (patch)
tree3e6202183fd15098654bb83c8b1f9015b35847b5 /org.fox.ttrss
parent3dfd1a4c9c978fd0a6f1e58a7d8fd235ed6d997a (diff)
wip implementation of viewmodel/executor/livedata headlines
Diffstat (limited to 'org.fox.ttrss')
-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.java11
-rwxr-xr-xorg.fox.ttrss/src/main/java/org/fox/ttrss/ArticlePager.java11
-rwxr-xr-xorg.fox.ttrss/src/main/java/org/fox/ttrss/HeadlinesFragment.java127
-rwxr-xr-xorg.fox.ttrss/src/main/java/org/fox/ttrss/HeadlinesLoader.java289
-rw-r--r--org.fox.ttrss/src/main/java/org/fox/ttrss/HeadlinesModel.java317
-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/res/values/strings.xml1
11 files changed, 401 insertions, 373 deletions
diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/ApiCommon.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/ApiCommon.java
index 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 81ae7bdf..08beaa99 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
@@ -19,19 +19,24 @@ public class Application extends android.app.Application {
// 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 final ArticleList m_articles = new ArticleList();
private String m_sessionId;
private int m_apiLevel;
public LinkedHashMap<String, String> m_customSortModes = new LinkedHashMap<>();
ConnectivityManager m_cmgr;
+ HeadlinesModel m_headlinesModel;
public static Application getInstance(){
return m_singleton;
}
public static ArticleList getArticles() {
- return getInstance().m_articles;
+ return getInstance().m_headlinesModel.getArticles();
+ }
+
+ public HeadlinesModel getHeadlinesModel() {
+ return getInstance().m_headlinesModel;
}
@Override
@@ -40,6 +45,7 @@ public class Application extends android.app.Application {
m_singleton = this;
m_cmgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
+ m_headlinesModel = new HeadlinesModel(this);
}
public String getSessionId() {
@@ -87,5 +93,4 @@ public class Application extends android.app.Application {
return false;
}
-
}
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 f1ff8384..72b873a2 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
@@ -35,8 +35,6 @@ public class ArticlePager extends androidx.fragment.app.Fragment {
public PagerAdapter(@NonNull Fragment fragment) {
super(fragment, new HeadlinesDiffItemCallback());
-
- syncToSharedArticles();
}
private void syncToSharedArticles() {
@@ -88,6 +86,15 @@ public class ArticlePager extends androidx.fragment.app.Fragment {
View view = inflater.inflate(R.layout.fragment_article_pager, container, false);
m_adapter = new PagerAdapter(this);
+ m_adapter.submitList(Application.getArticles());
+
+ HeadlinesModel model = Application.getInstance().getHeadlinesModel();
+
+ // deal with further updates
+ model.getLiveData().observe(getActivity(), articles -> {
+ Log.d(TAG, "observed article list size=" + articles.size());
+ m_adapter.submitList(articles);
+ });
m_pager = view.findViewById(R.id.article_pager);
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 a3a1bb46..6ca33b60 100755
--- a/org.fox.ttrss/src/main/java/org/fox/ttrss/HeadlinesFragment.java
+++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/HeadlinesFragment.java
@@ -48,7 +48,6 @@ import androidx.annotation.Nullable;
import androidx.core.app.ActivityCompat;
import androidx.core.app.ActivityOptionsCompat;
import androidx.core.view.ViewCompat;
-import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader;
import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.DefaultItemAnimator;
@@ -86,74 +85,10 @@ import java.util.TimeZone;
import jp.wasabeef.glide.transformations.CropCircleTransformation;
-public class HeadlinesFragment extends androidx.fragment.app.Fragment implements LoaderManager.LoaderCallbacks<ArticleList> {
+public class HeadlinesFragment extends androidx.fragment.app.Fragment {
private boolean m_isLazyLoading;
- @NonNull
- @Override
- public Loader<ArticleList> onCreateLoader(int id, @Nullable Bundle args) {
- return new HeadlinesLoader(getContext(), m_feed, m_activity.getResizeWidth(), Application.getArticles());
- }
-
- @Override
- public void onLoadFinished(@NonNull Loader<ArticleList> loader, ArticleList data) {
- Log.d(TAG, "onLoadFinished loader=" + loader + " size=" + (data != null ? data.size() : "N/A (null)"));
-
- HeadlinesLoader headlinesLoader = (HeadlinesLoader) loader;
-
- // successful update
- if (data != null) {
-
- // shared article list contains raw returned data without footers
- ArticleList sharedArticles = Application.getArticles();
- sharedArticles.clear();
- sharedArticles.addAll(data);
-
- ArticleList tmp = new ArticleList();
-
- tmp.addAll(data);
-
- if (m_prefs.getBoolean("headlines_mark_read_scroll", false))
- tmp.add(new Article(Article.TYPE_AMR_FOOTER));
-
- final boolean appended = headlinesLoader.getAppend();
-
- m_adapter.submitList(tmp, () -> {
- if (!appended)
- m_list.scrollToPosition(0);
- });
-
- if (headlinesLoader.getFirstIdChanged())
- Snackbar.make(getView(), R.string.headlines_row_top_changed, Snackbar.LENGTH_LONG)
- .setAction(R.string.reload, v -> refresh(false)).show();
-
- m_listener.onHeadlinesLoaded(headlinesLoader.getAppend());
-
- } else {
- if (headlinesLoader.getLastError() == ApiCommon.ApiError.LOGIN_FAILED) {
- m_activity.login();
- } else {
- if (headlinesLoader.getLastErrorMessage() != null) {
- m_activity.toast(m_activity.getString(headlinesLoader.getErrorMessage()) + "\n" + headlinesLoader.getLastErrorMessage());
- } else {
- m_activity.toast(headlinesLoader.getErrorMessage());
- }
- }
- }
-
- if (m_swipeLayout != null)
- m_swipeLayout.setRefreshing(false);
-
- m_isLazyLoading = false;
- }
-
- @Override
- public void onLoaderReset(@NonNull Loader<ArticleList> loader) {
- if (m_swipeLayout != null)
- m_swipeLayout.setRefreshing(false);
- }
-
public void notifyItemChanged(int position) {
if (m_adapter != null)
m_adapter.notifyItemChanged(position);
@@ -169,7 +104,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment implements
private Feed m_feed;
private int m_activeArticleId;
private String m_searchQuery = "";
- private HeadlinesLoader m_loader;
+ //private HeadlinesLoader m_loader;
private SharedPreferences m_prefs;
@@ -502,7 +437,9 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment implements
}
}
- if (dy > 0 && !m_isLazyLoading && (m_loader == null || m_loader.lazyLoadEnabled()) &&
+ HeadlinesModel model = Application.getInstance().getHeadlinesModel();
+
+ if (dy > 0 && !m_isLazyLoading && model.lazyLoadEnabled() &&
lastVisibleItem >= Application.getArticles().size() - 5) {
m_isLazyLoading = true;
@@ -519,6 +456,47 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment implements
m_activity.setTitle(m_feed.title);
}
+ HeadlinesModel model = Application.getInstance().getHeadlinesModel();
+
+ model.getLiveData().observe(getActivity(), articles -> {
+ Log.d(TAG, "observed article list size=" + articles.size());
+
+ ArticleList tmp = new ArticleList();
+
+ tmp.addAll(articles);
+
+ if (m_prefs.getBoolean("headlines_mark_read_scroll", false))
+ tmp.add(new Article(Article.TYPE_AMR_FOOTER));
+
+ final boolean appended = model.getAppend();
+
+ m_adapter.submitList(tmp, () -> {
+ if (!appended)
+ m_list.scrollToPosition(0);
+ });
+
+ if (model.getFirstIdChanged())
+ Snackbar.make(getView(), R.string.headlines_row_top_changed, Snackbar.LENGTH_LONG)
+ .setAction(R.string.reload, v -> refresh(false)).show();
+
+ if (m_swipeLayout != null)
+ m_swipeLayout.setRefreshing(false);
+
+ m_isLazyLoading = false;
+
+ 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());
+ }
+ }
+
+ m_listener.onHeadlinesLoaded(appended);
+ });
+
return view;
}
@@ -561,17 +539,22 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment implements
// if we try to initLoader() all the time, onLoadFinished() might be sent twice
// https://stackoverflow.com/questions/11293441/android-loadercallbacks-onloadfinished-called-twice
- if (m_loader == null) {
+ /* if (m_loader == null) {
m_loader = (HeadlinesLoader) LoaderManager.getInstance(this).
initLoader(Application.LOADER_HEADLINES, null, this);
- }
+ } */
if (m_swipeLayout != null)
m_swipeLayout.setRefreshing(true);
- m_loader.setSearchQuery(getSearchQuery());
- m_loader.startLoading(append);
+ HeadlinesModel model = Application.getInstance().getHeadlinesModel();
+
+ model.setSearchQuery(getSearchQuery());
+ model.startLoading(append, m_feed, m_activity.getResizeWidth());
+
+ //m_loader.setSearchQuery(getSearchQuery());
+ //m_loader.startLoading(append);
}
static class ArticleViewHolder extends RecyclerView.ViewHolder {
diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/HeadlinesLoader.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/HeadlinesLoader.java
deleted file mode 100755
index 812a745e..00000000
--- a/org.fox.ttrss/src/main/java/org/fox/ttrss/HeadlinesLoader.java
+++ /dev/null
@@ -1,289 +0,0 @@
-package org.fox.ttrss;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.util.Log;
-
-import androidx.loader.content.AsyncTaskLoader;
-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.ApiCommon.ApiError;
-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;
-
-public class HeadlinesLoader extends AsyncTaskLoader<ArticleList> implements ApiCommon.ApiCaller {
- private final String TAG = this.getClass().getSimpleName();
-
- private final int m_responseCode = 0;
- protected String m_responseMessage;
- private int m_apiStatusCode = 0;
-
- private Context m_context;
- private String m_lastErrorMessage;
- private ApiError m_lastError;
- private ArticleList m_articles;
- private Feed m_feed;
- private SharedPreferences m_prefs;
- 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;
-
- HeadlinesLoader(Context context, Feed feed, int resizeWidth, ArticleList articles) {
- super(context);
-
- Log.d(TAG, "HeadlinesLoader created!");
-
- m_context = context;
- m_lastError = ApiError.NO_ERROR;
- m_feed = feed;
- m_articles = new ArticleList();
-
- m_articles.addAll(articles);
-
- m_resizeWidth = resizeWidth;
- m_prefs = PreferenceManager.getDefaultSharedPreferences(context);
- }
-
- protected void startLoading(boolean append) {
- Log.d(TAG, this + " startLoading append=" + append + " inProgress=" + m_loadingInProgress + " lazyLoadEnabled=" + m_lazyLoadEnabled + "localSize="+ m_articles.size());
-
- if (!append) {
- m_append = false;
- m_lazyLoadEnabled = true;
-
- forceLoad();
- } else if (m_lazyLoadEnabled && !m_loadingInProgress) {
- m_append = true;
- forceLoad();
- } else {
- deliverResult(m_articles);
- }
- }
-
- @Override
- public void deliverResult(ArticleList data) {
- super.deliverResult(data);
- }
-
- public int getErrorMessage() {
- return ApiCommon.getErrorMessage(m_lastError);
- }
-
- ApiError getLastError() {
- return m_lastError;
- }
-
- String getLastErrorMessage() {
- return m_lastErrorMessage;
- }
-
- public boolean lazyLoadEnabled() {
- return m_lazyLoadEnabled;
- }
-
- @Override
- public ArticleList loadInBackground() {
- Log.d(TAG, this + " loadInBackground append=" + m_append + " offset=" + m_offset);
-
- m_loadingInProgress = true;
-
- final int skip = getSkip(m_append);
- final boolean allowForceUpdate = 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", 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) || !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 (Application.getInstance().getApiLevel() >= 12) {
- params.put("include_header", "true");
- }
-
- Log.d(TAG, "firstId=" + m_firstId + " append=" + m_append + " skip=" + skip + " localSize=" + m_articles.size());
-
- JsonElement result = ApiCommon.performRequest(m_context, 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 (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)
- m_articles.clear();
-
- m_amountLoaded = articlesJson.size();
-
- for (Article article : articlesJson)
- if (!m_articles.containsId(article.id)) {
- article.collectMediaInfo();
- article.cleanupExcerpt();
- article.fixNullFields();
- m_articles.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=" + m_articles.size());
-
- return m_articles;
- }
-
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-
- m_loadingInProgress = false;
-
- return null;
- }
-
- private int getSkip(boolean append) {
- int skip = 0;
-
- if (append) {
- // adaptive, all_articles, marked, published, unread
- String viewMode = m_prefs.getString("view_mode", "adaptive");
-
- int numUnread = Math.toIntExact(m_articles.getUnreadCount());
- int numAll = Math.toIntExact(m_articles.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;
- }
- }
-
- return skip;
- }
-
- @Override
- public void setStatusCode(int statusCode) {
- m_apiStatusCode = statusCode;
- }
-
- @Override
- public void setLastError(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;
- }
-}
diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/HeadlinesModel.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/HeadlinesModel.java
new file mode 100644
index 00000000..08adb73b
--- /dev/null
+++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/HeadlinesModel.java
@@ -0,0 +1,317 @@
+package org.fox.ttrss;
+
+import android.app.Application;
+import android.content.Context;
+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.loader.app.LoaderManager;
+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 HeadlinesModel 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());
+
+ public HeadlinesModel(@NonNull Application application) {
+ super(application);
+
+ m_prefs = PreferenceManager.getDefaultSharedPreferences(application);
+
+ // do we need concurrency or not?
+ m_executor = Executors.newSingleThreadExecutor();
+ }
+
+ public LiveData<ArticleList> getLiveData() {
+ return m_articles;
+
+ }
+
+ public ArticleList getArticles() {
+ return 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;
+
+ forceLoad();
+ } else if (feed != m_feed || m_lazyLoadEnabled && !m_loadingInProgress) {
+ m_append = true;
+ m_feed = feed;
+ forceLoad();
+ } else {
+ ArticleList tmp = new ArticleList();
+ tmp.addAll(m_articles.getValue());
+
+ m_articles.postValue(tmp);
+ }
+ }
+
+ public void forceLoad() {
+ Log.d(TAG, "forceLoad");
+
+ m_articles.postValue(loadInBackground());
+ }
+
+ public ArticleList loadInBackground() {
+ Log.d(TAG, this + " loadInBackground append=" + m_append + " offset=" + m_offset);
+
+ ArticleList articlesWork = new ArticleList();
+ articlesWork.addAll(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.postValue(articlesWork);
+ });
+ });
+
+ m_loadingInProgress = false;
+
+ return articlesWork;
+ }
+
+ 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;
+ }
+
+}
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/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>