diff options
| author | Andrew Dolgov <fox@fakecake.org> | 2025-05-14 13:10:27 +0300 |
|---|---|---|
| committer | Andrew Dolgov <fox@fakecake.org> | 2025-05-14 13:10:27 +0300 |
| commit | 5dac45ba85a786b4a95038d11f5925ce09594898 (patch) | |
| tree | 3e6202183fd15098654bb83c8b1f9015b35847b5 /org.fox.ttrss/src | |
| parent | 3dfd1a4c9c978fd0a6f1e58a7d8fd235ed6d997a (diff) | |
wip implementation of viewmodel/executor/livedata headlines
Diffstat (limited to 'org.fox.ttrss/src')
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> |