diff options
31 files changed, 777 insertions, 1231 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 adc0881d..b6a9ff32 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 @@ -19,6 +19,7 @@ import com.google.gson.JsonParser; import java.io.IOException; import java.util.HashMap; import java.util.Locale; +import java.util.concurrent.TimeUnit; import okhttp3.Credentials; import okhttp3.MediaType; @@ -137,9 +138,13 @@ public class ApiCommon { Request request = requestBuilder.build(); - Response response = new OkHttpClient() - .newCall(request) - .execute(); + OkHttpClient client = new OkHttpClient.Builder() + .connectTimeout(10, TimeUnit.SECONDS) + .writeTimeout(10, TimeUnit.SECONDS) + .readTimeout(30, TimeUnit.SECONDS) + .build(); + + Response response = client.newCall(request).execute(); if (response.isSuccessful()) { String payloadReceived = response.body().string(); 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/ArticlePager.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/ArticlePager.java index cf43f4f0..52f78cf0 100755 --- a/org.fox.ttrss/src/main/java/org/fox/ttrss/ArticlePager.java +++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/ArticlePager.java @@ -15,8 +15,8 @@ import androidx.viewpager2.widget.ViewPager2; import org.fox.ttrss.types.Article; import org.fox.ttrss.types.ArticleList; import org.fox.ttrss.types.Feed; -import org.fox.ttrss.util.DiffFragmentStateAdapter; import org.fox.ttrss.util.ArticleDiffItemCallback; +import org.fox.ttrss.util.DiffFragmentStateAdapter; public class ArticlePager extends androidx.fragment.app.Fragment { 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 b37f7386..00000000 --- a/org.fox.ttrss/src/main/java/org/fox/ttrss/BaseFeedlistFragment.java +++ /dev/null @@ -1,79 +0,0 @@ -package org.fox.ttrss; - -import android.content.Intent; -import android.content.SharedPreferences; -import android.util.TypedValue; -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.drawer_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.drawer_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/DetailActivity.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/DetailActivity.java index bca2aa40..500eccb9 100755 --- a/org.fox.ttrss/src/main/java/org/fox/ttrss/DetailActivity.java +++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/DetailActivity.java @@ -19,7 +19,6 @@ import com.google.android.material.bottomappbar.BottomAppBar; import com.google.android.material.floatingactionbutton.FloatingActionButton; import org.fox.ttrss.types.Article; -import org.fox.ttrss.types.ArticleList; import org.fox.ttrss.types.Feed; public class DetailActivity extends OnlineActivity implements HeadlinesEventListener { 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<JsonElement> { - 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<JsonElement> onCreateLoader(int id, Bundle args) { - final String sessionId = m_activity.getSessionId(); - final boolean unreadOnly = m_activity.getUnreadOnly(); - - HashMap<String, String> 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<JsonElement> 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<List<FeedCategory>>() {}.getType(); - final List<FeedCategory> 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<FeedCategory> 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<JsonElement> loader) { - Log.d(TAG, "onLoaderReset: " + loader); - - /*m_cats.clear(); - m_adapter.notifyDataSetChanged();*/ - } - - @SuppressLint("DefaultLocale") - static class CatUnreadComparator implements Comparator<FeedCategory> { - @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<FeedCategory> { - - @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<FeedCategory> { - - @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<FeedCategory> { - private final ArrayList<FeedCategory> 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<FeedCategory> 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 c198343d..513eaeb7 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 @@ -3,13 +3,12 @@ package org.fox.ttrss; import android.annotation.SuppressLint; import android.app.Activity; import android.app.Dialog; -import android.content.Context; +import android.content.Intent; import android.content.SharedPreferences; import android.content.SharedPreferences.OnSharedPreferenceChangeListener; 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; @@ -17,75 +16,79 @@ 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.annotation.NonNull; +import androidx.fragment.app.Fragment; import androidx.loader.app.LoaderManager; import androidx.loader.content.Loader; import androidx.preference.PreferenceManager; +import androidx.recyclerview.widget.DefaultItemAnimator; +import androidx.recyclerview.widget.DiffUtil; +import androidx.recyclerview.widget.LinearLayoutManager; +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 org.fox.ttrss.types.FeedList; 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 BaseFeedlistFragment implements OnItemClickListener, OnSharedPreferenceChangeListener, +public class FeedsFragment extends Fragment implements OnSharedPreferenceChangeListener, LoaderManager.LoaderCallbacks<JsonElement> { private final String TAG = this.getClass().getSimpleName(); - private SharedPreferences m_prefs; - private FeedListAdapter m_adapter; - private final FeedList m_feeds = new FeedList(); - private MasterActivity m_activity; - Feed m_selectedFeed; - FeedCategory m_activeCategory; - private SwipeRefreshLayout m_swipeLayout; - boolean m_enableParentBtn = false; - private ListView m_list; - - public void initialize(FeedCategory cat, boolean enableParentBtn) { - m_activeCategory = cat; - m_enableParentBtn = enableParentBtn; + protected SharedPreferences m_prefs; + protected MasterActivity m_activity; + protected Feed m_rootFeed; + private Feed m_selectedFeed; + protected SwipeRefreshLayout m_swipeLayout; + private boolean m_enableParentBtn = false; + protected FeedsAdapter m_adapter; + private RecyclerView m_list; + private RecyclerView.LayoutManager m_layoutManager; + + public void initialize(@NonNull Feed rootFeed, boolean enableParentBtn) { + Log.d(TAG, "initialize, feed=" + rootFeed); + + m_rootFeed = rootFeed; + m_enableParentBtn = enableParentBtn; } + @NonNull @Override public Loader<JsonElement> onCreateLoader(int id, Bundle args) { - if (m_swipeLayout != null) m_swipeLayout.setRefreshing(true); - final int catId = (m_activeCategory != null) ? m_activeCategory.id : -4; - - final String sessionId = m_activity.getSessionId(); - final boolean unreadOnly = m_activity.getUnreadOnly() && (m_activeCategory == null || m_activeCategory.id != -1); + if (m_swipeLayout != null) + m_swipeLayout.setRefreshing(true); HashMap<String,String> 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(catId)); - - if (unreadOnly) - params.put("unread_only", "true"); + params.put("cat_id", String.valueOf(m_rootFeed.id)); - return new FeedsLoader(getActivity().getApplicationContext(), params, catId); + return new ApiLoader(getContext(), params); } @Override - public void onLoadFinished(Loader<JsonElement> loader, JsonElement result) { + public void onLoadFinished(@NonNull Loader<JsonElement> loader, JsonElement result) { if (m_swipeLayout != null) m_swipeLayout.setRefreshing(false); if (result != null) { @@ -94,64 +97,55 @@ public class FeedsFragment extends BaseFeedlistFragment implements OnItemClickLi if (content != null) { Type listType = new TypeToken<List<Feed>>() {}.getType(); - final List<Feed> feeds = new Gson().fromJson(content, listType); - - m_feeds.clear(); - - int catUnread = 0; + List<Feed> feedsJson = new Gson().fromJson(content, listType); + List<Feed> feeds = new ArrayList<>(); - int catId = ((FeedsLoader) loader).getCatId(); + if (m_activity.getUnreadOnly() && m_rootFeed.id != Feed.CAT_SPECIAL) + feedsJson = feedsJson.stream() + .filter(f -> f.unread > 0) + .collect(Collectors.toList()); - for (Feed f : feeds) - if (f.id > -10 || catId != -4) { // skip labels for flat feedlist for now - if (m_activeCategory != null || f.id >= 0) { - m_feeds.add(f); - catUnread += f.unread; - } + sortFeeds(feedsJson, m_rootFeed); - if (m_activeCategory != null && m_activeCategory.id == -1) - f.title = Feed.getSpecialFeedTitleById(m_activity, f.id); - } - - sortFeeds(); + if (m_enableParentBtn) { + feeds.add(0, new Feed(Feed.TYPE_GOBACK)); - if (m_activeCategory == null) { - Feed feed = new Feed(-1, "Special", true); - feed.unread = catUnread; + if (m_rootFeed.id >= 0 && !feedsJson.isEmpty()) { + Feed feed = new Feed(m_rootFeed.id, m_rootFeed.title, true); - m_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); + } } - if (m_enableParentBtn && m_activeCategory != null && m_activeCategory.id >= 0 && !m_feeds.isEmpty()) { - Feed feed = new Feed(m_activeCategory.id, m_activeCategory.title, true); - feed.unread = catUnread; - feed.always_display_as_feed = true; - feed.display_title = getString(R.string.feed_all_articles); + feeds.addAll(feedsJson); - m_feeds.add(0, feed); - } + feeds.add(new Feed(Feed.TYPE_DIVIDER)); + feeds.add(new Feed(Feed.TYPE_TOGGLE_UNREAD, getString(R.string.unread_only), true)); - m_adapter.notifyDataSetChanged(); + m_adapter.submitList(feeds); return; } } catch (Exception e) { - e.printStackTrace(); + m_activity.toast(e.getMessage()); } } - ApiLoader al = (ApiLoader) loader; + ApiLoader apiLoader = (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()); + if (apiLoader.getLastError() != null && apiLoader.getLastError() != ApiCommon.ApiError.SUCCESS) { + if (apiLoader.getLastError() == ApiCommon.ApiError.LOGIN_FAILED) { + m_activity.login(true); } else { - m_activity.toast(al.getErrorMessage()); + if (apiLoader.getLastErrorMessage() != null) { + m_activity.toast(getString(apiLoader.getErrorMessage()) + "\n" + apiLoader.getLastErrorMessage()); + } else { + m_activity.toast(apiLoader.getErrorMessage()); + } } } } @@ -169,7 +163,7 @@ public class FeedsFragment extends BaseFeedlistFragment implements OnItemClickLi else return a.title.toUpperCase().compareTo(b.title.toUpperCase()); } - + } @@ -193,6 +187,17 @@ public class FeedsFragment extends BaseFeedlistFragment implements OnItemClickLi } @SuppressLint("DefaultLocale") + static class SpecialOrderComparator implements Comparator<Feed> { + static List<Integer> 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<Feed> { @Override @@ -222,46 +227,44 @@ public class FeedsFragment extends BaseFeedlistFragment implements OnItemClickLi @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 tmpFeed = new Feed(feed); + + if (!neverOpenHeadlines(feed)) + tmpFeed.always_open_headlines = true; + + m_activity.onFeedSelected(tmpFeed); 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); 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); } @@ -269,22 +272,23 @@ public class FeedsFragment extends BaseFeedlistFragment implements OnItemClickLi @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) { + if (!feed.is_cat) menu.findItem(R.id.browse_feeds).setVisible(false); - } - if (feed.id <= 0) { + if (neverOpenHeadlines(feed)) + menu.findItem(R.id.browse_headlines).setVisible(false); + + if (feed.id <= 0 || feed.is_cat) menu.findItem(R.id.unsubscribe_feed).setVisible(false); - } super.onCreateContextMenu(menu, v, menuInfo); @@ -295,13 +299,8 @@ public class FeedsFragment extends BaseFeedlistFragment implements OnItemClickLi super.onCreate(savedInstanceState); if (savedInstanceState != null) { - ArrayList<Feed> list = savedInstanceState.getParcelableArrayList("m_feeds"); - - m_feeds.clear(); - m_feeds.addAll(list); - + m_rootFeed = savedInstanceState.getParcelable("m_feed"); m_selectedFeed = savedInstanceState.getParcelable("m_selectedFeed"); - m_activeCategory = savedInstanceState.getParcelable("m_activeCategory"); m_enableParentBtn = savedInstanceState.getBoolean("m_enableParentBtn"); } } @@ -310,9 +309,8 @@ public class FeedsFragment extends BaseFeedlistFragment implements OnItemClickLi public void onSaveInstanceState(Bundle out) { super.onSaveInstanceState(out); - out.putParcelableArrayList("m_feeds", m_feeds); + out.putParcelable("m_feed", m_rootFeed); out.putParcelable("m_selectedFeed", m_selectedFeed); - out.putParcelable("m_activeCategory", m_activeCategory); out.putBoolean("m_enableParentBtn", m_enableParentBtn); } @@ -322,26 +320,45 @@ public class FeedsFragment extends BaseFeedlistFragment implements OnItemClickLi 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); + registerForContextMenu(m_list); - initDrawerHeader(inflater, view, m_list, m_activity, m_prefs); + m_layoutManager = new LinearLayoutManager(m_activity.getApplicationContext()); + m_list.setLayoutManager(m_layoutManager); + m_list.setItemAnimator(new DefaultItemAnimator()); - if (m_enableParentBtn) { - View layout = inflater.inflate(R.layout.feeds_goback, m_list, false); + m_adapter = new FeedsAdapter(); + m_list.setAdapter(m_adapter); - layout.setOnClickListener(view1 -> m_activity.getSupportFragmentManager().popBackStack()); + TextView login = view.findViewById(R.id.drawer_header_login); - m_list.addHeaderView(layout, null, false); + if (login != null) { + login.setText(m_prefs.getString("login", "")); } - m_adapter = new FeedListAdapter(getActivity(), R.layout.feeds_row, m_feeds); - m_list.setAdapter(m_adapter); - m_list.setOnItemClickListener(this); + TextView server = view.findViewById(R.id.drawer_header_server); - registerForContextMenu(m_list); + if (server != null) { + try { + server.setText(new URL(m_prefs.getString("ttrss_url", "")).getHost()); + } catch (MalformedURLException e) { + server.setText(""); + } + } + + View settingsBtn = view.findViewById(R.id.drawer_settings_btn); + + if (settingsBtn != null) { + settingsBtn.setOnClickListener(v -> { + Intent intent = new Intent(getActivity(), + PreferencesActivity.class); + + startActivityForResult(intent, 0); + }); + } return view; } @@ -359,46 +376,24 @@ public class FeedsFragment extends BaseFeedlistFragment implements OnItemClickLi m_prefs.registerOnSharedPreferenceChangeListener(this); m_activity = (MasterActivity)activity; - } @Override public void onResume() { super.onResume(); - LoaderManager.getInstance(this).initLoader(Application.LOADER_FEEDS, null, this).forceLoad(); - + Log.d(TAG, "onResume"); + + setSelectedFeed(m_activity.getActiveFeed()); + + 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 (!isAdded()) + return; if (m_swipeLayout != null) { m_swipeLayout.setRefreshing(true); @@ -407,153 +402,250 @@ public class FeedsFragment extends BaseFeedlistFragment implements OnItemClickLi LoaderManager.getInstance(this).restartLoader(Application.LOADER_FEEDS, null, this).forceLoad(); } - private class FeedListAdapter extends ArrayAdapter<Feed> { - private final ArrayList<Feed> items; + private class FeedViewHolder extends RecyclerView.ViewHolder { - public static final int VIEW_NORMAL = 0; - public static final int VIEW_SELECTED = 1; + private View view; + private ImageView icon; + private TextView title; + private TextView unreadCounter; + private MaterialSwitch rowSwitch; - public static final int VIEW_COUNT = VIEW_SELECTED+1; + public FeedViewHolder(@NonNull View itemView) { + super(itemView); - public FeedListAdapter(Context context, int textViewResourceId, ArrayList<Feed> items) { - super(context, textViewResourceId, items); - this.items = items; + view = itemView; + icon = itemView.findViewById(R.id.icon); + title = itemView.findViewById(R.id.title); + unreadCounter = itemView.findViewById(R.id.unread_counter); + rowSwitch = itemView.findViewById(R.id.row_switch); } + } - @Override - public boolean isEmpty() { - return !m_enableParentBtn && super.isEmpty(); - } + private class FeedDiffUtilItemCallback extends DiffUtil.ItemCallback<Feed> { - @Override - public int getViewTypeCount() { - return VIEW_COUNT; + @Override + public boolean areItemsTheSame(@NonNull Feed oldItem, @NonNull Feed newItem) { + return oldItem.id == newItem.id; } @Override - public int getItemViewType(int position) { - Feed feed = items.get(position); + public boolean areContentsTheSame(@NonNull Feed oldItem, @NonNull Feed newItem) { + return oldItem.id == newItem.id && + oldItem.is_cat == newItem.is_cat && + oldItem.title.equals(newItem.title) && + oldItem.unread == newItem.unread; + } + } - if (/*!m_activity.isSmallScreen() &&*/ m_selectedFeed != null && feed.id == m_selectedFeed.id) { - return VIEW_SELECTED; - } else { - return VIEW_NORMAL; + protected class FeedsAdapter extends ListAdapter<Feed, FeedViewHolder> { + 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_TOGGLE_UNREAD = 4; + public static final int VIEW_DIVIDER = 5; + + protected FeedsAdapter() { + super(new FeedDiffUtilItemCallback()); + } + + @NonNull + @Override + public FeedViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + int layoutId = R.layout.feeds_row; + + switch (viewType) { + case VIEW_SELECTED: + layoutId = R.layout.feeds_row_selected; + break; + case VIEW_GOBACK: + layoutId = R.layout.feeds_row_goback; + break; + case VIEW_TOGGLE_UNREAD: + layoutId = R.layout.feeds_row_toggle; + break; + case VIEW_DIVIDER: + layoutId = R.layout.feeds_row_divider; + break; } + + return new FeedViewHolder(LayoutInflater.from(parent.getContext()).inflate(layoutId, parent, false)); } @Override - public View getView(int position, View convertView, ViewGroup parent) { - View v = convertView; + public void onBindViewHolder(@NonNull FeedViewHolder holder, int position) { + Feed feed = getItem(position); - Feed feed = items.get(position); + if (holder.icon != null) { + holder.icon.setImageResource(getIconForFeed(feed)); + } - if (v == null) { - int layoutId = R.layout.feeds_row; + if (holder.title != null) { + holder.title.setText(feed.title); - if (getItemViewType(position) == VIEW_SELECTED) { - layoutId = R.layout.feeds_row_selected; - } + if (feed.always_open_headlines || (!feed.is_cat && feed.id == -4)) { + holder.title.setTypeface(null, Typeface.BOLD); + } else { + holder.title.setTypeface(null, Typeface.NORMAL); + } - LayoutInflater vi = (LayoutInflater)getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE); - v = vi.inflate(layoutId, null); + } + if (holder.unreadCounter != null) { + holder.unreadCounter.setText(String.valueOf(feed.unread)); + holder.unreadCounter.setVisibility((feed.unread > 0) ? View.VISIBLE : View.INVISIBLE); } - ImageView icon = v.findViewById(R.id.icon); - - if (icon != null) { - if (feed.id == 0 && !feed.is_cat) { - icon.setImageResource(R.drawable.baseline_archive_24); - } else if (feed.id == -1 && !feed.is_cat) { - icon.setImageResource(R.drawable.baseline_star_24); - } else if (feed.id == -2 && !feed.is_cat) { - icon.setImageResource(R.drawable.rss); - } else if (feed.id == -3 && !feed.is_cat) { - icon.setImageResource(R.drawable.baseline_local_fire_department_24); - } else if (feed.id == -4 && !feed.is_cat) { - icon.setImageResource(R.drawable.baseline_inbox_24); - } else if (feed.id == -6 && !feed.is_cat) { - icon.setImageResource(R.drawable.baseline_restore_24); - } else if (feed.is_cat) { - icon.setImageResource(R.drawable.baseline_folder_open_24); - } else { - icon.setImageResource(R.drawable.rss); - } + // there's only one kind of row with checkbox atm + if (holder.rowSwitch != null) { + holder.rowSwitch.setChecked(m_activity.getUnreadOnly()); + + holder.rowSwitch.setOnCheckedChangeListener((button, isChecked) -> { + m_activity.setUnreadOnly(isChecked); + }); } - TextView tt = v.findViewById(R.id.title); + 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; + }); + + // default open handler (i.e. tap) + holder.view.setOnClickListener(view -> { + if (feed.id == Feed.TYPE_GOBACK) { + m_activity.getSupportFragmentManager().popBackStack(); + } else if (feed.id == Feed.TYPE_TOGGLE_UNREAD || feed.id == Feed.TYPE_DIVIDER) { + // + } else { + Feed tmpFeed = new Feed(feed); - if (tt != null) { - tt.setText(feed.display_title != null ? feed.display_title : feed.title); + if (!neverOpenHeadlines(feed) && !tmpFeed.always_open_headlines) + tmpFeed.always_open_headlines = m_prefs.getBoolean("browse_cats_like_feeds", false); - if (feed.always_display_as_feed || (!feed.is_cat && feed.id == -4)) { - tt.setTypeface(null, Typeface.BOLD); - } else { - tt.setTypeface(null, Typeface.NORMAL); - } + m_activity.onFeedSelected(tmpFeed); + } + }); + } + @Override + public int getItemViewType(int position) { + Feed feed = getItem(position); + + if (feed.id == Feed.TYPE_GOBACK) { + return VIEW_GOBACK; + } else if (feed.id == Feed.TYPE_DIVIDER) { + return VIEW_DIVIDER; + } else if (feed.id == Feed.TYPE_TOGGLE_UNREAD) { + return VIEW_TOGGLE_UNREAD; + } else if (m_selectedFeed != null && feed.id == m_selectedFeed.id && feed.is_cat == m_selectedFeed.is_cat) { + return VIEW_SELECTED; + } else { + return VIEW_NORMAL; } + } - TextView tu = v.findViewById(R.id.unread_counter); + public int getPositionOf(Feed feed) { + List<Feed> feeds = getCurrentList(); - if (tu != null) { - tu.setText(String.valueOf(feed.unread)); - tu.setVisibility((feed.unread > 0) ? View.VISIBLE : View.INVISIBLE); - } + return IntStream.range(0, feeds.size()) + .sequential() + .filter(i -> { + Feed f = feeds.get(i); - return v; + return f.id == feed.id && f.is_cat == feed.is_cat; + }) + .findFirst() + .orElse(-1); } } - public void sortFeeds() { + /** we always show Labels and Special contents, regardless of the setting */ + private boolean neverOpenHeadlines(Feed feed) { + return feed.id == Feed.CAT_SPECIAL || feed.id == Feed.CAT_LABELS; + } + + protected void sortFeeds(List<Feed> feeds, Feed feed) { Comparator<Feed> cmp; - - if (m_prefs.getBoolean("sort_feeds_by_unread", false)) { - cmp = new FeedUnreadComparator(); + + if (feed.id == -1) { + cmp = new SpecialOrderComparator(); } else { - if (m_activity.getApiLevel() >= 3) { - cmp = new FeedOrderComparator(); + if (m_prefs.getBoolean("sort_feeds_by_unread", false)) { + cmp = new FeedUnreadComparator(); } else { - cmp = new FeedTitleComparator(); + if (m_activity.getApiLevel() >= 3) { + cmp = new FeedOrderComparator(); + } else { + cmp = new FeedTitleComparator(); + } } } - + try { - m_feeds.sort(cmp); + feeds.sort(cmp); } catch (IllegalArgumentException e) { - // sort order got changed in prefs or something - e.printStackTrace(); + // } + } - try { - m_adapter.notifyDataSetChanged(); - } catch (NullPointerException e) { - // adapter missing + 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) { - sortFeeds(); - } + // Can't access ViewModels from detached fragment (= backstack) + if (isAdded()) { + String[] filter = new String[] { "sort_feeds_by_unread", "show_unread_only" }; - public Feed getFeedAtPosition(int position) { - try { - return (Feed) m_list.getItemAtPosition(position); - } catch (ArrayIndexOutOfBoundsException e) { - e.printStackTrace(); + if (Arrays.asList(filter).contains(key)) + refresh(); } - - return null; } - public void setSelectedfeed(Feed feed) { - m_selectedFeed = feed; - + public void setSelectedFeed(Feed feed) { if (m_adapter != null) { - m_adapter.notifyDataSetChanged(); + + int oldPosition = -1; + + if (m_selectedFeed != null) + oldPosition = m_adapter.getPositionOf(m_selectedFeed); + + int newPosition = m_adapter.getPositionOf(feed); + + m_selectedFeed = feed; + + if (oldPosition != -1) + m_adapter.notifyItemChanged(oldPosition); + + if (newPosition != -1) + m_adapter.notifyItemChanged(newPosition); + } } } diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/FeedsLoader.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/FeedsLoader.java deleted file mode 100644 index 92ce64d2..00000000 --- a/org.fox.ttrss/src/main/java/org/fox/ttrss/FeedsLoader.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.fox.ttrss; - -import android.content.Context; - -import java.util.HashMap; - -class FeedsLoader extends ApiLoader { - private final int m_catId; - - public FeedsLoader(Context context, HashMap<String, String> params, int catId) { - super(context, params); - - m_catId = catId; - } - - public int getCatId() { - return m_catId; - } -} diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/HeadlinesEventListener.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/HeadlinesEventListener.java index 27e00bbf..14215a83 100644 --- a/org.fox.ttrss/src/main/java/org/fox/ttrss/HeadlinesEventListener.java +++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/HeadlinesEventListener.java @@ -1,7 +1,6 @@ package org.fox.ttrss; import org.fox.ttrss.types.Article; -import org.fox.ttrss.types.ArticleList; public interface HeadlinesEventListener { void onArticleListSelectionChange(); 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 8d15f5ec..a784ea8a 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; @@ -129,12 +130,17 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { } public void initialize(Feed feed) { + + // clear loaded headlines before switching feed + if (feed != m_feed) + Application.getArticlesModel().update(new ArticleList()); + m_feed = feed; } public void initialize(Feed feed, int activeArticleId, boolean compactMode) { m_feed = feed; - m_compactLayoutMode = compactMode; + m_compactLayoutMode = compactMode; m_activeArticleId = activeArticleId; } @@ -245,6 +251,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); @@ -293,7 +305,11 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { m_swipeLayout = view.findViewById(R.id.headlines_swipe_container); - m_swipeLayout.setOnRefreshListener(() -> refresh(false)); + // see below re: viewpager2 + if (!(m_activity instanceof DetailActivity)) + m_swipeLayout.setOnRefreshListener(() -> refresh(false)); + else + m_swipeLayout.setEnabled(false); m_list = view.findViewById(R.id.headlines_list); registerForContextMenu(m_list); @@ -309,7 +325,9 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { refresh(false); } - if (m_prefs.getBoolean("headlines_swipe_to_dismiss", true) /*&& !m_prefs.getBoolean("headlines_mark_read_scroll", false) */) { + // we disable this because default implementationof viewpager2 does not support removing/reordering/changing items + // https://stackoverflow.com/questions/69368198/delete-item-in-android-viewpager2 + if (m_prefs.getBoolean("headlines_swipe_to_dismiss", true) && !(m_activity instanceof DetailActivity)) { ItemTouchHelper swipeHelper = new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.RIGHT) { @@ -454,10 +472,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 @@ -555,6 +569,10 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { public void refresh(final boolean append) { ArticleModel model = Application.getArticlesModel(); + // we do not support non-append refreshes while in DetailActivity because of viewpager2 + if (m_activity instanceof DetailActivity && !append) + return; + if (!append) m_activeArticleId = -1; @@ -993,12 +1011,6 @@ public class HeadlinesFragment extends androidx.fragment.app.Fragment { // this is needed if our flavor image goes behind base listview element holder.headlineHeader.setOnClickListener(v -> { m_listener.onArticleSelected(article); - - // only set active article when it makes sense (in DetailActivity) - if (getActivity() instanceof DetailActivity) { - m_activeArticleId = article.id; - m_adapter.notifyDataSetChanged(); - } }); holder.headlineHeader.setOnLongClickListener(v -> { 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 458f1f76..bb750c01 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; @@ -45,8 +42,7 @@ public class MasterActivity extends OnlineActivity implements HeadlinesEventList protected long m_lastRefresh = 0; protected long m_lastWidgetRefresh = 0; - protected boolean m_feedIsSelected = false; - protected boolean m_userFeedSelected = false; + protected Feed m_activeFeed; 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,16 +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); - } + // 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 @@ -180,26 +173,23 @@ 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); } ft.commit(); - m_feedIsSelected = true; - } else { // savedInstanceState != null - m_feedIsSelected = savedInstanceState.getBoolean("m_feedIsSelected"); - m_userFeedSelected = savedInstanceState.getBoolean("m_userFeedSelected"); + m_activeFeed = savedInstanceState.getParcelable("m_activeFeed"); - if (m_drawerLayout != null && !m_feedIsSelected) { + if (m_drawerLayout != null && m_activeFeed == null) { m_drawerLayout.openDrawer(GravityCompat.START); } } @@ -246,6 +236,7 @@ public class MasterActivity extends OnlineActivity implements HeadlinesEventList protected void onPostCreate(Bundle savedInstanceState) { super.onPostCreate(savedInstanceState); + // Sync the toggle state after onRestoreInstanceState has occurred. if (m_drawerToggle != null) m_drawerToggle.syncState(); } @@ -256,91 +247,55 @@ 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) { - - FeedsFragment ff = (FeedsFragment) getSupportFragmentManager().findFragmentByTag(FRAG_FEEDS); - - if (ff != null && ff.isAdded()) { - ff.setSelectedfeed(feed); - } - - if (m_drawerLayout != null) { - m_drawerLayout.closeDrawers(); - } - - Application.getArticles().clear(); - - new Handler().postDelayed(() -> { - FragmentTransaction ft = getSupportFragmentManager() - .beginTransaction(); - - HeadlinesFragment hf = new HeadlinesFragment(); - hf.initialize(feed); - - ft.replace(R.id.headlines_fragment, hf, FRAG_HEADLINES); - - ft.commit(); - - m_feedIsSelected = true; - m_userFeedSelected = selectedByUser; - - }, 250); - - Date date = new Date(); - - 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); - } + public void onFeedSelected(Feed feed) { + // show subfolder of feeds below current level + if (feed.is_cat && !feed.always_open_headlines) { FragmentTransaction ft = getSupportFragmentManager() .beginTransaction(); FeedsFragment ff = new FeedsFragment(); - ff.initialize(cat, true); + ff.initialize(feed, true); ft.replace(R.id.feeds_fragment, ff, FRAG_FEEDS); ft.addToBackStack(null); ft.commit(); } else { - - if (fc != null) { - fc.setSelectedCategory(cat); + // actualy open the feed (i.e. show headlines) + + setActiveFeed(feed); + + if (m_drawerLayout != null) { + m_drawerLayout.closeDrawers(); } - Feed feed = new Feed(cat.id, cat.title, true); - onFeedSelected(feed); + HeadlinesFragment hf = (HeadlinesFragment) getSupportFragmentManager().findFragmentByTag(FRAG_HEADLINES); + + if (hf != null) { + hf.initialize(feed); + hf.refresh(false); + } else { + FragmentTransaction ft = getSupportFragmentManager() + .beginTransaction(); + + hf = new HeadlinesFragment(); + hf.initialize(feed); + + ft.replace(R.id.headlines_fragment, hf, FRAG_HEADLINES); + + ft.commit(); + } } } - - public void onCatSelected(FeedCategory cat) { - onCatSelected(cat, m_prefs.getBoolean("browse_cats_like_feeds", false)); - } + @Override public void logout() { @@ -407,7 +362,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 { @@ -431,8 +386,7 @@ public class MasterActivity extends OnlineActivity implements HeadlinesEventList public void onSaveInstanceState(Bundle out) { super.onSaveInstanceState(out); - out.putBoolean("m_feedIsSelected", m_feedIsSelected); - out.putBoolean("m_userFeedSelected", m_userFeedSelected); + out.putParcelable("m_activeFeed", m_activeFeed); Application.getInstance().save(out); } @@ -474,10 +428,6 @@ public class MasterActivity extends OnlineActivity implements HeadlinesEventList intent.putExtra("searchQuery", hf.getSearchQuery()); intent.putExtra("openedArticleId", article.id); - // we use shared article list, but detail activity does not use special footers - // we will append those back (if needed) in onActivityResult() - // Application.getArticles().stripFooters(); - startActivityForResult(intent, HEADLINES_REQUEST); overridePendingTransition(R.anim.slide_in_right, R.anim.slide_out_left); } @@ -511,10 +461,7 @@ public class MasterActivity extends OnlineActivity implements HeadlinesEventList } @Override - public void onHeadlinesLoaded(boolean appended) { - // TODO Auto-generated method stub - - } + public void onHeadlinesLoaded(boolean appended) { } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { @@ -553,4 +500,21 @@ public class MasterActivity extends OnlineActivity implements HeadlinesEventList } + public Feed getActiveFeed() { + return m_activeFeed; + } + + public void setActiveFeed(Feed feed) { + m_activeFeed = feed; + + setTitle(feed.title); + + FeedsFragment ff = (FeedsFragment) getSupportFragmentManager().findFragmentByTag(FRAG_FEEDS); + + if (ff != null) { + ff.setSelectedFeed(feed); + } + + } + } 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..b61a2736 --- /dev/null +++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/RootCategoriesFragment.java @@ -0,0 +1,138 @@ +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<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> { + + @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<Feed> 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 if (feed.id == Feed.CAT_LABELS) + return R.drawable.outline_label_24; + else if (feed.id == Feed.CAT_SPECIAL) + return R.drawable.baseline_folder_special_24; + else + return R.drawable.baseline_folder_open_24; + } + + @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<>(); + + sortFeeds(feedsJson, m_rootFeed); + + // 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)); + } + + if (m_activity.getUnreadOnly()) + feedsJson = feedsJson.stream() + .filter(f -> f.id == Feed.CAT_SPECIAL || f.unread > 0) + .collect(Collectors.toList()); + + feedsJson = feedsJson.stream() + .peek(f -> f.is_cat = true) + .collect(Collectors.toList()); + + 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()); + } + } + } + } +} + diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/share/CommonActivity.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/share/CommonActivity.java index 9c9187d9..7ece9e4f 100644 --- a/org.fox.ttrss/src/main/java/org/fox/ttrss/share/CommonActivity.java +++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/share/CommonActivity.java @@ -1,7 +1,6 @@ package org.fox.ttrss.share; import android.app.Activity; -import android.os.Bundle; import android.util.Log; import android.view.Display; import android.widget.Toast; diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/share/SubscribeActivity.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/share/SubscribeActivity.java index 82e2d52d..b894f213 100755 --- a/org.fox.ttrss/src/main/java/org/fox/ttrss/share/SubscribeActivity.java +++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/share/SubscribeActivity.java @@ -1,5 +1,6 @@ package org.fox.ttrss.share; +import android.annotation.SuppressLint; import android.content.Context; import android.content.Intent; import android.os.Bundle; @@ -21,16 +22,15 @@ import com.google.gson.reflect.TypeToken; import org.fox.ttrss.ApiCommon; import org.fox.ttrss.ApiRequest; import org.fox.ttrss.R; -import org.fox.ttrss.types.FeedCategory; -import org.fox.ttrss.types.FeedCategoryList; +import org.fox.ttrss.types.Feed; import java.lang.reflect.Type; import java.util.ArrayList; -import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; public class SubscribeActivity extends CommonShareActivity { private final String TAG = this.getClass().getSimpleName(); @@ -39,17 +39,17 @@ public class SubscribeActivity extends CommonShareActivity { private Button m_catButton; private CatListAdapter m_catAdapter; private FeedListAdapter m_feedAdapter; - private final FeedCategoryList m_cats = new FeedCategoryList(); + private final List<Feed> m_cats = new ArrayList<>(); private final ArrayList<Map.Entry<String, JsonElement>> m_feeds = new ArrayList<>(); private ProgressBar m_progressBar; private static final int REQ_CATS = 1; private static final int REQ_POST = 2; - static class CatTitleComparator implements Comparator<FeedCategory> { + static class TitleComparator implements Comparator<Feed> { @Override - public int compare(FeedCategory a, FeedCategory b) { + public int compare(Feed a, Feed b) { if (a.id >= 0 && b.id >= 0) return a.title.compareTo(b.title); else @@ -59,13 +59,10 @@ public class SubscribeActivity extends CommonShareActivity { } public void sortCats() { - Comparator<FeedCategory> cmp = new CatTitleComparator(); - - Collections.sort(m_cats, cmp); - try { + + if (m_catAdapter != null) { + m_cats.sort(new TitleComparator()); m_catAdapter.notifyDataSetChanged(); - } catch (NullPointerException e) { - // adapter missing } } @@ -82,30 +79,26 @@ public class SubscribeActivity extends CommonShareActivity { if (savedInstanceState != null) { urlValue = savedInstanceState.getString("url"); - - ArrayList<FeedCategory> list = savedInstanceState.getParcelableArrayList("cats"); - - m_cats.addAll(list); } setContentView(R.layout.activity_subscribe); setSmallScreen(false); - m_progressBar = (ProgressBar) findViewById(R.id.subscribe_progress); - Spinner catList = (Spinner) findViewById(R.id.category_spinner); + m_progressBar = findViewById(R.id.subscribe_progress); + Spinner catList = findViewById(R.id.category_spinner); - if (m_cats.isEmpty()) m_cats.add(new FeedCategory(0, "Uncategorized", 0)); + if (m_cats.isEmpty()) m_cats.add(new Feed(0, "Uncategorized", true)); m_catAdapter = new CatListAdapter(this, android.R.layout.simple_spinner_dropdown_item, m_cats); catList.setAdapter(m_catAdapter); - final Spinner feedList = (Spinner) findViewById(R.id.feed_spinner); + final Spinner feedList = findViewById(R.id.feed_spinner); feedList.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { String feed = m_feedAdapter.getItemURL(position); - EditText feedUrl = (EditText) findViewById(R.id.feed_url); + EditText feedUrl = findViewById(R.id.feed_url); if (feed != null && feedUrl != null) { feedUrl.setText(feed); @@ -140,24 +133,22 @@ public class SubscribeActivity extends CommonShareActivity { public void onSaveInstanceState(Bundle out) { super.onSaveInstanceState(out); - EditText url = (EditText) findViewById(R.id.url); + EditText url = findViewById(R.id.url); if (url != null) { out.putString("url", url.getText().toString()); } - - out.putParcelableArrayList("cats", m_cats); } private void subscribeToFeed() { m_postButton.setEnabled(false); - ApiRequest req = new ApiRequest(getApplicationContext()) { + @SuppressLint("StaticFieldLeak") ApiRequest req = new ApiRequest(getApplicationContext()) { protected void onPostExecute(JsonElement result) { m_progressBar.setVisibility(View.INVISIBLE); - if (m_lastError != ApiCommon.ApiError.UNKNOWN_ERROR) { + if (m_lastError != null && m_lastError != ApiCommon.ApiError.SUCCESS) { toast(getErrorMessage()); } else { try { @@ -166,7 +157,7 @@ public class SubscribeActivity extends CommonShareActivity { try { rc = result.getAsJsonObject().get("status").getAsJsonObject().get("code").getAsInt(); } catch (Exception e) { - e.printStackTrace(); + toast(e.getMessage()); } switch (rc) { @@ -212,8 +203,7 @@ public class SubscribeActivity extends CommonShareActivity { } } catch (Exception e) { - toast(R.string.error_while_subscribing); - e.printStackTrace(); + toast(e.getMessage()); } } @@ -221,10 +211,10 @@ public class SubscribeActivity extends CommonShareActivity { } }; - Spinner catSpinner = (Spinner) findViewById(R.id.category_spinner); + Spinner catSpinner = findViewById(R.id.category_spinner); - final FeedCategory cat = (FeedCategory) m_catAdapter.getCategory(catSpinner.getSelectedItemPosition()); - final EditText feedUrl = (EditText) findViewById(R.id.feed_url); + final Feed cat = m_catAdapter.getCategory(catSpinner.getSelectedItemPosition()); + final EditText feedUrl = findViewById(R.id.feed_url); if (feedUrl != null ) { HashMap<String, String> map = new HashMap<>(); @@ -255,29 +245,25 @@ public class SubscribeActivity extends CommonShareActivity { } private void updateCats() { - ApiRequest req = new ApiRequest(getApplicationContext()) { + @SuppressLint("StaticFieldLeak") ApiRequest req = new ApiRequest(getApplicationContext()) { protected void onPostExecute(JsonElement result) { m_progressBar.setVisibility(View.INVISIBLE); - if (m_lastError != ApiCommon.ApiError.UNKNOWN_ERROR) { + if (m_lastError != null && m_lastError != ApiCommon.ApiError.SUCCESS) { toast(getErrorMessage()); } else { JsonArray content = result.getAsJsonArray(); if (content != null) { - Type listType = new TypeToken<List<FeedCategory>>() {}.getType(); - final List<FeedCategory> cats = new Gson().fromJson(content, listType); - + Type listType = new TypeToken<List<Feed>>() {}.getType(); + final List<Feed> catsJson = new Gson().fromJson(content, listType); + m_cats.clear(); - - for (FeedCategory c : cats) { - if (c.id > 0) - m_cats.add(c); - } - + m_cats.addAll(catsJson.stream().filter(f -> f.id > 0).collect(Collectors.toList())); + sortCats(); - m_cats.add(0, new FeedCategory(0, "Uncategorized", 0)); + m_cats.add(0, new Feed(0, "Uncategorized", true)); m_catAdapter.notifyDataSetChanged(); @@ -316,10 +302,10 @@ public class SubscribeActivity extends CommonShareActivity { } private static class CatListAdapter extends ArrayAdapter<String> { - private final List<FeedCategory> m_items; + private final List<Feed> m_items; public CatListAdapter(Context context, int resource, - List<FeedCategory> items) { + List<Feed> items) { super(context, resource); m_items = items; @@ -330,7 +316,7 @@ public class SubscribeActivity extends CommonShareActivity { return m_items.get(item).title; } - public FeedCategory getCategory(int item) { + public Feed getCategory(int item) { try { return m_items.get(item); } catch (ArrayIndexOutOfBoundsException e) { diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/types/Article.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/types/Article.java index 55a2bf42..43a46d92 100755 --- a/org.fox.ttrss/src/main/java/org/fox/ttrss/types/Article.java +++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/types/Article.java @@ -319,7 +319,6 @@ public class Article implements Parcelable { if (note == null) note = ""; if (link == null) link = ""; if (tags == null) tags = new ArrayList<>(); - if (note == null) note = ""; if (excerpt == null) excerpt = ""; if (content == null) content = ""; if (comments_link == null) comments_link = ""; diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/types/ArticleList.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/types/ArticleList.java index 6724acb0..8a8a763f 100755 --- a/org.fox.ttrss/src/main/java/org/fox/ttrss/types/ArticleList.java +++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/types/ArticleList.java @@ -1,6 +1,5 @@ package org.fox.ttrss.types; -import java.util.ListIterator; import java.util.concurrent.CopyOnWriteArrayList; import java.util.stream.Collectors; 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 9cbe9f83..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,12 +1,15 @@ 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<Feed>, Parcelable { + public static final int TYPE_GOBACK = -10001; + public static final int TYPE_DIVIDER = -10002; + public static final int TYPE_TOGGLE_UNREAD = -10003; + public String feed_url; public String title; public int id; @@ -16,33 +19,75 @@ public class Feed implements Comparable<Feed>, 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; + this.title = "ID:" + id; + this.is_cat = false; + } + public Feed(int id, String title, boolean is_cat) { this.id = id; this.title = title; 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) { @@ -87,8 +132,7 @@ public class Feed implements Comparable<Feed>, 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) { @@ -101,8 +145,7 @@ public class Feed implements Comparable<Feed>, 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/java/org/fox/ttrss/types/FeedCategory.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/types/FeedCategory.java deleted file mode 100644 index c8193f94..00000000 --- a/org.fox.ttrss/src/main/java/org/fox/ttrss/types/FeedCategory.java +++ /dev/null @@ -1,58 +0,0 @@ -package org.fox.ttrss.types; - -import android.os.Parcel; -import android.os.Parcelable; - -public class FeedCategory implements Parcelable { - public int id; - public String title; - public int unread; - public int order_id; - - public FeedCategory(Parcel in) { - readFromParcel(in); - } - - public FeedCategory(int id, String title, int unread) { - this.id = id; - this.title = title; - this.unread = unread; - this.order_id = 0; - } - - public FeedCategory() { - - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeInt(id); - out.writeString(title); - out.writeInt(unread); - out.writeInt(order_id); - } - - public void readFromParcel(Parcel in) { - id = in.readInt(); - title = in.readString(); - unread = in.readInt(); - order_id = in.readInt(); - } - - @SuppressWarnings("rawtypes") - public static final Parcelable.Creator CREATOR = - new Parcelable.Creator() { - public FeedCategory createFromParcel(Parcel in) { - return new FeedCategory(in); - } - - public FeedCategory[] newArray(int size) { - return new FeedCategory[size]; - } - }; -} diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/types/FeedCategoryList.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/types/FeedCategoryList.java deleted file mode 100644 index ba8caaec..00000000 --- a/org.fox.ttrss/src/main/java/org/fox/ttrss/types/FeedCategoryList.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.fox.ttrss.types; - -import android.os.Parcel; -import android.os.Parcelable; - -import java.util.ArrayList; - -public class FeedCategoryList extends ArrayList<FeedCategory> implements Parcelable { - - public FeedCategoryList() { } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeList(this); - } - - public void readFromParcel(Parcel in) { - in.readList(this, getClass().getClassLoader()); - } - - public FeedCategoryList(Parcel in) { - readFromParcel(in); - } - - @SuppressWarnings("rawtypes") - public static final Parcelable.Creator CREATOR = - new Parcelable.Creator() { - public FeedCategoryList createFromParcel(Parcel in) { - return new FeedCategoryList(in); - } - - public FeedCategoryList[] newArray(int size) { - return new FeedCategoryList[size]; - } - }; - } diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/types/FeedList.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/types/FeedList.java deleted file mode 100644 index 08164361..00000000 --- a/org.fox.ttrss/src/main/java/org/fox/ttrss/types/FeedList.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.fox.ttrss.types; - -import android.os.Parcel; -import android.os.Parcelable; - -import java.util.ArrayList; - -public class FeedList extends ArrayList<Feed> implements Parcelable { - - public FeedList() { } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeList(this); - } - - public void readFromParcel(Parcel in) { - in.readList(this, getClass().getClassLoader()); - } - - public FeedList(Parcel in) { - readFromParcel(in); - } - - @SuppressWarnings("rawtypes") - public static final Parcelable.Creator CREATOR = - new Parcelable.Creator() { - public FeedList createFromParcel(Parcel in) { - return new FeedList(in); - } - - public FeedList[] newArray(int size) { - return new FeedList[size]; - } - }; - } diff --git a/org.fox.ttrss/src/main/res/drawable/baseline_folder_special_24.xml b/org.fox.ttrss/src/main/res/drawable/baseline_folder_special_24.xml new file mode 100644 index 00000000..d6017f8c --- /dev/null +++ b/org.fox.ttrss/src/main/res/drawable/baseline_folder_special_24.xml @@ -0,0 +1,5 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp"> + + <path android:fillColor="@android:color/white" android:pathData="M20,6h-8l-2,-2L4,4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,8c0,-1.1 -0.9,-2 -2,-2zM17.94,17L15,15.28 12.06,17l0.78,-3.33 -2.59,-2.24 3.41,-0.29L15,8l1.34,3.14 3.41,0.29 -2.59,2.24 0.78,3.33z"/> + +</vector> 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 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp"> + + <path android:fillColor="@android:color/white" android:pathData="M17.63,5.84C17.27,5.33 16.67,5 16,5L5,5.01C3.9,5.01 3,5.9 3,7v10c0,1.1 0.9,1.99 2,1.99L16,19c0.67,0 1.27,-0.33 1.63,-0.84L22,12l-4.37,-6.16z"/> + +</vector> diff --git a/org.fox.ttrss/src/main/res/drawable/outline_folder_special_24.xml b/org.fox.ttrss/src/main/res/drawable/outline_folder_special_24.xml new file mode 100644 index 00000000..3add00b7 --- /dev/null +++ b/org.fox.ttrss/src/main/res/drawable/outline_folder_special_24.xml @@ -0,0 +1,5 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp"> + + <path android:fillColor="@android:color/white" android:pathData="M20,6h-8l-2,-2L4,4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,8c0,-1.1 -0.9,-2 -2,-2zM20,18L4,18L4,6h5.17l2,2L20,8v10zM13.08,14.04L12.39,17 15,15.47 17.61,17l-0.69,-2.96 2.3,-1.99 -3.03,-0.26L15,9l-1.19,2.79 -3.03,0.26z"/> + +</vector> diff --git a/org.fox.ttrss/src/main/res/drawable/outline_label_24.xml b/org.fox.ttrss/src/main/res/drawable/outline_label_24.xml new file mode 100644 index 00000000..f05b7a20 --- /dev/null +++ b/org.fox.ttrss/src/main/res/drawable/outline_label_24.xml @@ -0,0 +1,5 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp"> + + <path android:fillColor="@android:color/white" android:pathData="M17.63,5.84C17.27,5.33 16.67,5 16,5L5,5.01C3.9,5.01 3,5.9 3,7v10c0,1.1 0.9,1.99 2,1.99L16,19c0.67,0 1.27,-0.33 1.63,-0.84L22,12l-4.37,-6.16zM16,17H5V7h11l3.55,5L16,17z"/> + +</vector> diff --git a/org.fox.ttrss/src/main/res/layout/drawer_header.xml b/org.fox.ttrss/src/main/res/layout/drawer_header.xml deleted file mode 100755 index f4954259..00000000 --- a/org.fox.ttrss/src/main/res/layout/drawer_header.xml +++ /dev/null @@ -1,70 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="72dp" - xmlns:tools="http://schemas.android.com/tools" - xmlns:app="http://schemas.android.com/apk/res-auto" - android:orientation="vertical" - android:weightSum="1" - android:clickable="false"> - - <FrameLayout - android:layout_width="match_parent" - android:layout_height="match_parent"> - - <com.google.android.material.button.MaterialButton - style="?attr/materialIconButtonStyle" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:scaleX="1.5" - android:scaleY="1.5" - app:icon="@drawable/baseline_settings_24" - android:id="@+id/drawer_settings_btn" - android:layout_gravity="center_vertical|end" - android:layout_marginEnd="8dp" - android:transitionName="SETTINGS_REVEAL" - /> - - <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical" - android:layout_alignParentBottom="true" - android:layout_alignParentLeft="true" - android:layout_alignParentStart="true" - android:id="@+id/linearLayout" - android:layout_gravity="center_horizontal|bottom"> - - <TextView - android:id="@+id/drawer_header_login" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginStart="16dp" - tools:text="user" - android:textAppearance="?attr/textAppearanceHeadlineSmall" - android:textColor="?attr/colorOnSurface"/> - - <TextView - android:id="@+id/drawer_header_server" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginStart="16dp" - android:layout_marginTop="8dp" - android:layout_marginBottom="8dp" - tools:text="example.org" - android:textAppearance="?attr/textAppearanceTitleSmall" - android:textColor="?attr/colorOnSurfaceVariant"/> - </LinearLayout> - </FrameLayout> - - <ProgressBar - style="?android:attr/progressBarStyleHorizontal" - android:visibility="invisible" - android:indeterminate="true" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:id="@+id/feeds_loading_bar" - android:layout_alignParentLeft="true" - android:layout_alignParentBottom="true" - android:layout_gravity="bottom" /> -</FrameLayout>
\ No newline at end of file diff --git a/org.fox.ttrss/src/main/res/layout/drawer_divider.xml b/org.fox.ttrss/src/main/res/layout/feeds_row_divider.xml index 55aa5fdc..55aa5fdc 100644 --- a/org.fox.ttrss/src/main/res/layout/drawer_divider.xml +++ b/org.fox.ttrss/src/main/res/layout/feeds_row_divider.xml diff --git a/org.fox.ttrss/src/main/res/layout/feeds_goback.xml b/org.fox.ttrss/src/main/res/layout/feeds_row_goback.xml index 75ba8fe1..75ba8fe1 100755 --- a/org.fox.ttrss/src/main/res/layout/feeds_goback.xml +++ b/org.fox.ttrss/src/main/res/layout/feeds_row_goback.xml 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 @@ <?xml version="1.0" encoding="utf-8"?> -<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" +<androidx.coordinatorlayout.widget.CoordinatorLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" android:id="@+id/feeds_fragment" - android:layout_width="fill_parent" - android:layout_height="fill_parent" > + android:layout_width="match_parent" + android:layout_height="wrap_content"> - <androidx.swiperefreshlayout.widget.SwipeRefreshLayout - android:id="@+id/feeds_swipe_container" - android:layout_width="match_parent" - android:layout_height="match_parent"> - - <ListView - android:id="@+id/feeds" - android:dividerHeight="4dp" - android:divider="@null" - android:paddingStart="4dp" - android:paddingEnd="4dp" + <com.google.android.material.appbar.AppBarLayout + android:background="@null" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <FrameLayout + app:layout_scrollFlags="scroll|enterAlways" android:layout_width="match_parent" - android:layout_height="match_parent" - android:layout_alignParentLeft="true" - android:layout_alignParentStart="true"> - </ListView> + android:layout_height="wrap_content"> + + <com.google.android.material.button.MaterialButton + style="?attr/materialIconButtonStyle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:scaleX="1.5" + android:scaleY="1.5" + app:icon="@drawable/baseline_settings_24" + android:id="@+id/drawer_settings_btn" + android:layout_gravity="center_vertical|end" + android:layout_marginEnd="8dp" + android:transitionName="SETTINGS_REVEAL" + /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:layout_alignParentBottom="true" + android:layout_alignParentLeft="true" + android:layout_alignParentStart="true" + android:id="@+id/linearLayout" + android:layout_gravity="center_horizontal|bottom"> + + <TextView + android:id="@+id/drawer_header_login" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="16dp" + tools:text="user" + android:textAppearance="?attr/textAppearanceHeadlineSmall" + android:textColor="?attr/colorOnSurface"/> + + <TextView + android:id="@+id/drawer_header_server" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="16dp" + android:layout_marginTop="8dp" + android:layout_marginBottom="8dp" + tools:text="example.org" + android:textAppearance="?attr/textAppearanceTitleSmall" + android:textColor="?attr/colorOnSurfaceVariant"/> + </LinearLayout> + </FrameLayout> + + </com.google.android.material.appbar.AppBarLayout> + + <androidx.swiperefreshlayout.widget.SwipeRefreshLayout + android:id="@+id/feeds_swipe_container" + app:layout_behavior="@string/appbar_scrolling_view_behavior" + 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" /> + </androidx.swiperefreshlayout.widget.SwipeRefreshLayout> -</RelativeLayout>
\ No newline at end of file + +</androidx.coordinatorlayout.widget.CoordinatorLayout> 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 @@ -44,13 +44,7 @@ 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" /> |