summaryrefslogtreecommitdiff
path: root/org.fox.ttrss/src/main
diff options
context:
space:
mode:
authorAndrew Dolgov <fox@fakecake.org>2025-05-17 13:17:19 +0300
committerAndrew Dolgov <fox@fakecake.org>2025-05-17 13:17:19 +0300
commit43fed40ae3ea1bba19546c3f6fe2c0f22cd1c63c (patch)
treebaaeacda61277a263cafcf6c3089d68c3db40137 /org.fox.ttrss/src/main
parent334379d21bf9ac24bed68665bcc06b1c49d8f109 (diff)
parent6785f7021c5599d9f1f0acdfff20fe34e90b5903 (diff)
Merge branch 'protected/feeds-model' into 'master'
open drawer onBackPressed() instead of quitting if there's a feed open and... See merge request tt-rss/tt-rss-android!36
Diffstat (limited to 'org.fox.ttrss/src/main')
-rw-r--r--org.fox.ttrss/src/main/java/org/fox/ttrss/ApiCommon.java1
-rwxr-xr-xorg.fox.ttrss/src/main/java/org/fox/ttrss/ApiLoader.java84
-rwxr-xr-xorg.fox.ttrss/src/main/java/org/fox/ttrss/Application.java3
-rw-r--r--org.fox.ttrss/src/main/java/org/fox/ttrss/ArticleModel.java20
-rwxr-xr-xorg.fox.ttrss/src/main/java/org/fox/ttrss/CommonActivity.java2
-rwxr-xr-xorg.fox.ttrss/src/main/java/org/fox/ttrss/FeedsFragment.java273
-rw-r--r--org.fox.ttrss/src/main/java/org/fox/ttrss/FeedsModel.java163
-rwxr-xr-xorg.fox.ttrss/src/main/java/org/fox/ttrss/HeadlinesFragment.java27
-rwxr-xr-xorg.fox.ttrss/src/main/java/org/fox/ttrss/MasterActivity.java19
-rwxr-xr-xorg.fox.ttrss/src/main/java/org/fox/ttrss/OnlineActivity.java8
-rwxr-xr-xorg.fox.ttrss/src/main/java/org/fox/ttrss/RootCategoriesFragment.java90
-rw-r--r--org.fox.ttrss/src/main/java/org/fox/ttrss/types/Feed.java16
-rwxr-xr-xorg.fox.ttrss/src/main/res/layout/feeds_row.xml4
-rw-r--r--org.fox.ttrss/src/main/res/layout/feeds_row_divider.xml4
-rwxr-xr-xorg.fox.ttrss/src/main/res/layout/feeds_row_goback.xml4
-rwxr-xr-xorg.fox.ttrss/src/main/res/layout/feeds_row_selected.xml4
-rwxr-xr-xorg.fox.ttrss/src/main/res/layout/feeds_row_toggle.xml4
-rwxr-xr-xorg.fox.ttrss/src/main/res/layout/fragment_feeds.xml13
-rwxr-xr-xorg.fox.ttrss/src/main/res/menu/activity_main.xml134
-rw-r--r--org.fox.ttrss/src/main/res/menu/context_category.xml15
-rw-r--r--org.fox.ttrss/src/main/res/menu/context_feed.xml14
-rwxr-xr-xorg.fox.ttrss/src/main/res/xml/preferences.xml6
22 files changed, 496 insertions, 412 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 3bbf4df9..17d0c75a 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
@@ -149,6 +149,7 @@ public class ApiCommon {
OkHttpProgressGlideModule.ResponseProgressListener listener = new OkHttpProgressGlideModule.ResponseProgressListener() {
@Override
public void update(HttpUrl url, long bytesRead, long contentLength) {
+ // Log.d(TAG, "[progress] " + url + " " + bytesRead + " of " + contentLength);
if (contentLength > 0)
caller.notifyProgress((int) (bytesRead * 100f / contentLength));
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
deleted file mode 100755
index 5cba0ed1..00000000
--- a/org.fox.ttrss/src/main/java/org/fox/ttrss/ApiLoader.java
+++ /dev/null
@@ -1,84 +0,0 @@
-package org.fox.ttrss;
-
-import android.content.Context;
-
-import androidx.loader.content.AsyncTaskLoader;
-
-import com.google.gson.JsonElement;
-
-import org.fox.ttrss.ApiCommon.ApiError;
-
-import java.util.HashMap;
-
-public class ApiLoader extends AsyncTaskLoader<JsonElement> 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 final Context m_context;
- private String m_lastErrorMessage;
- private ApiError m_lastError;
- private final HashMap<String,String> m_params;
- private JsonElement m_data;
-
- ApiLoader(Context context, HashMap<String, String> params) {
- super(context);
-
- m_context = context;
- m_lastError = ApiError.UNKNOWN_ERROR;
- m_params = params;
- }
-
- @Override
- protected void onStartLoading() {
- if (m_data != null) {
- deliverResult(m_data);
- } else {
- forceLoad();
- }
- }
-
- @Override
- public void deliverResult(JsonElement data) {
- m_data = data;
-
- super.deliverResult(data);
- }
-
- public int getErrorMessage() {
- return ApiCommon.getErrorMessage(m_lastError);
- }
-
- ApiError getLastError() {
- return m_lastError;
- }
-
- String getLastErrorMessage() {
- return m_lastErrorMessage;
- }
-
- @Override
- public JsonElement loadInBackground() {
- return ApiCommon.performRequest(m_context, m_params, this);
- }
-
- @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;
- }
-
- @Override
- public void notifyProgress(int progress) { }
-}
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 75542e3c..11e32bcf 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
@@ -11,9 +11,6 @@ import java.util.HashMap;
import java.util.LinkedHashMap;
public class Application extends android.app.Application {
- public static final int LOADER_HEADLINES = 0;
- public static final int LOADER_FEEDS = 1;
- public static final int LOADER_CATS = 2;
private static Application m_singleton;
diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/ArticleModel.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/ArticleModel.java
index f4deb372..2b596828 100644
--- a/org.fox.ttrss/src/main/java/org/fox/ttrss/ArticleModel.java
+++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/ArticleModel.java
@@ -47,7 +47,7 @@ public class ArticleModel extends AndroidViewModel implements ApiCommon.ApiCalle
private int m_resizeWidth;
private boolean m_append;
private boolean m_lazyLoadEnabled = true;
- private boolean m_loadingInProgress;
+ private MutableLiveData<Boolean> m_isLoading = new MutableLiveData<>(Boolean.valueOf(false));
private ExecutorService m_executor;
private Handler m_mainHandler = new Handler(Looper.getMainLooper());
private MutableLiveData<Long> m_lastUpdate = new MutableLiveData<>(Long.valueOf(0));
@@ -91,7 +91,7 @@ public class ArticleModel extends AndroidViewModel implements ApiCommon.ApiCalle
m_feed = feed;
loadInBackground();
- } else if (feed != m_feed || m_lazyLoadEnabled && !m_loadingInProgress) {
+ } else if (feed != m_feed || m_lazyLoadEnabled || !m_isLoading.getValue()) {
m_append = true;
m_feed = feed;
@@ -106,7 +106,7 @@ public class ArticleModel extends AndroidViewModel implements ApiCommon.ApiCalle
ArticleList articlesWork = new ArticleList(m_articles.getValue());
- m_loadingInProgress = true;
+ m_isLoading.postValue(true);
final int skip = getSkip(m_append, articlesWork);
final boolean allowForceUpdate = org.fox.ttrss.Application.getInstance().getApiLevel() >= 9 &&
@@ -212,23 +212,21 @@ public class ArticleModel extends AndroidViewModel implements ApiCommon.ApiCalle
}
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_isLoading.postValue(false);
});
});
-
- m_loadingInProgress = false;
-
}
private int getSkip(boolean append, ArticleList articles) {
@@ -299,7 +297,7 @@ public class ArticleModel extends AndroidViewModel implements ApiCommon.ApiCalle
return m_offset;
}
- public boolean lazyLoadEnabled() {
+ public boolean isLazyLoadEnabled() {
return m_lazyLoadEnabled;
}
@@ -316,7 +314,11 @@ public class ArticleModel extends AndroidViewModel implements ApiCommon.ApiCalle
}
public boolean isLoading() {
- return m_loadingInProgress;
+ return m_isLoading.getValue();
+ }
+
+ public LiveData<Boolean> getIsLoading() {
+ return m_isLoading;
}
public LiveData<Integer> getLoadingProgress() {
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 d5d593ae..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
@@ -310,7 +310,7 @@ public class CommonActivity extends AppCompatActivity implements SharedPreferenc
setAppTheme(sharedPreferences);
}
- String[] filter = new String[] { "headline_mode", "widget_update_interval",
+ String[] filter = new String[] { "enable_cats", "headline_mode", "widget_update_interval",
"headlines_swipe_to_dismiss", "headlines_mark_read_scroll", "headlines_request_size",
"force_phone_layout", "open_on_startup"};
diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/FeedsFragment.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/FeedsFragment.java
index 513eaeb7..e6daed4b 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
@@ -6,9 +6,11 @@ import android.app.Dialog;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+import android.content.res.ColorStateList;
import android.graphics.Typeface;
import android.os.Bundle;
import android.util.Log;
+import android.util.TypedValue;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.LayoutInflater;
@@ -20,9 +22,9 @@ import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
+import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
-import androidx.loader.app.LoaderManager;
-import androidx.loader.content.Loader;
+import androidx.lifecycle.ViewModelProvider;
import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.DefaultItemAnimator;
import androidx.recyclerview.widget.DiffUtil;
@@ -33,26 +35,20 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.materialswitch.MaterialSwitch;
-import com.google.gson.Gson;
-import com.google.gson.JsonArray;
-import com.google.gson.JsonElement;
-import com.google.gson.reflect.TypeToken;
+import com.google.android.material.progressindicator.LinearProgressIndicator;
import org.fox.ttrss.types.Feed;
-import java.lang.reflect.Type;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
-import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
-public class FeedsFragment extends Fragment implements OnSharedPreferenceChangeListener,
- LoaderManager.LoaderCallbacks<JsonElement> {
+public class FeedsFragment extends Fragment implements OnSharedPreferenceChangeListener {
private final String TAG = this.getClass().getSimpleName();
protected SharedPreferences m_prefs;
protected MasterActivity m_activity;
@@ -63,6 +59,7 @@ public class FeedsFragment extends Fragment implements OnSharedPreferenceChangeL
protected FeedsAdapter m_adapter;
private RecyclerView m_list;
private RecyclerView.LayoutManager m_layoutManager;
+ private LinearProgressIndicator m_loadingProgress;
public void initialize(@NonNull Feed rootFeed, boolean enableParentBtn) {
Log.d(TAG, "initialize, feed=" + rootFeed);
@@ -71,88 +68,6 @@ public class FeedsFragment extends Fragment implements OnSharedPreferenceChangeL
m_enableParentBtn = enableParentBtn;
}
- @NonNull
- @Override
- public Loader<JsonElement> onCreateLoader(int id, Bundle args) {
-
- if (m_swipeLayout != null)
- m_swipeLayout.setRefreshing(true);
-
- HashMap<String,String> params = new HashMap<>();
- params.put("op", "getFeeds");
- params.put("sid", m_activity.getSessionId());
- params.put("include_nested", "true");
- params.put("cat_id", String.valueOf(m_rootFeed.id));
-
- return new ApiLoader(getContext(), params);
- }
-
- @Override
- public void onLoadFinished(@NonNull Loader<JsonElement> loader, JsonElement result) {
- if (m_swipeLayout != null) m_swipeLayout.setRefreshing(false);
-
- if (result != null) {
- try {
- JsonArray content = result.getAsJsonArray();
- if (content != null) {
-
- Type listType = new TypeToken<List<Feed>>() {}.getType();
- List<Feed> feedsJson = new Gson().fromJson(content, listType);
- List<Feed> feeds = new ArrayList<>();
-
- if (m_activity.getUnreadOnly() && m_rootFeed.id != Feed.CAT_SPECIAL)
- feedsJson = feedsJson.stream()
- .filter(f -> f.unread > 0)
- .collect(Collectors.toList());
-
- sortFeeds(feedsJson, m_rootFeed);
-
- if (m_enableParentBtn) {
- feeds.add(0, new Feed(Feed.TYPE_GOBACK));
-
- if (m_rootFeed.id >= 0 && !feedsJson.isEmpty()) {
- Feed feed = new Feed(m_rootFeed.id, m_rootFeed.title, true);
-
- feed.unread = feedsJson.stream().map(a -> a.unread).reduce(0, Integer::sum);
- feed.always_open_headlines = true;
-
- feeds.add(1, feed);
- }
- }
-
- feeds.addAll(feedsJson);
-
- feeds.add(new Feed(Feed.TYPE_DIVIDER));
- feeds.add(new Feed(Feed.TYPE_TOGGLE_UNREAD, getString(R.string.unread_only), true));
-
- m_adapter.submitList(feeds);
-
- return;
- }
-
- } catch (Exception e) {
- m_activity.toast(e.getMessage());
- }
- }
-
- ApiLoader apiLoader = (ApiLoader) loader;
-
- if (apiLoader.getLastError() != null && apiLoader.getLastError() != ApiCommon.ApiError.SUCCESS) {
- if (apiLoader.getLastError() == ApiCommon.ApiError.LOGIN_FAILED) {
- m_activity.login(true);
- } else {
- if (apiLoader.getLastErrorMessage() != null) {
- m_activity.toast(getString(apiLoader.getErrorMessage()) + "\n" + apiLoader.getLastErrorMessage());
- } else {
- m_activity.toast(apiLoader.getErrorMessage());
- }
- }
- }
- }
-
- @Override
- public void onLoaderReset(Loader<JsonElement> loader) { }
-
@SuppressLint("DefaultLocale")
static class FeedUnreadComparator implements Comparator<Feed> {
@@ -165,7 +80,6 @@ public class FeedsFragment extends Fragment implements OnSharedPreferenceChangeL
}
}
-
@SuppressLint("DefaultLocale")
static class FeedTitleComparator implements Comparator<Feed> {
@@ -230,39 +144,47 @@ public class FeedsFragment extends Fragment implements OnSharedPreferenceChangeL
AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) item
.getMenuInfo();
- final Feed feed = m_adapter.getCurrentList().get(info.position);
-
- Log.d(TAG, "context for feed=" + feed.id);
-
- int itemId = item.getItemId();
- if (itemId == R.id.browse_headlines) {
- Feed tmpFeed = new Feed(feed);
+ // all onContextItemSelected are invoked in sequence so we might get a context menu for headlines, etc
+ try {
+ if (info != null) {
+ final Feed feed = m_adapter.getCurrentList().get(info.position);
- if (!neverOpenHeadlines(feed))
- tmpFeed.always_open_headlines = true;
+ Log.d(TAG, "context for feed=" + feed.id);
- m_activity.onFeedSelected(tmpFeed);
- return true;
- } else if (itemId == R.id.browse_feeds) {
- m_activity.onFeedSelected(feed);
- return true;
- } else if (itemId == R.id.unsubscribe_feed) {
- MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(getContext())
- .setMessage(getString(R.string.unsubscribe_from_prompt, feed.title))
- .setPositiveButton(R.string.unsubscribe,
- (dialog, which) -> m_activity.unsubscribeFeed(feed))
- .setNegativeButton(R.string.dialog_cancel,
- (dialog, which) -> {
+ int itemId = item.getItemId();
+ if (itemId == R.id.feed_browse_headlines) {
+ Feed tmpFeed = new Feed(feed);
- });
+ if (!neverOpenHeadlines(feed))
+ tmpFeed.always_open_headlines = true;
- Dialog dlg = builder.create();
- dlg.show();
+ m_activity.onFeedSelected(tmpFeed);
+ return true;
+ } else if (itemId == R.id.feed_browse_feeds) {
+ m_activity.onFeedSelected(feed);
+ return true;
+ } else if (itemId == R.id.feed_unsubscribe) {
+ MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(getContext())
+ .setMessage(getString(R.string.unsubscribe_from_prompt, feed.title))
+ .setPositiveButton(R.string.unsubscribe,
+ (dialog, which) -> m_activity.unsubscribeFeed(feed))
+ .setNegativeButton(R.string.dialog_cancel,
+ (dialog, which) -> {
+
+ });
+
+ Dialog dlg = builder.create();
+ dlg.show();
+
+ return true;
+ } else if (itemId == R.id.feed_catchup) {
+ m_activity.catchupDialog(feed);
+ return true;
+ }
+ }
- return true;
- } else if (itemId == R.id.catchup_feed) {
- m_activity.catchupDialog(feed);
- return true;
+ } catch (IndexOutOfBoundsException e) {
+ e.printStackTrace();
}
Log.d(TAG, "onContextItemSelected, unhandled id=" + item.getItemId());
@@ -282,13 +204,13 @@ public class FeedsFragment extends Fragment implements OnSharedPreferenceChangeL
menu.setHeaderTitle(feed.title);
if (!feed.is_cat)
- menu.findItem(R.id.browse_feeds).setVisible(false);
+ menu.findItem(R.id.feed_browse_feeds).setVisible(false);
if (neverOpenHeadlines(feed))
- menu.findItem(R.id.browse_headlines).setVisible(false);
+ menu.findItem(R.id.feed_browse_headlines).setVisible(false);
if (feed.id <= 0 || feed.is_cat)
- menu.findItem(R.id.unsubscribe_feed).setVisible(false);
+ menu.findItem(R.id.feed_unsubscribe).setVisible(false);
super.onCreateContextMenu(menu, v, menuInfo);
@@ -323,6 +245,8 @@ public class FeedsFragment extends Fragment implements OnSharedPreferenceChangeL
m_swipeLayout.setOnRefreshListener(this::refresh);
+ m_loadingProgress = view.findViewById(R.id.loading_progress);
+
m_list = view.findViewById(R.id.feeds);
registerForContextMenu(m_list);
@@ -360,9 +284,93 @@ public class FeedsFragment extends Fragment implements OnSharedPreferenceChangeL
});
}
+ FeedsModel model = new ViewModelProvider(this).get(FeedsModel.class);
+
+ model.getUpdatesData().observe(m_activity, lastUpdate -> {
+ Log.d(TAG, "observed update=" + lastUpdate);
+ });
+
+ model.getLoadingProgress().observe(m_activity, progress -> {
+ Log.d(TAG, "observed feeds loading progress=" + progress);
+
+ if (isAdded() && m_loadingProgress != null) {
+ m_loadingProgress.setVisibility(progress < 100 ? View.VISIBLE : View.GONE);
+ m_loadingProgress.setProgress(progress);
+ }
+ });
+
+ model.getIsLoading().observe(m_activity, isLoading -> {
+ Log.d(TAG, "observed isLoading=" + isLoading);
+
+ if (isAdded()) {
+ if (m_swipeLayout != null)
+ m_swipeLayout.setRefreshing(isLoading);
+
+ if (m_loadingProgress != null && !isLoading)
+ m_loadingProgress.setVisibility(View.GONE);
+ }
+ });
+
+ model.getFeeds().observe(getActivity(), feeds -> {
+ Log.d(TAG, "observed feeds size=" + feeds.size());
+
+ if (isAdded()) {
+ onFeedsLoaded(feeds);
+
+ if (model.getLastError() != null && model.getLastError() != ApiCommon.ApiError.SUCCESS) {
+ if (model.getLastError() == ApiCommon.ApiError.LOGIN_FAILED) {
+ m_activity.login(true);
+ } else {
+ if (model.getLastErrorMessage() != null) {
+ m_activity.toast(getString(model.getErrorMessage()) + "\n" + model.getLastErrorMessage());
+ } else {
+ m_activity.toast(model.getErrorMessage());
+ }
+ }
+ }
+ }
+ });
+
return view;
}
+ protected void onFeedsLoaded(List<Feed> loadedFeeds) {
+ List<Feed> feedsWork = new ArrayList<>();
+
+ if (m_activity.getUnreadOnly() && m_rootFeed.id != Feed.CAT_SPECIAL)
+ loadedFeeds = loadedFeeds.stream()
+ .filter(f -> f.unread > 0)
+ .collect(Collectors.toList());
+
+ sortFeeds(loadedFeeds, m_rootFeed);
+
+ if (m_enableParentBtn) {
+ feedsWork.add(0, new Feed(Feed.TYPE_GOBACK));
+
+ if (m_rootFeed.id >= 0 && !loadedFeeds.isEmpty()) {
+ Feed feed = new Feed(m_rootFeed.id, m_rootFeed.title, true);
+
+ feed.unread = loadedFeeds.stream().map(a -> a.unread).reduce(0, Integer::sum);
+ feed.always_open_headlines = true;
+
+ feedsWork.add(1, feed);
+ }
+ } else if (m_rootFeed.id == Feed.ALL_ARTICLES) {
+ // if all articles feed is requested as a root element (no parent button) let's filter
+ // labels out so this is at least somewhat readable, instead we'll insert a link to this category to the top
+ loadedFeeds = loadedFeeds.stream().filter(a -> a.id >= -10).collect(Collectors.toList());
+
+ loadedFeeds.add(0, new Feed(Feed.CAT_LABELS, getString(R.string.cat_labels), true));
+ }
+
+ feedsWork.addAll(loadedFeeds);
+
+ feedsWork.add(new Feed(Feed.TYPE_DIVIDER));
+ feedsWork.add(new Feed(Feed.TYPE_TOGGLE_UNREAD, getString(R.string.unread_only), true));
+
+ m_adapter.submitList(feedsWork);
+ }
+
@Override
public void onDestroy() {
super.onDestroy();
@@ -395,11 +403,8 @@ public class FeedsFragment extends Fragment implements OnSharedPreferenceChangeL
if (!isAdded())
return;
- if (m_swipeLayout != null) {
- m_swipeLayout.setRefreshing(true);
- }
-
- LoaderManager.getInstance(this).restartLoader(Application.LOADER_FEEDS, null, this).forceLoad();
+ FeedsModel model = new ViewModelProvider(this).get(FeedsModel.class);
+ model.startLoading(m_rootFeed, false);
}
private class FeedViewHolder extends RecyclerView.ViewHolder {
@@ -433,7 +438,9 @@ public class FeedsFragment extends Fragment implements OnSharedPreferenceChangeL
return oldItem.id == newItem.id &&
oldItem.is_cat == newItem.is_cat &&
oldItem.title.equals(newItem.title) &&
- oldItem.unread == newItem.unread;
+ oldItem.unread == newItem.unread &&
+ oldItem.update_interval != newItem.update_interval &&
+ oldItem.last_error.equals(newItem.last_error);
}
}
@@ -488,6 +495,12 @@ public class FeedsFragment extends Fragment implements OnSharedPreferenceChangeL
holder.title.setTypeface(null, Typeface.NORMAL);
}
+ TypedValue tv = new TypedValue();
+ m_activity.getTheme().resolveAttribute(feed.last_error.isEmpty() ? R.attr.colorOnSurface : R.attr.colorError, tv, true);
+
+ holder.title.setTextColor(ColorStateList.valueOf(ContextCompat.getColor(m_activity, tv.resourceId)));
+
+ holder.title.setAlpha(feed.update_interval == -1 ? 0.5f : 1f);
}
if (holder.unreadCounter != null) {
diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/FeedsModel.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/FeedsModel.java
new file mode 100644
index 00000000..171fb29a
--- /dev/null
+++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/FeedsModel.java
@@ -0,0 +1,163 @@
+package org.fox.ttrss;
+
+import android.app.Application;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.AndroidViewModel;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.reflect.TypeToken;
+
+import org.fox.ttrss.types.ArticleList;
+import org.fox.ttrss.types.Feed;
+
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.stream.Collectors;
+
+public class FeedsModel extends AndroidViewModel implements ApiCommon.ApiCaller {
+ private final String TAG = this.getClass().getSimpleName();
+ private MutableLiveData<List<Feed>> m_feeds = new MutableLiveData<>(new ArrayList<>());
+ private MutableLiveData<Integer> m_loadingProgress = new MutableLiveData<>(Integer.valueOf(0));
+ private MutableLiveData<Long> m_lastUpdate = new MutableLiveData<>(Long.valueOf(0));
+ private MutableLiveData<Boolean> m_isLoading = new MutableLiveData<>(Boolean.valueOf(false));
+
+ private Feed m_feed;
+
+ private ExecutorService m_executor;
+ private Handler m_mainHandler = new Handler(Looper.getMainLooper());
+
+ 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 boolean m_rootMode;
+
+ public FeedsModel(@NonNull Application application) {
+ super(application);
+
+ // do we need concurrency or not?
+ m_executor = Executors.newSingleThreadExecutor();
+
+ Log.d(TAG, this + " created");
+ }
+
+ @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 void startLoading(Feed feed, boolean rootMode) {
+ Log.d(TAG, "startLoading feed id=" + feed.id + " cat=" + feed.is_cat);
+
+ m_feed = feed;
+ m_rootMode = rootMode;
+
+ loadInBackground();
+ }
+
+ @Override
+ public void notifyProgress(int progress) {
+ m_loadingProgress.postValue(progress);
+ }
+
+ protected HashMap<String,String> constructParams() {
+ HashMap<String,String> params = new HashMap<>();
+
+ if (m_rootMode) {
+ params.put("op", "getCategories");
+
+ // this confusingly named option means "return top level categories only"
+ params.put("enable_nested", "true");
+ } else {
+ params.put("op", "getFeeds");
+ params.put("cat_id", String.valueOf(m_feed.id));
+ params.put("include_nested", "true");
+ }
+
+ params.put("sid", ((org.fox.ttrss.Application)getApplication()).getSessionId());
+
+ return params;
+ }
+
+ private void loadInBackground() {
+ Log.d(TAG, this + " loadInBackground");
+
+ m_isLoading.postValue(true);
+
+ HashMap<String,String> params = constructParams();
+
+ m_executor.execute(() -> {
+ JsonElement result = ApiCommon.performRequest(getApplication(), params, this);
+
+ Log.d(TAG, "got result=" + result);
+
+ try {
+ JsonArray content = result.getAsJsonArray();
+ if (content != null) {
+
+ Type listType = new TypeToken<List<Feed>>() {}.getType();
+
+ List<Feed> feeds = new Gson().fromJson(content, listType);
+
+ feeds = feeds.stream().peek(Feed::fixNullFields).collect(Collectors.toList());
+
+ m_feeds.postValue(feeds);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ m_isLoading.postValue(false);
+ });
+ }
+
+ public LiveData<Integer> getLoadingProgress() {
+ return m_loadingProgress;
+ }
+
+ public LiveData<Long> getUpdatesData() {
+ return m_lastUpdate;
+ }
+
+ public LiveData<Boolean> getIsLoading() { return m_isLoading; }
+
+ public LiveData<List<Feed>> getFeeds() {
+ return m_feeds;
+ }
+
+ public int getErrorMessage() {
+ return ApiCommon.getErrorMessage(m_lastError);
+ }
+
+ ApiCommon.ApiError getLastError() {
+ return m_lastError;
+ }
+
+ String getLastErrorMessage() {
+ return m_lastErrorMessage;
+ }
+
+} \ No newline at end of file
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 099afc60..8c24a14a 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
@@ -217,12 +217,12 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment {
}
}
+ // all onContextItemSelected are invoked in sequence so we might get a context menu for headlines, etc
public boolean onContextItemSelected(MenuItem item) {
AdapterContextMenuInfo info = (AdapterContextMenuInfo) item
.getMenuInfo();
if (info != null) {
-
try {
Article article = Application.getArticles().get(info.position);
@@ -233,6 +233,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment {
}
}
+ Log.d(TAG, "onContextItemSelected, unhandled id=" + item.getItemId());
return super.onContextItemSelected(item);
}
@@ -459,7 +460,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment {
ArticleModel model = Application.getArticlesModel();
- if (dy > 0 && !m_isLazyLoading && !model.isLoading() && model.lazyLoadEnabled() &&
+ if (dy > 0 && !m_isLazyLoading && !model.isLoading() && model.isLazyLoadEnabled() &&
lastVisibleItem >= Application.getArticles().size() - 5) {
Log.d(TAG, "attempting to lazy load more articles...");
@@ -474,8 +475,17 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment {
ArticleModel model = Application.getArticlesModel();
+ model.getIsLoading().observe(getActivity(), isLoading -> {
+ Log.d(TAG, "observed headlines isLoading=" + isLoading);
+
+ if (m_swipeLayout != null)
+ m_swipeLayout.setRefreshing(isLoading);
+ });
+
// this gets notified on loading %
model.getLoadingProgress().observe(getActivity(), progress -> {
+ Log.d(TAG, "observed headlines loading progress=" + progress);
+
m_listener.onHeadlinesLoadingProgress(progress);
});
@@ -484,7 +494,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment {
if (lastUpdate > 0) {
ArticleList tmp = new ArticleList(model.getArticles().getValue());
- Log.d(TAG, "observed last update=" + lastUpdate + " article count=" + tmp.size());
+ Log.d(TAG, "observed headlines last update=" + lastUpdate + " article count=" + tmp.size());
if (m_prefs.getBoolean("headlines_mark_read_scroll", false))
tmp.add(new Article(Article.TYPE_AMR_FOOTER));
@@ -495,9 +505,6 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment {
if (!appended)
m_list.scrollToPosition(0);
- if (m_swipeLayout != null)
- m_swipeLayout.setRefreshing(false);
-
m_isLazyLoading = false;
m_listener.onHeadlinesLoaded(appended);
@@ -510,9 +517,6 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment {
if (model.getLastError() != null && model.getLastError() != ApiCommon.ApiError.SUCCESS) {
- if (m_swipeLayout != null)
- m_swipeLayout.setRefreshing(false);
-
m_isLazyLoading = false;
if (model.getLastError() == ApiCommon.ApiError.LOGIN_FAILED) {
@@ -533,7 +537,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment {
// loaded articles might get modified for all sorts of reasons
model.getArticles().observe(getActivity(), articles -> {
- Log.d(TAG, "observed article list size=" + articles.size());
+ Log.d(TAG, "observed headlines article list size=" + articles.size());
ArticleList tmp = new ArticleList(articles);
@@ -583,9 +587,6 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment {
if (!append)
m_activeArticleId = -1;
- if (m_swipeLayout != null)
- m_swipeLayout.setRefreshing(true);
-
model.setSearchQuery(getSearchQuery());
model.startLoading(append, m_feed, m_activity.getResizeWidth());
}
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 643f859c..f2ef3aa0 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
@@ -159,12 +159,23 @@ public class MasterActivity extends OnlineActivity implements HeadlinesEventList
}
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
- RootCategoriesFragment fc = new RootCategoriesFragment();
+ FeedsFragment fc;
+
+ if (m_prefs.getBoolean("enable_cats", true)) {
+ fc = new RootCategoriesFragment();
+ // it doesn't matter which feed is used here
+ fc.initialize(new Feed(Feed.CAT_SPECIAL, getString(R.string.cat_special), true), false);
+ } else {
+ fc = new FeedsFragment();
+ fc.initialize(new Feed(Feed.ALL_ARTICLES, getString(R.string.feed_all_articles), true), false);
+ }
- // it doesn't matter which feed is used here
- fc.initialize(new Feed(-1, getString(R.string.cat_special), true), false);
ft.replace(R.id.feeds_fragment, fc, FRAG_FEEDS);
+ /* FeedsFragment ff = new FeedsFragment();
+ ff.initialize(new Feed(12, "Technology", true), true);
+ ft.replace(R.id.feeds_fragment, ff, FRAG_FEEDS); */
+
// allow overriding feed to open on startup in non-shortcut mode, default to
// open_on_startup prefs setting and not-category
@@ -364,7 +375,7 @@ public class MasterActivity extends OnlineActivity implements HeadlinesEventList
@Override
public void onBackPressed() {
if (m_drawerLayout != null && !m_drawerLayout.isDrawerOpen(GravityCompat.START) &&
- (getSupportFragmentManager().getBackStackEntryCount() > 0)) {
+ (getSupportFragmentManager().getBackStackEntryCount() > 0 || m_activeFeed != null)) {
m_drawerLayout.openDrawer(GravityCompat.START);
} else {
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 cbbf1188..467b0bfd 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
@@ -1165,10 +1165,14 @@ article.score = Integer.parseInt(edit.getText().toString());
protected void initMenu() {
if (m_menu != null) {
if (getSessionId() != null) {
- m_menu.setGroupVisible(R.id.menu_group_logged_in, true);
+ m_menu.setGroupVisible(R.id.menu_group_feeds, true);
+ m_menu.setGroupVisible(R.id.menu_group_headlines, true);
+ m_menu.setGroupVisible(R.id.menu_group_article, true);
m_menu.setGroupVisible(R.id.menu_group_logged_out, false);
} else {
- m_menu.setGroupVisible(R.id.menu_group_logged_in, false);
+ m_menu.setGroupVisible(R.id.menu_group_feeds, false);
+ m_menu.setGroupVisible(R.id.menu_group_headlines, false);
+ m_menu.setGroupVisible(R.id.menu_group_article, false);
m_menu.setGroupVisible(R.id.menu_group_logged_out, true);
}
diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/RootCategoriesFragment.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/RootCategoriesFragment.java
index b61a2736..55c23226 100755
--- a/org.fox.ttrss/src/main/java/org/fox/ttrss/RootCategoriesFragment.java
+++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/RootCategoriesFragment.java
@@ -4,6 +4,7 @@ import android.annotation.SuppressLint;
import android.os.Bundle;
import androidx.annotation.NonNull;
+import androidx.lifecycle.ViewModelProvider;
import androidx.loader.content.Loader;
import com.google.gson.Gson;
@@ -23,19 +24,6 @@ import java.util.stream.Collectors;
public class RootCategoriesFragment extends FeedsFragment {
private final String TAG = this.getClass().getSimpleName();
- @Override
- @NonNull
- public Loader<JsonElement> onCreateLoader(int id, Bundle args) {
- HashMap<String, String> params = new HashMap<>();
- params.put("op", "getCategories");
- params.put("sid", m_activity.getSessionId());
-
- // this confusingly named option means "return top level categories only"
- params.put("enable_nested", "true");
-
- return new ApiLoader(getContext(), params);
- }
-
@SuppressLint("DefaultLocale")
static class CatOrderComparator implements Comparator<Feed> {
@@ -74,65 +62,43 @@ public class RootCategoriesFragment extends FeedsFragment {
}
@Override
- public void onLoadFinished(@NonNull Loader<JsonElement> loader, JsonElement result) {
- if (m_swipeLayout != null) m_swipeLayout.setRefreshing(false);
-
- if (result != null) {
- try {
- JsonArray content = result.getAsJsonArray();
- if (content != null) {
-
- Type listType = new TypeToken<List<Feed>>() {}.getType();
- List<Feed> feedsJson = new Gson().fromJson(content, listType);
+ public void refresh() {
+ if (!isAdded())
+ return;
- List<Feed> feeds = new ArrayList<>();
-
- sortFeeds(feedsJson, m_rootFeed);
+ FeedsModel model = new ViewModelProvider(this).get(FeedsModel.class);
+ model.startLoading(m_rootFeed, true);
+ }
- // virtual cats implemented in getCategories since api level 1
- if (m_activity.getApiLevel() == 0) {
- feeds.add(0, new Feed(-2, getString(R.string.cat_labels), true));
- feeds.add(1, new Feed(-1, getString(R.string.cat_special), true));
- feeds.add(new Feed(0, getString(R.string.cat_uncategorized), true));
- }
+ @Override
+ protected void onFeedsLoaded(List<Feed> loadedFeeds) {
+ List<Feed> feedsWork = new ArrayList<>();
- if (m_activity.getUnreadOnly())
- feedsJson = feedsJson.stream()
- .filter(f -> f.id == Feed.CAT_SPECIAL || f.unread > 0)
- .collect(Collectors.toList());
+ sortFeeds(loadedFeeds, m_rootFeed);
- feedsJson = feedsJson.stream()
- .peek(f -> f.is_cat = true)
- .collect(Collectors.toList());
+ // virtual cats implemented in getCategories since api level 1
+ if (m_activity.getApiLevel() == 0) {
+ feedsWork.add(0, new Feed(-2, getString(R.string.cat_labels), true));
+ feedsWork.add(1, new Feed(-1, getString(R.string.cat_special), true));
+ feedsWork.add(new Feed(0, getString(R.string.cat_uncategorized), true));
+ }
- feeds.addAll(feedsJson);
+ if (m_activity.getUnreadOnly())
+ loadedFeeds = loadedFeeds.stream()
+ .filter(f -> f.id == Feed.CAT_SPECIAL || f.unread > 0)
+ .collect(Collectors.toList());
- feeds.add(new Feed(Feed.TYPE_DIVIDER));
- feeds.add(new Feed(Feed.TYPE_TOGGLE_UNREAD, getString(R.string.unread_only), true));
+ loadedFeeds = loadedFeeds.stream()
+ .peek(f -> f.is_cat = true)
+ .collect(Collectors.toList());
- m_adapter.submitList(feeds);
+ feedsWork.addAll(loadedFeeds);
- return;
- }
+ feedsWork.add(new Feed(Feed.TYPE_DIVIDER));
+ feedsWork.add(new Feed(Feed.TYPE_TOGGLE_UNREAD, getString(R.string.unread_only), true));
- } catch (Exception e) {
- m_activity.toast(e.getMessage());
- }
- }
+ m_adapter.submitList(feedsWork);
- ApiLoader apiLoader = (ApiLoader) loader;
-
- if (apiLoader.getLastError() != null && apiLoader.getLastError() != ApiCommon.ApiError.SUCCESS) {
- if (apiLoader.getLastError() == ApiCommon.ApiError.LOGIN_FAILED) {
- m_activity.login(true);
- } else {
- if (apiLoader.getLastErrorMessage() != null) {
- m_activity.toast(getString(apiLoader.getErrorMessage()) + "\n" + apiLoader.getLastErrorMessage());
- } else {
- m_activity.toast(apiLoader.getErrorMessage());
- }
- }
- }
}
}
diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/types/Feed.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/types/Feed.java
index f8a2be8f..c1ec027a 100644
--- a/org.fox.ttrss/src/main/java/org/fox/ttrss/types/Feed.java
+++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/types/Feed.java
@@ -18,19 +18,23 @@ public class Feed implements Comparable<Feed>, Parcelable {
public int cat_id;
public int last_updated;
public int order_id;
+ public String last_error;
public boolean is_cat;
+ public int update_interval;
transient public boolean always_open_headlines;
public Feed(int id) {
this.id = id;
this.title = "ID:" + id;
this.is_cat = false;
+ this.last_error = "";
}
public Feed(int id, String title, boolean is_cat) {
this.id = id;
this.title = title;
this.is_cat = is_cat;
+ this.last_error = "";
}
public Feed(Feed feed) {
@@ -44,6 +48,8 @@ public class Feed implements Comparable<Feed>, Parcelable {
order_id = feed.order_id;
is_cat = feed.is_cat;
always_open_headlines = feed.always_open_headlines;
+ last_error = feed.last_error;
+ update_interval = feed.update_interval;
}
public static final int MARKED = -1;
@@ -116,6 +122,12 @@ public class Feed implements Comparable<Feed>, Parcelable {
return this.title.compareTo(feed.title);
}
+ public void fixNullFields() {
+ if (feed_url == null) feed_url = "";
+ if (title == null) title = "";
+ if (last_error == null) last_error = "";
+ }
+
@Override
public int describeContents() {
return 0;
@@ -133,6 +145,8 @@ public class Feed implements Comparable<Feed>, Parcelable {
out.writeInt(is_cat ? 1 : 0);
out.writeInt(order_id);
out.writeInt(always_open_headlines ? 1 : 0);
+ out.writeString(last_error);
+ out.writeInt(update_interval);
}
public void readFromParcel(Parcel in) {
@@ -146,6 +160,8 @@ public class Feed implements Comparable<Feed>, Parcelable {
is_cat = in.readInt() == 1;
order_id = in.readInt();
always_open_headlines = in.readInt() == 1;
+ last_error = in.readString();
+ update_interval = in.readInt();
}
@SuppressWarnings("rawtypes")
diff --git a/org.fox.ttrss/src/main/res/layout/feeds_row.xml b/org.fox.ttrss/src/main/res/layout/feeds_row.xml
index 0f9c4e54..ed0f2354 100755
--- a/org.fox.ttrss/src/main/res/layout/feeds_row.xml
+++ b/org.fox.ttrss/src/main/res/layout/feeds_row.xml
@@ -7,8 +7,8 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:descendantFocusability="blocksDescendants"
- android:paddingLeft="16dp"
- android:paddingRight="16dp"
+ android:paddingLeft="8dp"
+ android:paddingRight="8dp"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:baselineAligned="true"
diff --git a/org.fox.ttrss/src/main/res/layout/feeds_row_divider.xml b/org.fox.ttrss/src/main/res/layout/feeds_row_divider.xml
index 55aa5fdc..4362dcd2 100644
--- a/org.fox.ttrss/src/main/res/layout/feeds_row_divider.xml
+++ b/org.fox.ttrss/src/main/res/layout/feeds_row_divider.xml
@@ -7,8 +7,8 @@
<com.google.android.material.divider.MaterialDivider
android:layout_width="match_parent"
- app:dividerInsetEnd="16dp"
- app:dividerInsetStart="16dp"
+ app:dividerInsetEnd="8dp"
+ app:dividerInsetStart="8dp"
android:layout_marginTop="8dp"
android:layout_height="wrap_content"/>
diff --git a/org.fox.ttrss/src/main/res/layout/feeds_row_goback.xml b/org.fox.ttrss/src/main/res/layout/feeds_row_goback.xml
index 75ba8fe1..84f387ff 100755
--- a/org.fox.ttrss/src/main/res/layout/feeds_row_goback.xml
+++ b/org.fox.ttrss/src/main/res/layout/feeds_row_goback.xml
@@ -5,8 +5,8 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:descendantFocusability="blocksDescendants"
- android:paddingLeft="16dp"
- android:paddingRight="16dp"
+ android:paddingLeft="8dp"
+ android:paddingRight="8dp"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:baselineAligned="true"
diff --git a/org.fox.ttrss/src/main/res/layout/feeds_row_selected.xml b/org.fox.ttrss/src/main/res/layout/feeds_row_selected.xml
index 031c2116..ab280e7f 100755
--- a/org.fox.ttrss/src/main/res/layout/feeds_row_selected.xml
+++ b/org.fox.ttrss/src/main/res/layout/feeds_row_selected.xml
@@ -13,8 +13,8 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:descendantFocusability="blocksDescendants"
- android:paddingLeft="16dp"
- android:paddingRight="16dp"
+ android:paddingLeft="8dp"
+ android:paddingRight="8dp"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:baselineAligned="true"
diff --git a/org.fox.ttrss/src/main/res/layout/feeds_row_toggle.xml b/org.fox.ttrss/src/main/res/layout/feeds_row_toggle.xml
index ad76bca5..dda0f02c 100755
--- a/org.fox.ttrss/src/main/res/layout/feeds_row_toggle.xml
+++ b/org.fox.ttrss/src/main/res/layout/feeds_row_toggle.xml
@@ -6,8 +6,8 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:descendantFocusability="blocksDescendants"
- android:paddingLeft="16dp"
- android:paddingRight="16dp"
+ android:paddingLeft="8dp"
+ android:paddingRight="8dp"
android:baselineAligned="true"
android:gravity="center_vertical"
android:orientation="horizontal">
diff --git a/org.fox.ttrss/src/main/res/layout/fragment_feeds.xml b/org.fox.ttrss/src/main/res/layout/fragment_feeds.xml
index 9f57dda2..3fe12847 100755
--- a/org.fox.ttrss/src/main/res/layout/fragment_feeds.xml
+++ b/org.fox.ttrss/src/main/res/layout/fragment_feeds.xml
@@ -60,6 +60,16 @@
android:textAppearance="?attr/textAppearanceTitleSmall"
android:textColor="?attr/colorOnSurfaceVariant"/>
</LinearLayout>
+
+ <com.google.android.material.progressindicator.LinearProgressIndicator
+ android:id="@+id/loading_progress"
+ android:max="100"
+ android:progress="50"
+ android:visibility="gone"
+ android:indeterminate="false"
+ android:layout_gravity="bottom"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
</FrameLayout>
</com.google.android.material.appbar.AppBarLayout>
@@ -67,13 +77,14 @@
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/feeds_swipe_container"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
+ android:layout_marginStart="8dp"
+ android:layout_marginEnd="8dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<org.fox.ttrss.util.ContextMenuRecyclerView
android:id="@+id/feeds"
android:scrollbars="vertical"
-
android:layout_width="match_parent"
android:layout_height="wrap_content" />
diff --git a/org.fox.ttrss/src/main/res/menu/activity_main.xml b/org.fox.ttrss/src/main/res/menu/activity_main.xml
index 6f3198a2..eeb97a22 100755
--- a/org.fox.ttrss/src/main/res/menu/activity_main.xml
+++ b/org.fox.ttrss/src/main/res/menu/activity_main.xml
@@ -1,78 +1,74 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto">
+ <group android:id="@+id/menu_group_feeds" >
+ <item
+ android:id="@+id/subscribe_to_feed"
+ android:orderInCategory="1"
+ app:showAsAction=""
+ android:title="@string/subscribe_to_feed"/> <!-- iRroom -->
+ </group>
+ <group android:id="@+id/menu_group_headlines" >
+ <item
+ android:id="@+id/search"
+ android:icon="@drawable/baseline_search_24"
+ app:iconTint="?attr/colorControlNormal"
+ app:showAsAction="ifRoom"
+ android:title="@string/search"/>
+ <item
+ android:id="@+id/headlines_mark_as_read"
+ app:showAsAction=""
+ app:iconTint="?attr/colorControlNormal"
+ android:icon="@drawable/baseline_mark_email_read_24"
+ android:title="@string/headlines_mark_as_read"/>
+ <item
+ android:id="@+id/headlines_select"
+ app:showAsAction="ifRoom"
+ app:iconTint="?attr/colorControlNormal"
+ android:icon="@drawable/baseline_select_all_24"
+ android:title="@string/headlines_select"/>
- <group android:id="@+id/menu_group_logged_in" >
- <group android:id="@+id/menu_group_feeds" >
-
- <item
- android:id="@+id/subscribe_to_feed"
- android:orderInCategory="1"
- app:showAsAction=""
- android:title="@string/subscribe_to_feed"/> <!-- iRroom -->
- </group>
- <group android:id="@+id/menu_group_headlines" >
- <item
- android:id="@+id/search"
- android:icon="@drawable/baseline_search_24"
- app:iconTint="?attr/colorControlNormal"
- app:showAsAction="ifRoom"
- android:title="@string/search"/>
- <item
- android:id="@+id/headlines_mark_as_read"
- app:showAsAction=""
- app:iconTint="?attr/colorControlNormal"
- android:icon="@drawable/baseline_mark_email_read_24"
- android:title="@string/headlines_mark_as_read"/>
- <item
- android:id="@+id/headlines_select"
- app:showAsAction="ifRoom"
- app:iconTint="?attr/colorControlNormal"
- android:icon="@drawable/baseline_select_all_24"
- android:title="@string/headlines_select"/>
-
- <item
- android:id="@+id/headlines_toggle_sort_order"
- app:iconTint="?attr/colorControlNormal"
- android:icon="@drawable/baseline_sort_by_alpha_24"
- app:showAsAction="ifRoom"
- android:title="@string/toggle_sort_order"/>
-
- <item
- android:id="@+id/headlines_view_mode"
- app:showAsAction=""
- android:title="@string/headlines_view_mode"/>
+ <item
+ android:id="@+id/headlines_toggle_sort_order"
+ app:iconTint="?attr/colorControlNormal"
+ android:icon="@drawable/baseline_sort_by_alpha_24"
+ app:showAsAction="ifRoom"
+ android:title="@string/toggle_sort_order"/>
- <item
- android:id="@+id/headlines_display_mode"
- app:showAsAction=""
- android:title="@string/headlines_display_mode"/>
+ <item
+ android:id="@+id/headlines_view_mode"
+ app:showAsAction=""
+ android:title="@string/headlines_view_mode"/>
- </group>
- <group android:id="@+id/menu_group_article" >
- <item
- android:id="@+id/toggle_marked"
- android:icon="@drawable/baseline_star_outline_24"
- app:iconTint="?attr/colorControlNormal"
- app:showAsAction="ifRoom"
- android:title="@string/article_toggle_marked"/>
- <item
- android:id="@+id/toggle_published"
- android:icon="@drawable/baseline_rss_feed_24"
- app:iconTint="?attr/colorControlNormal"
- app:showAsAction="ifRoom"
- android:title="@string/article_toggle_published"/>
- <item
- android:id="@+id/share_article"
- android:icon="@drawable/baseline_share_24"
- app:iconTint="?attr/colorControlNormal"
- app:showAsAction="ifRoom"
- android:title="@string/share_article"/>
- <item
- android:id="@+id/catchup_above"
- app:showAsAction=""
- android:title="@string/article_mark_read_above"/>
- </group>
+ <item
+ android:id="@+id/headlines_display_mode"
+ app:showAsAction=""
+ android:title="@string/headlines_display_mode"/>
</group>
+ <group android:id="@+id/menu_group_article" >
+ <item
+ android:id="@+id/toggle_marked"
+ android:icon="@drawable/baseline_star_outline_24"
+ app:iconTint="?attr/colorControlNormal"
+ app:showAsAction="ifRoom"
+ android:title="@string/article_toggle_marked"/>
+ <item
+ android:id="@+id/toggle_published"
+ android:icon="@drawable/baseline_rss_feed_24"
+ app:iconTint="?attr/colorControlNormal"
+ app:showAsAction="ifRoom"
+ android:title="@string/article_toggle_published"/>
+ <item
+ android:id="@+id/share_article"
+ android:icon="@drawable/baseline_share_24"
+ app:iconTint="?attr/colorControlNormal"
+ app:showAsAction="ifRoom"
+ android:title="@string/share_article"/>
+ <item
+ android:id="@+id/catchup_above"
+ app:showAsAction=""
+ android:title="@string/article_mark_read_above"/>
+ </group>
+
<group android:id="@+id/menu_group_logged_out" >
<item
diff --git a/org.fox.ttrss/src/main/res/menu/context_category.xml b/org.fox.ttrss/src/main/res/menu/context_category.xml
deleted file mode 100644
index 8f975c0a..00000000
--- a/org.fox.ttrss/src/main/res/menu/context_category.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<menu xmlns:android="http://schemas.android.com/apk/res/android" >
-
- <item
- android:id="@+id/browse_headlines"
- android:title="@string/category_browse_headlines"/>
-
- <item
- android:id="@+id/browse_feeds"
- android:title="@string/category_browse_feeds"/>
-
- <item
- android:id="@+id/catchup_category"
- android:title="@string/catchup"/>
-
-</menu> \ No newline at end of file
diff --git a/org.fox.ttrss/src/main/res/menu/context_feed.xml b/org.fox.ttrss/src/main/res/menu/context_feed.xml
index 0b0cdc3e..53b610f7 100644
--- a/org.fox.ttrss/src/main/res/menu/context_feed.xml
+++ b/org.fox.ttrss/src/main/res/menu/context_feed.xml
@@ -1,23 +1,19 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item
- android:id="@+id/browse_headlines"
+ android:id="@+id/feed_browse_headlines"
android:title="@string/category_browse_headlines"/>
-
- <!-- <item
- android:id="@+id/browse_articles"
- android:title="@string/category_browse_articles"/> -->
-
+
<item
- android:id="@+id/browse_feeds"
+ android:id="@+id/feed_browse_feeds"
android:title="@string/category_browse_feeds"/>
<item
- android:id="@+id/catchup_feed"
+ android:id="@+id/feed_catchup"
android:title="@string/catchup"/>
<item
- android:id="@+id/unsubscribe_feed"
+ android:id="@+id/feed_unsubscribe"
android:title="@string/unsubscribe"/>
</menu> \ No newline at end of file
diff --git a/org.fox.ttrss/src/main/res/xml/preferences.xml b/org.fox.ttrss/src/main/res/xml/preferences.xml
index 27bf2a8a..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,13 @@
android:title="@string/sort_feeds_by_unread" />
<SwitchPreferenceCompat
+ android:defaultValue="true"
+ android:key="enable_cats"
+ android:title="@string/enable_cats" />
+
+ <SwitchPreferenceCompat
android:defaultValue="false"
+ android:dependency="enable_cats"
android:key="browse_cats_like_feeds"
android:summary="@string/browse_cats_like_feeds_summary"
android:title="@string/browse_cats_like_feeds" />