diff options
Diffstat (limited to 'org.fox.ttrss')
22 files changed, 510 insertions, 422 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..87bdc96f 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) { @@ -546,17 +559,21 @@ public class FeedsFragment extends Fragment implements OnSharedPreferenceChangeL } public int getPositionOf(Feed feed) { - List<Feed> feeds = getCurrentList(); - - return IntStream.range(0, feeds.size()) - .sequential() - .filter(i -> { - Feed f = feeds.get(i); + if (feed != null) { + List<Feed> feeds = getCurrentList(); + + return IntStream.range(0, feeds.size()) + .sequential() + .filter(i -> { + Feed f = feeds.get(i); + + return f.id == feed.id && f.is_cat == feed.is_cat; + }) + .findFirst() + .orElse(-1); + } - return f.id == feed.id && f.is_cat == feed.is_cat; - }) - .findFirst() - .orElse(-1); + return -1; } } 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" /> |