From 191ba10cf398212e25e80114a43100dfdcfa55ab Mon Sep 17 00:00:00 2001 From: Andrew Dolgov Date: Fri, 16 May 2025 08:05:26 +0300 Subject: switch feeds to recycler view while removing most of legacy code --- .../src/main/java/org/fox/ttrss/ArticleModel.java | 2 +- .../java/org/fox/ttrss/BaseFeedlistFragment.java | 78 ---- .../main/java/org/fox/ttrss/CommonActivity.java | 2 +- .../java/org/fox/ttrss/FeedCategoriesFragment.java | 446 --------------------- .../src/main/java/org/fox/ttrss/FeedsFragment.java | 356 ++++++++-------- .../main/java/org/fox/ttrss/HeadlinesFragment.java | 13 +- .../main/java/org/fox/ttrss/MasterActivity.java | 149 ++++--- .../main/java/org/fox/ttrss/OnlineActivity.java | 7 - .../java/org/fox/ttrss/RootCategoriesFragment.java | 134 +++++++ .../src/main/java/org/fox/ttrss/types/Feed.java | 84 ++-- .../src/main/res/drawable/baseline_label_24.xml | 5 + .../src/main/res/layout/feeds_row_header.xml | 70 ---- .../src/main/res/layout/fragment_feeds.xml | 96 ++++- .../main/res/layout/fragment_feeds_recycler.xml | 18 - org.fox.ttrss/src/main/res/xml/preferences.xml | 6 - 15 files changed, 543 insertions(+), 923 deletions(-) delete mode 100755 org.fox.ttrss/src/main/java/org/fox/ttrss/BaseFeedlistFragment.java delete mode 100755 org.fox.ttrss/src/main/java/org/fox/ttrss/FeedCategoriesFragment.java create mode 100755 org.fox.ttrss/src/main/java/org/fox/ttrss/RootCategoriesFragment.java create mode 100644 org.fox.ttrss/src/main/res/drawable/baseline_label_24.xml delete mode 100755 org.fox.ttrss/src/main/res/layout/feeds_row_header.xml delete mode 100755 org.fox.ttrss/src/main/res/layout/fragment_feeds_recycler.xml 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 80467f63..7a79887d 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 @@ -80,7 +80,7 @@ public class ArticleModel extends AndroidViewModel implements ApiCommon.ApiCalle } public void startLoading(boolean append, @NonNull Feed feed, int resizeWidth) { - Log.d(TAG, "startLoading append=" + append); + Log.d(TAG, "startLoading append=" + append + " feed id=" + feed.id + " cat=" + feed.is_cat); m_resizeWidth = resizeWidth; diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/BaseFeedlistFragment.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/BaseFeedlistFragment.java deleted file mode 100755 index 4f48a19e..00000000 --- a/org.fox.ttrss/src/main/java/org/fox/ttrss/BaseFeedlistFragment.java +++ /dev/null @@ -1,78 +0,0 @@ -package org.fox.ttrss; - -import android.content.Intent; -import android.content.SharedPreferences; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.ImageView; -import android.widget.ListView; -import android.widget.TextView; - -import androidx.appcompat.widget.SwitchCompat; - -import java.net.MalformedURLException; -import java.net.URL; - -public abstract class BaseFeedlistFragment extends androidx.fragment.app.Fragment { - abstract public void refresh(); - - public void initDrawerHeader(LayoutInflater inflater, View view, ListView list, final CommonActivity activity, final SharedPreferences prefs) { - - View layout = inflater.inflate(R.layout.feeds_row_header, list, false); - list.addHeaderView(layout, null, false); - - TextView login = view.findViewById(R.id.drawer_header_login); - TextView server = view.findViewById(R.id.drawer_header_server); - - login.setText(prefs.getString("login", "")); - try { - server.setText(new URL(prefs.getString("ttrss_url", "")).getHost()); - } catch (MalformedURLException e) { - server.setText(""); - } - - View settings = view.findViewById(R.id.drawer_settings_btn); - - settings.setOnClickListener(v -> { - try { - Intent intent = new Intent(getActivity(), - PreferencesActivity.class); - - startActivityForResult(intent, 0); - - } catch (Exception e) { - e.printStackTrace(); - } - }); - - /* deal with ~material~ footers */ - - // divider - final View footer = inflater.inflate(R.layout.feeds_row_divider, list, false); - footer.setOnClickListener(v -> { - // - }); - list.addFooterView(footer); - - // unread only checkbox - final View rowToggle = inflater.inflate(R.layout.feeds_row_toggle, list, false); - list.addFooterView(rowToggle); - TextView text = rowToggle.findViewById(R.id.title); - text.setText(R.string.unread_only); - - ImageView icon = rowToggle.findViewById(R.id.icon); - icon.setImageResource(R.drawable.baseline_filter_alt_24); - - final SwitchCompat rowSwitch = rowToggle.findViewById(R.id.row_switch); - rowSwitch.setChecked(activity.getUnreadOnly()); - - rowSwitch.setOnCheckedChangeListener((button, isChecked) -> { - activity.setUnreadOnly(isChecked); - refresh(); - }); - - footer.setOnClickListener(v -> rowSwitch.setChecked(!rowSwitch.isChecked())); - - } - -} 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 97e356c4..d5d593ae 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[] { "enable_cats", "headline_mode", "widget_update_interval", + String[] filter = new String[] { "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/FeedCategoriesFragment.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/FeedCategoriesFragment.java deleted file mode 100755 index 34127013..00000000 --- a/org.fox.ttrss/src/main/java/org/fox/ttrss/FeedCategoriesFragment.java +++ /dev/null @@ -1,446 +0,0 @@ -package org.fox.ttrss; - -import android.annotation.SuppressLint; -import android.app.Activity; -import android.content.Context; -import android.content.SharedPreferences; -import android.content.SharedPreferences.OnSharedPreferenceChangeListener; -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; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.AdapterView.AdapterContextMenuInfo; -import android.widget.AdapterView.OnItemClickListener; -import android.widget.ArrayAdapter; -import android.widget.ImageView; -import android.widget.ListView; -import android.widget.TextView; - -import androidx.loader.app.LoaderManager; -import androidx.loader.content.Loader; -import androidx.preference.PreferenceManager; -import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; - -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.Feed; -import org.fox.ttrss.types.FeedCategory; -import org.fox.ttrss.types.FeedCategoryList; - -import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; - -public class FeedCategoriesFragment extends BaseFeedlistFragment implements OnItemClickListener, OnSharedPreferenceChangeListener, - LoaderManager.LoaderCallbacks { - private final String TAG = this.getClass().getSimpleName(); - private FeedCategoryListAdapter m_adapter; - private final FeedCategoryList m_cats = new FeedCategoryList(); - FeedCategory m_selectedCat; - private MasterActivity m_activity; - private SwipeRefreshLayout m_swipeLayout; - private ListView m_list; - protected SharedPreferences m_prefs; - - @Override - public Loader onCreateLoader(int id, Bundle args) { - final String sessionId = m_activity.getSessionId(); - final boolean unreadOnly = m_activity.getUnreadOnly(); - - HashMap params = new HashMap<>(); - params.put("op", "getCategories"); - params.put("sid", sessionId); - params.put("enable_nested", "true"); - - if (unreadOnly) - params.put("unread_only", "true"); - - return new ApiLoader(getContext(), params); - } - - @Override - public void onLoadFinished(Loader loader, JsonElement result) { - Log.d(TAG, "onLoadFinished: " + loader + " " + result); - - if (m_swipeLayout != null) m_swipeLayout.setRefreshing(false); - - if (result != null) { - try { - JsonArray content = result.getAsJsonArray(); - if (content != null) { - Type listType = new TypeToken>() {}.getType(); - final List cats = new Gson().fromJson(content, listType); - - m_cats.clear(); - - int apiLevel = m_activity.getApiLevel(); - - boolean specialCatFound = false; - - // virtual cats implemented in getCategories since api level 1 - if (apiLevel == 0) { - m_cats.add(new FeedCategory(-1, getString(R.string.cat_special), 0)); - m_cats.add(new FeedCategory(-2, getString(R.string.cat_labels), 0)); - m_cats.add(new FeedCategory(0, getString(R.string.cat_uncategorized), 0)); - - specialCatFound = true; - } - - for (FeedCategory c : cats) { - if (c.id == -1) { - specialCatFound = true; - break; - } - } - - m_cats.addAll(cats); - - sortCats(); - - if (!specialCatFound) { - m_cats.add(0, new FeedCategory(-1, getString(R.string.cat_special), 0)); - } - - m_adapter.notifyDataSetChanged(); - - return; - } - - } catch (Exception e) { - e.printStackTrace(); - } - } - - ApiLoader al = (ApiLoader) loader; - - if (al.getLastError() == ApiCommon.ApiError.LOGIN_FAILED) { - m_activity.login(true); - } else { - if (al.getLastErrorMessage() != null) { - m_activity.toast(getString(al.getErrorMessage()) + "\n" + al.getLastErrorMessage()); - } else { - m_activity.toast(al.getErrorMessage()); - } - } - } - - public void sortCats() { - Comparator cmp; - - if (m_prefs.getBoolean("sort_feeds_by_unread", false)) { - cmp = new CatUnreadComparator(); - } else { - if (m_activity.getApiLevel() >= 3) { - cmp = new CatOrderComparator(); - } else { - cmp = new CatTitleComparator(); - } - } - - try { - m_cats.sort(cmp); - } catch (IllegalArgumentException e) { - e.printStackTrace(); - } - try { - m_adapter.notifyDataSetChanged(); - } catch (NullPointerException e) { - // adapter missing - } - } - - @Override - public void onLoaderReset(Loader loader) { - Log.d(TAG, "onLoaderReset: " + loader); - - /*m_cats.clear(); - m_adapter.notifyDataSetChanged();*/ - } - - @SuppressLint("DefaultLocale") - static class CatUnreadComparator implements Comparator { - @Override - public int compare(FeedCategory a, FeedCategory b) { - if (a.unread != b.unread) - return b.unread - a.unread; - else - return a.title.toUpperCase().compareTo(b.title.toUpperCase()); - } - } - - - @SuppressLint("DefaultLocale") - static class CatTitleComparator implements Comparator { - - @Override - public int compare(FeedCategory a, FeedCategory b) { - if (a.id >= 0 && b.id >= 0) - return a.title.toUpperCase().compareTo(b.title.toUpperCase()); - else - return a.id - b.id; - } - - } - - @SuppressLint("DefaultLocale") - static class CatOrderComparator implements Comparator { - - @Override - public int compare(FeedCategory a, FeedCategory b) { - if (a.id >= 0 && b.id >= 0) - if (a.order_id != 0 && b.order_id != 0) - return a.order_id - b.order_id; - else - return a.title.toUpperCase().compareTo(b.title.toUpperCase()); - else - return a.id - b.id; - } - - } - - @Override - public boolean onContextItemSelected(MenuItem item) { - AdapterContextMenuInfo info = (AdapterContextMenuInfo) item - .getMenuInfo(); - - int itemId = item.getItemId(); - if (itemId == R.id.browse_headlines) { - FeedCategory cat = getCategoryAtPosition(info.position); - if (cat != null) { - m_activity.onCatSelected(cat, true); - //setSelectedCategory(cat); - } - return true; - } else if (itemId == R.id.browse_feeds) { - FeedCategory cat = getCategoryAtPosition(info.position); - if (cat != null) { - m_activity.onCatSelected(cat, false); - //cf.setSelectedCategory(cat); - } - return true; - } else if (itemId == R.id.catchup_category) { - final FeedCategory cat = getCategoryAtPosition(info.position); - - if (cat != null) { - m_activity.catchupDialog(new Feed(cat.id, cat.title, true)); - } - return true; - } - Log.d(TAG, "onContextItemSelected, unhandled id=" + item.getItemId()); - return super.onContextItemSelected(item); - } - - @Override - public void onCreateContextMenu(ContextMenu menu, View v, - ContextMenuInfo menuInfo) { - - m_activity.getMenuInflater().inflate(R.menu.context_category, menu); - - AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo; - FeedCategory cat = (FeedCategory) m_list.getItemAtPosition(info.position); - - if (cat != null) - menu.setHeaderTitle(cat.title); - - super.onCreateContextMenu(menu, v, menuInfo); - - } - - public FeedCategory getCategoryAtPosition(int position) { - try { - return (FeedCategory) m_list.getItemAtPosition(position); - } catch (NullPointerException e) { - return null; - } catch (IndexOutOfBoundsException e) { - return null; - } - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - if (savedInstanceState != null) { - m_selectedCat = savedInstanceState.getParcelable("m_selectedCat"); - } - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - - View view = inflater.inflate(R.layout.fragment_feeds, container, false); - - m_swipeLayout = view.findViewById(R.id.feeds_swipe_container); - - m_swipeLayout.setOnRefreshListener(this::refresh); - - m_list = view.findViewById(R.id.feeds); - m_adapter = new FeedCategoryListAdapter(getActivity(), R.layout.feeds_row, m_cats); - - initDrawerHeader(inflater, view, m_list, m_activity, m_prefs); - - m_list.setAdapter(m_adapter); - m_list.setOnItemClickListener(this); - registerForContextMenu(m_list); - - return view; - } - - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - - m_activity = (MasterActivity)activity; - - m_prefs = PreferenceManager.getDefaultSharedPreferences(getActivity().getApplicationContext()); - m_prefs.registerOnSharedPreferenceChangeListener(this); - - } - - @Override - public void onResume() { - super.onResume(); - - LoaderManager.getInstance(this).initLoader(Application.LOADER_CATS, null, this).forceLoad(); - - m_activity.invalidateOptionsMenu(); - } - - public void refresh() { - if (!isAdded()) - return; - - if (m_swipeLayout != null) - m_swipeLayout.setRefreshing(true); - - LoaderManager.getInstance(this).restartLoader(Application.LOADER_CATS, null, this).forceLoad(); - } - - private class FeedCategoryListAdapter extends ArrayAdapter { - private final ArrayList items; - - public static final int VIEW_NORMAL = 0; - public static final int VIEW_SELECTED = 1; - - public static final int VIEW_COUNT = VIEW_SELECTED+1; - - public FeedCategoryListAdapter(Context context, int textViewResourceId, ArrayList items) { - super(context, textViewResourceId, items); - this.items = items; - } - - public int getViewTypeCount() { - return VIEW_COUNT; - } - - @Override - public int getItemViewType(int position) { - FeedCategory cat = items.get(position); - - if (/*!m_activity.isSmallScreen() &&*/ m_selectedCat != null && cat.id == m_selectedCat.id) { - return VIEW_SELECTED; - } else { - return VIEW_NORMAL; - } - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - View v = convertView; - - FeedCategory cat = items.get(position); - - if (v == null) { - int layoutId = R.layout.feeds_row; - - if (getItemViewType(position) == VIEW_SELECTED) { - layoutId = R.layout.feeds_row_selected; - } - - LayoutInflater vi = (LayoutInflater)getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE); - v = vi.inflate(layoutId, null); - - } - - ImageView icon = v.findViewById(R.id.icon); - - if (icon != null) { - icon.setImageResource(R.drawable.baseline_folder_open_24); - - } - - TextView tt = v.findViewById(R.id.title); - - if (tt != null) { - tt.setText(cat.title); - } - - TextView tu = v.findViewById(R.id.unread_counter); - - if (tu != null) { - tu.setText(String.valueOf(cat.unread)); - tu.setVisibility((cat.unread > 0) ? View.VISIBLE : View.INVISIBLE); - } - - return v; - } - } - - @Override - public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, - String key) { - - sortCats(); - - } - - @Override - public void onItemClick(AdapterView av, View view, int position, long id) { - ListView list = (ListView)av; - - Log.d(TAG, "onItemClick=" + position); - - if (list != null) { - - FeedCategory cat = (FeedCategory)list.getItemAtPosition(position); - - m_selectedCat = null; - m_adapter.notifyDataSetChanged(); - - if (cat != null) { - if (cat.id < 0) { - m_activity.onCatSelected(cat, false); - } else { - m_activity.onCatSelected(cat); - } - - } - } - } - - public void setSelectedCategory(FeedCategory cat) { - m_selectedCat = cat; - - if (m_adapter != null) { - m_adapter.notifyDataSetChanged(); - } - } - - @Override - public void onSaveInstanceState(Bundle out) { - super.onSaveInstanceState(out); - - out.putParcelable("m_selectedCat", m_selectedCat); - } -} 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 da0af1de..f66758ee 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 @@ -2,6 +2,7 @@ package org.fox.ttrss; import android.annotation.SuppressLint; import android.app.Activity; +import android.app.Dialog; import android.content.Intent; import android.content.SharedPreferences; import android.content.SharedPreferences.OnSharedPreferenceChangeListener; @@ -14,6 +15,7 @@ import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import android.widget.AdapterView; import android.widget.ImageView; import android.widget.TextView; @@ -29,35 +31,42 @@ import androidx.recyclerview.widget.ListAdapter; import androidx.recyclerview.widget.RecyclerView; 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 org.fox.ttrss.types.Feed; -import org.fox.ttrss.types.FeedCategory; 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; public class FeedsFragment extends Fragment implements OnSharedPreferenceChangeListener, LoaderManager.LoaderCallbacks { private final String TAG = this.getClass().getSimpleName(); - private SharedPreferences m_prefs; - private MasterActivity m_activity; - private FeedCategory m_category; - private int m_selectedFeedId; - private SwipeRefreshLayout m_swipeLayout; + protected SharedPreferences m_prefs; + protected MasterActivity m_activity; + protected Feed m_feed; + private Feed m_selectedFeed; + protected SwipeRefreshLayout m_swipeLayout; private boolean m_enableParentBtn = false; - private FeedsAdapter m_adapter; + protected FeedsAdapter m_adapter; private RecyclerView m_list; private RecyclerView.LayoutManager m_layoutManager; - public void initialize(@NonNull FeedCategory cat, boolean enableParentBtn) { - m_category = cat; + public void initialize(@NonNull Feed feed, boolean enableParentBtn) { + Log.d(TAG, "initialize, feed=" + feed); + + m_feed = feed; m_enableParentBtn = enableParentBtn; } @@ -65,23 +74,21 @@ public class FeedsFragment extends Fragment implements OnSharedPreferenceChangeL public Loader onCreateLoader(int id, Bundle args) { if (m_swipeLayout != null) m_swipeLayout.setRefreshing(true); - final String sessionId = m_activity.getSessionId(); - final boolean unreadOnly = m_activity.getUnreadOnly() && m_category.id != -1; /* starred */ - HashMap params = new HashMap<>(); params.put("op", "getFeeds"); - params.put("sid", sessionId); + params.put("sid", m_activity.getSessionId()); params.put("include_nested", "true"); - params.put("cat_id", String.valueOf(m_category.id)); + params.put("cat_id", String.valueOf(m_feed.id)); - if (unreadOnly) + /* except marked */ + if (m_activity.getUnreadOnly() && m_feed.id != -1) params.put("unread_only", "true"); return new ApiLoader(getContext(), params); } @Override - public void onLoadFinished(Loader loader, JsonElement result) { + public void onLoadFinished(@NonNull Loader loader, JsonElement result) { if (m_swipeLayout != null) m_swipeLayout.setRefreshing(false); if (result != null) { @@ -90,7 +97,7 @@ public class FeedsFragment extends Fragment implements OnSharedPreferenceChangeL if (content != null) { Type listType = new TypeToken>() {}.getType(); - final List feedsJson = new Gson().fromJson(content, listType); + List feedsJson = new Gson().fromJson(content, listType); List feeds = new ArrayList<>(); @@ -130,25 +137,32 @@ public class FeedsFragment extends Fragment implements OnSharedPreferenceChangeL // m_adapter.sortFeeds(feedsJson); - feeds.add(new Feed(Feed.TYPE_HEADER)); + //List feedsJsonFiltered = feedsJson.stream().filter(a -> a.id >= -10).collect(Collectors.toList()); + + if (m_feed.id == Feed.ALL_ARTICLES) + feedsJson = feedsJson.stream().filter(a -> a.id > -10).collect(Collectors.toList()); + + sortFeeds(feedsJson, m_feed); + + // feeds.add(new Feed(Feed.TYPE_HEADER)); if (m_enableParentBtn) { - feeds.add(new Feed(Feed.TYPE_GOBACK)); + feeds.add(0, new Feed(Feed.TYPE_GOBACK)); - if (m_enableParentBtn && m_category.id >= 0 && !feedsJson.isEmpty()) { - Feed feed = new Feed(m_category.id, m_category.title, true); - feed.unread = catUnread; - feed.always_display_as_feed = true; - feed.display_title = getString(R.string.feed_all_articles); + if (m_feed.id >= 0 && !feedsJson.isEmpty()) { + Feed feed = new Feed(m_feed.id, m_feed.title, true); - feeds.add(0, feed); + 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)); + feeds.add(new Feed(Feed.TYPE_TOGGLE_UNREAD, getString(R.string.unread_only), true)); m_adapter.submitList(feeds); @@ -156,7 +170,7 @@ public class FeedsFragment extends Fragment implements OnSharedPreferenceChangeL } } catch (Exception e) { - e.printStackTrace(); + m_activity.toast(e.getMessage()); } } @@ -211,6 +225,17 @@ public class FeedsFragment extends Fragment implements OnSharedPreferenceChangeL } + @SuppressLint("DefaultLocale") + static class SpecialOrderComparator implements Comparator { + static List order = Arrays.asList(Feed.ALL_ARTICLES, Feed.FRESH, Feed.MARKED, + Feed.PUBLISHED, Feed.ARCHIVED, Feed.RECENTLY_READ); + + @Override + public int compare(Feed a, Feed b) { + return Integer.valueOf(order.indexOf(a.id)).compareTo(order.indexOf(b.id)); + } + } + @SuppressLint("DefaultLocale") static class FeedOrderComparator implements Comparator { @@ -241,46 +266,42 @@ public class FeedsFragment extends Fragment implements OnSharedPreferenceChangeL @Override public boolean onContextItemSelected(MenuItem item) { - /* AdapterContextMenuInfo info = (AdapterContextMenuInfo) item + 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 feed = getFeedAtPosition(info.position); - if (feed != null) { - m_activity.onFeedSelected(feed); - } + Feed forceFeed = new Feed(feed); + forceFeed.always_open_headlines = true; + + m_activity.onFeedSelected(forceFeed); return true; } else if (itemId == R.id.browse_feeds) { - Feed feed = getFeedAtPosition(info.position); - if (feed != null) { - m_activity.onCatSelected(new FeedCategory(feed.id, feed.title, feed.unread), false); - } + m_activity.onFeedSelected(feed); + //m_activity.onCatSelected(new FeedCategory(feed.id, feed.title, feed.unread), false); return true; } else if (itemId == R.id.unsubscribe_feed) { - final Feed feed = getFeedAtPosition(info.position); - if (feed != null) { - 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(); - } + 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.catchup_feed) { - Feed feed = getFeedAtPosition(info.position); - - if (feed != null) { - m_activity.catchupDialog(feed); - } + m_activity.catchupDialog(feed); return true; - } */ + } Log.d(TAG, "onContextItemSelected, unhandled id=" + item.getItemId()); return super.onContextItemSelected(item); @@ -289,14 +310,14 @@ public class FeedsFragment extends Fragment implements OnSharedPreferenceChangeL @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { + + m_activity.getMenuInflater().inflate(R.menu.context_feed, menu); - /* getActivity().getMenuInflater().inflate(R.menu.context_feed, menu); - - AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo; + AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo; - Feed feed = (Feed) m_list.getItemAtPosition(info.position); + Feed feed = m_adapter.getCurrentList().get(info.position); - menu.setHeaderTitle(feed.display_title != null ? feed.display_title : feed.title); + menu.setHeaderTitle(feed.title); if (!feed.is_cat) { menu.findItem(R.id.browse_feeds).setVisible(false); @@ -304,7 +325,7 @@ public class FeedsFragment extends Fragment implements OnSharedPreferenceChangeL if (feed.id <= 0) { menu.findItem(R.id.unsubscribe_feed).setVisible(false); - } */ + } super.onCreateContextMenu(menu, v, menuInfo); @@ -315,8 +336,8 @@ public class FeedsFragment extends Fragment implements OnSharedPreferenceChangeL super.onCreate(savedInstanceState); if (savedInstanceState != null) { - m_catId = savedInstanceState.getInt("m_catId"); - m_selectedFeedId = savedInstanceState.getInt("m_selectedId"); + m_feed = savedInstanceState.getParcelable("m_feed"); + m_selectedFeed = savedInstanceState.getParcelable("m_selectedFeed"); m_enableParentBtn = savedInstanceState.getBoolean("m_enableParentBtn"); } } @@ -325,15 +346,15 @@ public class FeedsFragment extends Fragment implements OnSharedPreferenceChangeL public void onSaveInstanceState(Bundle out) { super.onSaveInstanceState(out); - out.putInt("m_catId", m_catId); - out.putInt("m_selectedId", m_selectedFeedId); + out.putParcelable("m_feed", m_feed); + out.putParcelable("m_selectedFeed", m_selectedFeed); out.putBoolean("m_enableParentBtn", m_enableParentBtn); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.fragment_feeds_recycler, container, false); + View view = inflater.inflate(R.layout.fragment_feeds, container, false); m_swipeLayout = view.findViewById(R.id.feeds_swipe_container); @@ -349,19 +370,32 @@ public class FeedsFragment extends Fragment implements OnSharedPreferenceChangeL m_adapter = new FeedsAdapter(); m_list.setAdapter(m_adapter); - /* if (m_enableParentBtn) { - View layout = inflater.inflate(R.layout.feeds_goback, m_list, false); + TextView login = view.findViewById(R.id.drawer_header_login); - layout.setOnClickListener(view1 -> m_activity.getSupportFragmentManager().popBackStack()); + if (login != null) { + login.setText(m_prefs.getString("login", "")); + } - m_list.addHeaderView(layout, null, false); + TextView server = view.findViewById(R.id.drawer_header_server); + + if (server != null) { + try { + server.setText(new URL(m_prefs.getString("ttrss_url", "")).getHost()); + } catch (MalformedURLException e) { + server.setText(""); + } } - m_adapter = new FeedListAdapter(getActivity(), R.layout.feeds_row, m_feeds); - m_list.setAdapter(m_adapter); - m_list.setOnItemClickListener(this); + View settingsBtn = view.findViewById(R.id.drawer_settings_btn); - registerForContextMenu(m_list); */ + if (settingsBtn != null) { + settingsBtn.setOnClickListener(v -> { + Intent intent = new Intent(getActivity(), + PreferencesActivity.class); + + startActivityForResult(intent, 0); + }); + } return view; } @@ -385,38 +419,17 @@ public class FeedsFragment extends Fragment implements OnSharedPreferenceChangeL public void onResume() { super.onResume(); - LoaderManager.getInstance(this).initLoader(Application.LOADER_FEEDS, null, this).forceLoad(); + Log.d(TAG, "onResume"); + + refresh(); m_activity.invalidateOptionsMenu(); } - - /* @Override - public void onItemClick(AdapterView av, View view, int position, long id) { - ListView list = (ListView)av; - - if (list != null) { - Feed feed = (Feed)list.getItemAtPosition(position); - - if (feed != null) { - if (feed.is_cat) { - if (feed.always_display_as_feed) { - m_activity.onCatSelected(new FeedCategory(feed.id, feed.title, feed.unread), true); - } else if (feed.id < 0) { - m_activity.onCatSelected(new FeedCategory(feed.id, feed.title, feed.unread), false); - } else { - m_activity.onCatSelected(new FeedCategory(feed.id, feed.title, feed.unread)); - } - } else { - m_activity.onFeedSelected(feed); - } - } - - //m_selectedFeed = feed; - //m_adapter.notifyDataSetChanged(); - } - } */ public void refresh() { + if (!isAdded()) + return; + if (m_swipeLayout != null) { m_swipeLayout.setRefreshing(true); } @@ -430,10 +443,7 @@ public class FeedsFragment extends Fragment implements OnSharedPreferenceChangeL private ImageView icon; private TextView title; private TextView unreadCounter; - private TextView rowSwitch; - private View settingsBtn; - private TextView login; - private TextView server; + private MaterialSwitch rowSwitch; public FeedViewHolder(@NonNull View itemView) { super(itemView); @@ -443,9 +453,6 @@ public class FeedsFragment extends Fragment implements OnSharedPreferenceChangeL title = itemView.findViewById(R.id.title); unreadCounter = itemView.findViewById(R.id.unread_counter); rowSwitch = itemView.findViewById(R.id.row_switch); - settingsBtn = itemView.findViewById(R.id.drawer_settings_btn); - login = itemView.findViewById(R.id.drawer_header_login); - server = itemView.findViewById(R.id.drawer_header_server); } } @@ -465,11 +472,10 @@ public class FeedsFragment extends Fragment implements OnSharedPreferenceChangeL } } - private class FeedsAdapter extends ListAdapter { + protected class FeedsAdapter extends ListAdapter { public static final int VIEW_NORMAL = 0; public static final int VIEW_SELECTED = 1; public static final int VIEW_GOBACK = 2; - public static final int VIEW_HEADER = 3; public static final int VIEW_TOGGLE_UNREAD = 4; public static final int VIEW_DIVIDER = 5; @@ -492,9 +498,6 @@ public class FeedsFragment extends Fragment implements OnSharedPreferenceChangeL case VIEW_TOGGLE_UNREAD: layoutId = R.layout.feeds_row_toggle; break; - case VIEW_HEADER: - layoutId = R.layout.feeds_row_header; - break; case VIEW_DIVIDER: layoutId = R.layout.feeds_row_divider; break; @@ -508,31 +511,13 @@ public class FeedsFragment extends Fragment implements OnSharedPreferenceChangeL Feed feed = getItem(position); if (holder.icon != null) { - if (feed.id == Feed.TYPE_GOBACK) { - holder.icon.setImageResource(R.drawable.baseline_arrow_back_24); - } else if (feed.id == 0 && !feed.is_cat) { - holder.icon.setImageResource(R.drawable.baseline_archive_24); - } else if (feed.id == -1 && !feed.is_cat) { - holder.icon.setImageResource(R.drawable.baseline_star_24); - } else if (feed.id == -2 && !feed.is_cat) { - holder.icon.setImageResource(R.drawable.rss); - } else if (feed.id == -3 && !feed.is_cat) { - holder.icon.setImageResource(R.drawable.baseline_local_fire_department_24); - } else if (feed.id == -4 && !feed.is_cat) { - holder.icon.setImageResource(R.drawable.baseline_inbox_24); - } else if (feed.id == -6 && !feed.is_cat) { - holder.icon.setImageResource(R.drawable.baseline_restore_24); - } else if (feed.is_cat) { - holder.icon.setImageResource(R.drawable.baseline_folder_open_24); - } else { - holder.icon.setImageResource(R.drawable.rss); - } + holder.icon.setImageResource(getIconForFeed(feed)); } if (holder.title != null) { - holder.title.setText(feed.display_title != null ? feed.display_title : feed.title); + holder.title.setText(feed.title); - if (feed.always_display_as_feed || (!feed.is_cat && feed.id == -4)) { + if (feed.always_open_headlines || (!feed.is_cat && feed.id == -4)) { holder.title.setTypeface(null, Typeface.BOLD); } else { holder.title.setTypeface(null, Typeface.NORMAL); @@ -545,25 +530,42 @@ public class FeedsFragment extends Fragment implements OnSharedPreferenceChangeL holder.unreadCounter.setVisibility((feed.unread > 0) ? View.VISIBLE : View.INVISIBLE); } - if (holder.settingsBtn != null) { - holder.settingsBtn.setOnClickListener(view -> { - Intent intent = new Intent(getActivity(), - PreferencesActivity.class); + // there's only one kind of row with checkbox atm + if (holder.rowSwitch != null) { + holder.rowSwitch.setChecked(m_activity.getUnreadOnly()); - startActivityForResult(intent, 0); + holder.rowSwitch.setOnCheckedChangeListener((button, isChecked) -> { + m_activity.setUnreadOnly(isChecked); + refresh(); }); } + holder.view.setOnLongClickListener(view -> { + if (feed.id != Feed.TYPE_TOGGLE_UNREAD && feed.id != Feed.TYPE_DIVIDER && feed.id != Feed.TYPE_GOBACK && feed.id != Feed.ALL_ARTICLES) { + m_list.showContextMenuForChild(view); + } + return true; + }); + holder.view.setOnClickListener(view -> { - if (feed.is_cat) { + if (feed.id == Feed.TYPE_GOBACK) { + m_activity.getSupportFragmentManager().popBackStack(); + } else if (feed.id == Feed.TYPE_TOGGLE_UNREAD || feed.id == Feed.TYPE_DIVIDER) { + // + } else { + + /* if (feed.is_cat) { if (feed.always_display_as_feed) { - m_activity.onCatSelected(new FeedCategory(feed.id, feed.title, feed.unread), true); + //m_activity.onCatSelected(new FeedCategory(feed.id, feed.title, feed.unread), true); } else if (feed.id < 0) { - m_activity.onCatSelected(new FeedCategory(feed.id, feed.title, feed.unread), false); + //m_activity.onCatSelected(new FeedCategory(feed.id, feed.title, feed.unread), false); } else { - m_activity.onCatSelected(new FeedCategory(feed.id, feed.title, feed.unread)); + //m_activity.onCatSelected(new FeedCategory(feed.id, feed.title, feed.unread)); } } else { + m_activity.onFeedSelected(feed); + } */ + m_activity.onFeedSelected(feed); } }); @@ -577,20 +579,22 @@ public class FeedsFragment extends Fragment implements OnSharedPreferenceChangeL return VIEW_GOBACK; } else if (feed.id == Feed.TYPE_DIVIDER) { return VIEW_DIVIDER; - } else if (feed.id == Feed.TYPE_HEADER) { - return VIEW_HEADER; } else if (feed.id == Feed.TYPE_TOGGLE_UNREAD) { return VIEW_TOGGLE_UNREAD; - } else if (feed.id == m_selectedFeedId) { + } else if (m_selectedFeed != null && feed.id == m_selectedFeed.id && feed.is_cat && m_selectedFeed.is_cat) { return VIEW_SELECTED; } else { return VIEW_NORMAL; } } + } - public void sortFeeds(List feeds) { - Comparator cmp; + protected void sortFeeds(List feeds, Feed feed) { + Comparator cmp; + if (feed.id == -1) { + cmp = new SpecialOrderComparator(); + } else { if (m_prefs.getBoolean("sort_feeds_by_unread", false)) { cmp = new FeedUnreadComparator(); } else { @@ -600,36 +604,54 @@ public class FeedsFragment extends Fragment implements OnSharedPreferenceChangeL cmp = new FeedTitleComparator(); } } + } - try { - feeds.sort(cmp); - } catch (IllegalArgumentException e) { - // sort order got changed in prefs or something - e.printStackTrace(); - } + try { + feeds.sort(cmp); + } catch (IllegalArgumentException e) { + // } } + protected int getIconForFeed(Feed feed) { + if (feed.id == Feed.TYPE_GOBACK) { + return R.drawable.baseline_arrow_back_24; + } else if (feed.id == Feed.TYPE_TOGGLE_UNREAD) { + return R.drawable.baseline_filter_alt_24; + } else if (feed.id == 0 && !feed.is_cat) { + return R.drawable.baseline_archive_24; + } else if (feed.id == -1 && !feed.is_cat) { + return R.drawable.baseline_star_24; + } else if (feed.id == -2 && !feed.is_cat) { + return R.drawable.rss; + } else if (feed.id == -3 && !feed.is_cat) { + return R.drawable.baseline_local_fire_department_24; + } else if (feed.id == -4 && !feed.is_cat) { + return R.drawable.baseline_inbox_24; + } else if (feed.id == -6 && !feed.is_cat) { + return R.drawable.baseline_restore_24; + } else if (feed.is_cat) { + return R.drawable.baseline_folder_open_24; + } else if (feed.id < -10 && !feed.is_cat) { + return R.drawable.baseline_label_24; + } else { + return R.drawable.rss; + } + } + @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { - refresh(); + // Can't access ViewModels from detached fragment (= backstack) + if (isAdded()) + refresh(); } - /* public Feed getFeedAtPosition(int position) { - try { - return (Feed) m_list.getItemAtPosition(position); - } catch (ArrayIndexOutOfBoundsException e) { - e.printStackTrace(); - } - - return null; - } */ - public void setSelectedFeedId(int feedId) { + public void setSelectedFeed(Feed feed) { if (m_adapter != null) { - m_selectedFeedId = feedId; + m_selectedFeed = feed; // TODO handle properly m_adapter.notifyDataSetChanged(); 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 9757f090..77ef1f8b 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 @@ -34,6 +34,7 @@ import android.view.TextureView; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; +import android.widget.AdapterView; import android.widget.AdapterView.AdapterContextMenuInfo; import android.widget.CheckBox; import android.widget.EditText; @@ -134,7 +135,7 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { public void initialize(Feed feed, int activeArticleId, boolean compactMode) { m_feed = feed; - m_compactLayoutMode = compactMode; + m_compactLayoutMode = compactMode; m_activeArticleId = activeArticleId; } @@ -245,6 +246,12 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { getActivity().getMenuInflater().inflate(R.menu.context_headlines, menu); + AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo; + + Article article = m_adapter.getCurrentList().get(info.position); + + menu.setHeaderTitle(article.title); + menu.findItem(R.id.article_set_labels).setEnabled(m_activity.getApiLevel() >= 1); menu.findItem(R.id.article_edit_note).setEnabled(m_activity.getApiLevel() >= 1); @@ -454,10 +461,6 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { } }); - if (m_activity.isSmallScreen() && m_feed != null) { - m_activity.setTitle(m_feed.title); - } - ArticleModel model = Application.getArticlesModel(); // this gets notified on network update 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 11265769..c92123dd 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 @@ -7,7 +7,6 @@ import android.content.Intent; import android.content.SharedPreferences; import android.net.Uri; import android.os.Bundle; -import android.os.Handler; import android.util.Log; import android.view.MenuItem; import android.view.View; @@ -28,9 +27,7 @@ import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.google.gson.JsonElement; import org.fox.ttrss.types.Article; -import org.fox.ttrss.types.ArticleList; import org.fox.ttrss.types.Feed; -import org.fox.ttrss.types.FeedCategory; import java.util.Date; import java.util.HashMap; @@ -46,7 +43,6 @@ public class MasterActivity extends OnlineActivity implements HeadlinesEventList protected long m_lastWidgetRefresh = 0; protected boolean m_feedIsSelected = false; - protected boolean m_userFeedSelected = false; private ActionBarDrawerToggle m_drawerToggle; private DrawerLayout m_drawerLayout; @@ -139,11 +135,11 @@ public class MasterActivity extends OnlineActivity implements HeadlinesEventList // app shortcuts are not allowed to pass string extras if (feedTitle == null) - feedTitle = Feed.getSpecialFeedTitleById(MasterActivity.this, feedId); + feedTitle = getString(Feed.getSpecialFeedTitleId(feedId, isCat)); Feed tmpFeed = new Feed(feedId, feedTitle, isCat); - onFeedSelected(tmpFeed, false); + onFeedSelected(tmpFeed); } @Override @@ -159,21 +155,13 @@ public class MasterActivity extends OnlineActivity implements HeadlinesEventList lr.execute(map); } - - //m_pullToRefreshAttacher.setRefreshing(true); FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); + RootCategoriesFragment fc = new RootCategoriesFragment(); - /* if (m_prefs.getBoolean("enable_cats", true)) { - ft.replace(R.id.feeds_fragment, new FeedCategoriesFragment(), FRAG_CATS); - } else { - ft.replace(R.id.feeds_fragment, new FeedsFragment(), FRAG_FEEDS); - } */ - - FeedsFragment ff = new FeedsFragment(); - ff.initialize(-4, false); - - ft.replace(R.id.feeds_fragment, ff, FRAG_FEEDS); + // 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); // allow overriding feed to open on startup in non-shortcut mode, default to // open_on_startup prefs setting and not-category @@ -185,12 +173,12 @@ public class MasterActivity extends OnlineActivity implements HeadlinesEventList String openFeedTitle = i.getStringExtra("feed_title"); if (openFeedTitle == null) - openFeedTitle = Feed.getSpecialFeedTitleById(this, openFeedId); + openFeedTitle = getString(Feed.getSpecialFeedTitleId(openFeedId, openFeedIsCat)); if (!shortcutMode && openFeedId != 0) { Log.d(TAG, "opening feed id: " + openFeedId); - onFeedSelected(new Feed(openFeedId, openFeedTitle, openFeedIsCat), false); + onFeedSelected(new Feed(openFeedId, openFeedTitle, openFeedIsCat)); } else if (m_drawerLayout != null) { m_drawerLayout.openDrawer(GravityCompat.START); } @@ -202,7 +190,6 @@ public class MasterActivity extends OnlineActivity implements HeadlinesEventList } else { // savedInstanceState != null m_feedIsSelected = savedInstanceState.getBoolean("m_feedIsSelected"); - m_userFeedSelected = savedInstanceState.getBoolean("m_userFeedSelected"); if (m_drawerLayout != null && !m_feedIsSelected) { m_drawerLayout.openDrawer(GravityCompat.START); @@ -261,91 +248,96 @@ public class MasterActivity extends OnlineActivity implements HeadlinesEventList if (m_menu != null && getSessionId() != null) { Fragment ff = getSupportFragmentManager().findFragmentByTag(FRAG_FEEDS); - Fragment cf = getSupportFragmentManager().findFragmentByTag(FRAG_CATS); HeadlinesFragment hf = (HeadlinesFragment)getSupportFragmentManager().findFragmentByTag(FRAG_HEADLINES); - m_menu.setGroupVisible(R.id.menu_group_feeds, (ff != null && ff.isAdded()) || (cf != null && cf.isAdded())); + m_menu.setGroupVisible(R.id.menu_group_feeds, ff != null && ff.isAdded()); m_menu.setGroupVisible(R.id.menu_group_headlines, hf != null && hf.isAdded()); } } - public void onFeedSelected(Feed feed) { - onFeedSelected(feed, true); - } - - public void onFeedSelected(final Feed feed, final boolean selectedByUser) { + public void onFeedSelected(Feed feed) { - FeedsFragment ff = (FeedsFragment) getSupportFragmentManager().findFragmentByTag(FRAG_FEEDS); + if (isSmallScreen()) + setTitle(feed.title); - if (ff != null && ff.isAdded()) { - ff.setSelectedFeedId(feed.id); - } + if (feed.is_cat && !feed.always_open_headlines) { + FragmentTransaction ft = getSupportFragmentManager() + .beginTransaction(); - if (m_drawerLayout != null) { - m_drawerLayout.closeDrawers(); - } + FeedsFragment ff = new FeedsFragment(); + ff.initialize(feed, true); + ft.replace(R.id.feeds_fragment, ff, FRAG_FEEDS); - Application.getArticles().clear(); + ft.addToBackStack(null); + ft.commit(); - new Handler().postDelayed(() -> { - FragmentTransaction ft = getSupportFragmentManager() - .beginTransaction(); + } else { + FeedsFragment ff = (FeedsFragment) getSupportFragmentManager().findFragmentByTag(FRAG_FEEDS); - HeadlinesFragment hf = new HeadlinesFragment(); - hf.initialize(feed); + if (ff != null) { + ff.setSelectedFeed(feed); + } - ft.replace(R.id.headlines_fragment, hf, FRAG_HEADLINES); + if (m_drawerLayout != null) { + m_drawerLayout.closeDrawers(); + } - ft.commit(); + HeadlinesFragment hf = (HeadlinesFragment) getSupportFragmentManager().findFragmentByTag(FRAG_HEADLINES); - m_feedIsSelected = true; - m_userFeedSelected = selectedByUser; + if (hf != null) { + hf.initialize(feed); + hf.refresh(false); + } else { + FragmentTransaction ft = getSupportFragmentManager() + .beginTransaction(); - }, 250); + hf = new HeadlinesFragment(); + hf.initialize(feed); - Date date = new Date(); + ft.replace(R.id.headlines_fragment, hf, FRAG_HEADLINES); - if (date.getTime() - m_lastRefresh > 30*1000) { - m_lastRefresh = date.getTime(); - refresh(false); - } - } - - public void onCatSelected(FeedCategory cat, boolean openAsFeed) { - FeedCategoriesFragment fc = (FeedCategoriesFragment) getSupportFragmentManager().findFragmentByTag(FRAG_CATS); - - //m_pullToRefreshAttacher.setRefreshing(true); - - if (!openAsFeed) { - - if (fc != null && fc.isAdded()) { - fc.setSelectedCategory(null); + ft.commit(); } - FragmentTransaction ft = getSupportFragmentManager() + /* FragmentTransaction ft = getSupportFragmentManager() .beginTransaction(); - FeedsFragment ff = new FeedsFragment(); - ff.initialize(cat.id, true); - ft.replace(R.id.feeds_fragment, ff, FRAG_FEEDS); + HeadlinesFragment hf = new HeadlinesFragment(); + hf.initialize(feed); + hf.refresh(false); + + ft.replace(R.id.headlines_fragment, hf, FRAG_HEADLINES); - ft.addToBackStack(null); ft.commit(); - } else { - - if (fc != null) { - fc.setSelectedCategory(cat); - } + m_feedIsSelected = true; - Feed feed = new Feed(cat.id, cat.title, true); - onFeedSelected(feed); + Date date = new Date(); + + if (date.getTime() - m_lastRefresh > 30 * 1000) { + m_lastRefresh = date.getTime(); + refresh(false); + } */ } } - public void onCatSelected(FeedCategory cat) { - onCatSelected(cat, m_prefs.getBoolean("browse_cats_like_feeds", false)); - } + /* public void onCatSelected(Feed cat) { + FeedCategoriesFragment fc = (FeedCategoriesFragment) getSupportFragmentManager().findFragmentByTag(FRAG_CATS); + + if (fc != null) { + fc.setSelectedCategory(null); + } + + FragmentTransaction ft = getSupportFragmentManager() + .beginTransaction(); + + FeedsFragment ff = new FeedsFragment(); + ff.initialize(, true); + ft.replace(R.id.feeds_fragment, ff, FRAG_FEEDS); + + ft.addToBackStack(null); + ft.commit(); + } */ @Override public void logout() { @@ -412,7 +404,7 @@ public class MasterActivity extends OnlineActivity implements HeadlinesEventList @Override public void onBackPressed() { if (m_drawerLayout != null && !m_drawerLayout.isDrawerOpen(GravityCompat.START) && - (getSupportFragmentManager().getBackStackEntryCount() > 0 || m_userFeedSelected)) { + (getSupportFragmentManager().getBackStackEntryCount() > 0)) { m_drawerLayout.openDrawer(GravityCompat.START); } else { @@ -437,7 +429,6 @@ public class MasterActivity extends OnlineActivity implements HeadlinesEventList super.onSaveInstanceState(out); out.putBoolean("m_feedIsSelected", m_feedIsSelected); - out.putBoolean("m_userFeedSelected", m_userFeedSelected); Application.getInstance().save(out); } 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 ffca776b..c946f405 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 @@ -8,7 +8,6 @@ import android.content.SharedPreferences; import android.content.res.ColorStateList; import android.graphics.Point; import android.net.ConnectivityManager; -import android.net.NetworkInfo; import android.net.Uri; import android.os.Build; import android.os.Bundle; @@ -1231,12 +1230,6 @@ article.score = Integer.parseInt(edit.getText().toString()); } protected void refresh(boolean includeHeadlines) { - FeedCategoriesFragment cf = (FeedCategoriesFragment) getSupportFragmentManager().findFragmentByTag(FRAG_CATS); - - if (cf != null) { - cf.refresh(); - } - FeedsFragment ff = (FeedsFragment) getSupportFragmentManager().findFragmentByTag(FRAG_FEEDS); if (ff != null) { 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 new file mode 100755 index 00000000..cb91cc14 --- /dev/null +++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/RootCategoriesFragment.java @@ -0,0 +1,134 @@ +package org.fox.ttrss; + +import android.annotation.SuppressLint; +import android.os.Bundle; + +import androidx.annotation.NonNull; +import androidx.loader.content.Loader; + +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.Feed; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.stream.Collectors; + +public class RootCategoriesFragment extends FeedsFragment { + private final String TAG = this.getClass().getSimpleName(); + + @Override + @NonNull + public Loader onCreateLoader(int id, Bundle args) { + HashMap 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"); + + if (m_activity.getUnreadOnly()) + params.put("unread_only", "true"); + + return new ApiLoader(getContext(), params); + } + + @SuppressLint("DefaultLocale") + static class CatOrderComparator implements Comparator { + + @Override + public int compare(Feed a, Feed b) { + if (a.id >= 0 && b.id >= 0) + if (a.order_id != 0 && b.order_id != 0) + return a.order_id - b.order_id; + else + return a.title.toUpperCase().compareTo(b.title.toUpperCase()); + else + return a.id - b.id; + } + + } + + @Override + protected void sortFeeds(List feeds, Feed feed) { + try { + feeds.sort(new CatOrderComparator()); + } catch (IllegalArgumentException e) { + // + } + } + + @Override + protected int getIconForFeed(Feed feed) { + if (feed.id == Feed.TYPE_TOGGLE_UNREAD) + return super.getIconForFeed(feed); + else + return R.drawable.baseline_folder_open_24; + } + + @Override + public void onLoadFinished(Loader 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>() {}.getType(); + List feedsJson = new Gson().fromJson(content, listType); + + List feeds = new ArrayList<>(); + + sortFeeds(feedsJson, m_feed); + + // 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), false)); + feeds.add(1, new Feed(-1, getString(R.string.cat_special), false)); + feeds.add(new Feed(0, getString(R.string.cat_uncategorized), false)); + } + + if (feedsJson.stream().noneMatch(a -> a.id == -1)) + feeds.add(new Feed(-1, getString(R.string.cat_special), true)); + + for (Feed f : feedsJson) { + f.is_cat = true; + feeds.add(f); + } + + 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()); + } + } + } + } +} + 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 5432bad1..f8a2be8f 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 @@ -1,13 +1,11 @@ package org.fox.ttrss.types; -import android.content.Context; import android.os.Parcel; import android.os.Parcelable; import org.fox.ttrss.R; public class Feed implements Comparable, Parcelable { - public static final int TYPE_HEADER = -10000; public static final int TYPE_GOBACK = -10001; public static final int TYPE_DIVIDER = -10002; public static final int TYPE_TOGGLE_UNREAD = -10003; @@ -21,8 +19,7 @@ public class Feed implements Comparable, Parcelable { public int last_updated; public int order_id; public boolean is_cat; - public boolean always_display_as_feed; - public String display_title; + transient public boolean always_open_headlines; public Feed(int id) { this.id = id; @@ -36,24 +33,61 @@ public class Feed implements Comparable, Parcelable { this.is_cat = is_cat; } - // TODO: maybe add special categories? (, bool isCat) - public static String getSpecialFeedTitleById(Context context, int feedId) { - switch (feedId) { - case -1: - return context.getString(R.string.feed_starred_articles); - case -2: - return context.getString(R.string.feed_published_articles); - case -3: - return context.getString(R.string.fresh_articles); - case -4: - return context.getString(R.string.feed_all_articles); - case -6: - return context.getString(R.string.feed_recently_read); - case 0: - return context.getString(R.string.feed_archived_articles); - default: - return null; - } + public Feed(Feed feed) { + id = feed.id; + feed_url = feed.feed_url; + title = feed.title; + unread = feed.unread; + has_icon = feed.has_icon; + cat_id = feed.cat_id; + last_updated = feed.last_updated; + order_id = feed.order_id; + is_cat = feed.is_cat; + always_open_headlines = feed.always_open_headlines; + } + + public static final int MARKED = -1; + public static final int PUBLISHED = -2; + public static final int FRESH = -3; + public static final int ALL_ARTICLES = -4; + public static final int RECENTLY_READ = -6; + public static final int ARCHIVED = 0; + + public static final int CAT_SPECIAL = -1; + public static final int CAT_LABELS = -2; + public static final int CAT_UNCATEGORIZED = 0; + + public static int getSpecialFeedTitleId(int feedId, boolean isCat) { + if (!isCat) + switch (feedId) { + case MARKED: + return R.string.feed_starred_articles; + case PUBLISHED: + return R.string.feed_published_articles; + case FRESH: + return R.string.fresh_articles; + case ALL_ARTICLES: + return R.string.feed_all_articles; + case RECENTLY_READ: + return R.string.feed_recently_read; + case ARCHIVED: + return R.string.feed_archived_articles; + case TYPE_TOGGLE_UNREAD: + return R.string.unread_only; + default: + throw new IllegalArgumentException("Invalid special feed id: " + feedId); + } + else + switch (feedId) { + case CAT_LABELS: + return R.string.cat_labels; + case CAT_SPECIAL: + return R.string.cat_special; + case CAT_UNCATEGORIZED: + return R.string.cat_uncategorized; + default: + throw new IllegalArgumentException("Invalid special category id: " + feedId); + } } public Feed(Parcel in) { @@ -98,8 +132,7 @@ public class Feed implements Comparable, Parcelable { out.writeInt(last_updated); out.writeInt(is_cat ? 1 : 0); out.writeInt(order_id); - out.writeInt(always_display_as_feed ? 1 : 0); - out.writeString(display_title); + out.writeInt(always_open_headlines ? 1 : 0); } public void readFromParcel(Parcel in) { @@ -112,8 +145,7 @@ public class Feed implements Comparable, Parcelable { last_updated = in.readInt(); is_cat = in.readInt() == 1; order_id = in.readInt(); - always_display_as_feed = in.readInt() == 1; - display_title = in.readString(); + always_open_headlines = in.readInt() == 1; } @SuppressWarnings("rawtypes") diff --git a/org.fox.ttrss/src/main/res/drawable/baseline_label_24.xml b/org.fox.ttrss/src/main/res/drawable/baseline_label_24.xml new file mode 100644 index 00000000..02371e76 --- /dev/null +++ b/org.fox.ttrss/src/main/res/drawable/baseline_label_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/org.fox.ttrss/src/main/res/layout/feeds_row_header.xml b/org.fox.ttrss/src/main/res/layout/feeds_row_header.xml deleted file mode 100755 index f4954259..00000000 --- a/org.fox.ttrss/src/main/res/layout/feeds_row_header.xml +++ /dev/null @@ -1,70 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file 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 bf26e3fc..9f57dda2 100755 --- a/org.fox.ttrss/src/main/res/layout/fragment_feeds.xml +++ b/org.fox.ttrss/src/main/res/layout/fragment_feeds.xml @@ -1,24 +1,82 @@ - + android:layout_width="match_parent" + android:layout_height="wrap_content"> - - - + + - + android:layout_height="wrap_content"> + + + + + + + + + + + + + + + + + - \ No newline at end of file + + diff --git a/org.fox.ttrss/src/main/res/layout/fragment_feeds_recycler.xml b/org.fox.ttrss/src/main/res/layout/fragment_feeds_recycler.xml deleted file mode 100755 index 1ed85a5b..00000000 --- a/org.fox.ttrss/src/main/res/layout/fragment_feeds_recycler.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - \ 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 8d064587..27bf2a8a 100755 --- a/org.fox.ttrss/src/main/res/xml/preferences.xml +++ b/org.fox.ttrss/src/main/res/xml/preferences.xml @@ -43,14 +43,8 @@ android:key="sort_feeds_by_unread" android:title="@string/sort_feeds_by_unread" /> - - -- cgit v1.2.3-54-g00ecf