From c23274c4ccc0812eab331f08e48f0aae0981e25c Mon Sep 17 00:00:00 2001 From: Roy Yosef Date: Sat, 9 May 2020 14:17:51 +0300 Subject: [PATCH] feat(group-feed-tab) * add group feed as tab in the main page (by Settings > Content > Content of main page) --- .../settings/SelectChannelFragment.java | 12 +- .../settings/SelectFeedGroupFragment.java | 205 ++++++++++++++++++ .../newpipe/settings/SelectKioskFragment.java | 12 +- .../settings/tabs/ChooseTabsFragment.java | 26 ++- .../org/schabi/newpipe/settings/tabs/Tab.java | 91 +++++++- .../res/layout/select_feed_group_fragment.xml | 43 ++++ .../res/layout/select_feed_group_item.xml | 35 +++ app/src/main/res/values/strings.xml | 3 + 8 files changed, 406 insertions(+), 21 deletions(-) create mode 100644 app/src/main/java/org/schabi/newpipe/settings/SelectFeedGroupFragment.java create mode 100644 app/src/main/res/layout/select_feed_group_fragment.xml create mode 100644 app/src/main/res/layout/select_feed_group_item.xml diff --git a/app/src/main/java/org/schabi/newpipe/settings/SelectChannelFragment.java b/app/src/main/java/org/schabi/newpipe/settings/SelectChannelFragment.java index 9ac3e2eda2b..c4ed03e983f 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/SelectChannelFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/SelectChannelFragment.java @@ -62,7 +62,7 @@ public class SelectChannelFragment extends DialogFragment { private final ImageLoader imageLoader = ImageLoader.getInstance(); - private OnSelectedLisener onSelectedLisener = null; + private OnSelectedListener onSelectedListener = null; private OnCancelListener onCancelListener = null; private ProgressBar progressBar; @@ -71,8 +71,8 @@ public class SelectChannelFragment extends DialogFragment { private List subscriptions = new Vector<>(); - public void setOnSelectedLisener(final OnSelectedLisener listener) { - onSelectedLisener = listener; + public void setOnSelectedListener(final OnSelectedListener listener) { + onSelectedListener = listener; } public void setOnCancelListener(final OnCancelListener listener) { @@ -121,9 +121,9 @@ public void onCancel(final DialogInterface dialogInterface) { } private void clickedItem(final int position) { - if (onSelectedLisener != null) { + if (onSelectedListener != null) { SubscriptionEntity entry = subscriptions.get(position); - onSelectedLisener + onSelectedListener .onChannelSelected(entry.getServiceId(), entry.getUrl(), entry.getName()); } dismiss(); @@ -178,7 +178,7 @@ protected void onError(final Throwable e) { // Interfaces //////////////////////////////////////////////////////////////////////////*/ - public interface OnSelectedLisener { + public interface OnSelectedListener { void onChannelSelected(int serviceId, String url, String name); } diff --git a/app/src/main/java/org/schabi/newpipe/settings/SelectFeedGroupFragment.java b/app/src/main/java/org/schabi/newpipe/settings/SelectFeedGroupFragment.java new file mode 100644 index 00000000000..e3367bcebc4 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/settings/SelectFeedGroupFragment.java @@ -0,0 +1,205 @@ +package org.schabi.newpipe.settings; + +import android.app.Activity; +import android.content.Context; +import android.content.DialogInterface; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.ProgressBar; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.fragment.app.DialogFragment; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import org.schabi.newpipe.R; +import org.schabi.newpipe.database.feed.model.FeedGroupEntity; +import org.schabi.newpipe.local.feed.FeedDatabaseManager; +import org.schabi.newpipe.report.ErrorActivity; +import org.schabi.newpipe.report.UserAction; + +import java.util.List; +import java.util.Vector; + +import io.reactivex.disposables.Disposable; + +/** + * Created by Christian Schabesberger on 26.09.17. + * SelectChannelFragment.java is part of NewPipe. + *

+ * NewPipe is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + *

+ *

+ * NewPipe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + *

+ *

+ * You should have received a copy of the GNU General Public License + * along with NewPipe. If not, see . + *

+ */ + +public class SelectFeedGroupFragment extends DialogFragment { + private OnSelectedListener onSelectedListener = null; + private OnCancelListener onCancelListener = null; + + private ProgressBar progressBar; + private TextView emptyView; + private RecyclerView recyclerView; + + private List feedGroups = new Vector<>(); + private Disposable feedGroupsSubscriber; + + public void setOnSelectedListener(final OnSelectedListener listener) { + onSelectedListener = listener; + } + + public void setOnCancelListener(final OnCancelListener listener) { + onCancelListener = listener; + } + + /*////////////////////////////////////////////////////////////////////////// + // Fragment's Lifecycle + //////////////////////////////////////////////////////////////////////////*/ + + @Override + public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup container, + final Bundle savedInstanceState) { + final View v = inflater.inflate( + R.layout.select_feed_group_fragment, container, false); + recyclerView = v.findViewById(R.id.items_list); + recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); + SelectFeedGroupAdapter channelAdapter = new SelectFeedGroupAdapter(); + recyclerView.setAdapter(channelAdapter); + + progressBar = v.findViewById(R.id.progressBar); + emptyView = v.findViewById(R.id.empty_state_view); + progressBar.setVisibility(View.VISIBLE); + recyclerView.setVisibility(View.GONE); + emptyView.setVisibility(View.GONE); + + final FeedDatabaseManager feedDatabaseManager = new FeedDatabaseManager(getContext()); + feedGroupsSubscriber = feedDatabaseManager.groups().toObservable() + .subscribe(this::displayFeedGroups, this::onError); + + return v; + } + + @Override + public void onDestroy() { + super.onDestroy(); + + if (feedGroupsSubscriber != null) { + feedGroupsSubscriber.dispose(); + feedGroupsSubscriber = null; + } + } + + /*////////////////////////////////////////////////////////////////////////// + // Handle actions + //////////////////////////////////////////////////////////////////////////*/ + + @Override + public void onCancel(final DialogInterface dialogInterface) { + super.onCancel(dialogInterface); + if (onCancelListener != null) { + onCancelListener.onCancel(); + } + } + + private void clickedItem(final int position) { + if (onSelectedListener != null) { + FeedGroupEntity entry = feedGroups.get(position); + onSelectedListener + .onFeedGroupSelected(entry.getUid(), entry.getName(), + entry.getIcon().getDrawableResourceAttr()); + } + dismiss(); + } + + /*////////////////////////////////////////////////////////////////////////// + // Item handling + //////////////////////////////////////////////////////////////////////////*/ + + private void displayFeedGroups(final List newFeedGroups) { + this.feedGroups = newFeedGroups; + progressBar.setVisibility(View.GONE); + if (newFeedGroups.isEmpty()) { + emptyView.setVisibility(View.VISIBLE); + return; + } + + recyclerView.setVisibility(View.VISIBLE); + } + + /*////////////////////////////////////////////////////////////////////////// + // Error + //////////////////////////////////////////////////////////////////////////*/ + + protected void onError(final Throwable e) { + final Activity activity = getActivity(); + ErrorActivity.reportError(activity, e, activity.getClass(), null, ErrorActivity.ErrorInfo + .make(UserAction.UI_ERROR, "none", "", R.string.app_ui_crash)); + } + + /*////////////////////////////////////////////////////////////////////////// + // Interfaces + //////////////////////////////////////////////////////////////////////////*/ + + public interface OnSelectedListener { + void onFeedGroupSelected(long id, String name, int thumbnailId); + } + + public interface OnCancelListener { + void onCancel(); + } + + private class SelectFeedGroupAdapter + extends RecyclerView.Adapter { + @Override + public SelectFeedGroupItemHolder onCreateViewHolder(final ViewGroup parent, + final int viewType) { + final View item = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.select_feed_group_item, parent, false); + + return new SelectFeedGroupItemHolder(item); + } + + @Override + public void onBindViewHolder(final SelectFeedGroupItemHolder holder, final int position) { + final FeedGroupEntity entry = feedGroups.get(position); + holder.titleView.setText(entry.getName()); + holder.view.setOnClickListener(view -> clickedItem(position)); + + final Context context = SelectFeedGroupFragment.this.getContext(); + holder.thumbnailView.setImageResource(entry.getIcon().getDrawableRes(context)); + } + + @Override + public int getItemCount() { + return feedGroups.size(); + } + + public class SelectFeedGroupItemHolder extends RecyclerView.ViewHolder { + public final View view; + final ImageView thumbnailView; + final TextView titleView; + + SelectFeedGroupItemHolder(final View v) { + super(v); + this.view = v; + thumbnailView = v.findViewById(R.id.itemThumbnailView); + titleView = v.findViewById(R.id.itemTitleView); + } + } + } +} diff --git a/app/src/main/java/org/schabi/newpipe/settings/SelectKioskFragment.java b/app/src/main/java/org/schabi/newpipe/settings/SelectKioskFragment.java index cb148c8436b..57a993efed7 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/SelectKioskFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/SelectKioskFragment.java @@ -50,11 +50,11 @@ public class SelectKioskFragment extends DialogFragment { private RecyclerView recyclerView = null; private SelectKioskAdapter selectKioskAdapter = null; - private OnSelectedLisener onSelectedLisener = null; + private OnSelectedListener onSelectedListener = null; private OnCancelListener onCancelListener = null; - public void setOnSelectedLisener(final OnSelectedLisener listener) { - onSelectedLisener = listener; + public void setOnSelectedListener(final OnSelectedListener listener) { + onSelectedListener = listener; } public void setOnCancelListener(final OnCancelListener listener) { @@ -90,8 +90,8 @@ public void onCancel(final DialogInterface dialogInterface) { } private void clickedItem(final SelectKioskAdapter.Entry entry) { - if (onSelectedLisener != null) { - onSelectedLisener.onKioskSelected(entry.serviceId, entry.kioskId, entry.kioskName); + if (onSelectedListener != null) { + onSelectedListener.onKioskSelected(entry.serviceId, entry.kioskId, entry.kioskName); } dismiss(); } @@ -110,7 +110,7 @@ protected void onError(final Throwable e) { // Interfaces //////////////////////////////////////////////////////////////////////////*/ - public interface OnSelectedLisener { + public interface OnSelectedListener { void onKioskSelected(int serviceId, String kioskId, String kioskName); } diff --git a/app/src/main/java/org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java index 8a3a7f67e38..f891f5cc1cb 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java @@ -33,6 +33,7 @@ import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.settings.SelectChannelFragment; +import org.schabi.newpipe.settings.SelectFeedGroupFragment; import org.schabi.newpipe.settings.SelectKioskFragment; import org.schabi.newpipe.settings.tabs.AddTabDialog.ChooseTabListItem; import org.schabi.newpipe.util.ThemeHelper; @@ -48,7 +49,7 @@ public class ChooseTabsFragment extends Fragment { private TabsManager tabsManager; - private List tabList = new ArrayList<>(); + private final List tabList = new ArrayList<>(); private ChooseTabsFragment.SelectedTabsAdapter selectedTabsAdapter; /*////////////////////////////////////////////////////////////////////////// @@ -78,10 +79,10 @@ public void onViewCreated(@NonNull final View rootView, initButton(rootView); - RecyclerView listSelectedTabs = rootView.findViewById(R.id.selectedTabs); + final RecyclerView listSelectedTabs = rootView.findViewById(R.id.selectedTabs); listSelectedTabs.setLayoutManager(new LinearLayoutManager(requireContext())); - ItemTouchHelper itemTouchHelper = new ItemTouchHelper(getItemTouchCallback()); + final ItemTouchHelper itemTouchHelper = new ItemTouchHelper(getItemTouchCallback()); itemTouchHelper.attachToRecyclerView(listSelectedTabs); selectedTabsAdapter = new SelectedTabsAdapter(requireContext(), itemTouchHelper); @@ -138,7 +139,7 @@ private void updateTabList() { private void updateTitle() { if (getActivity() instanceof AppCompatActivity) { - ActionBar actionBar = ((AppCompatActivity) getActivity()).getSupportActionBar(); + final ActionBar actionBar = ((AppCompatActivity) getActivity()).getSupportActionBar(); if (actionBar != null) { actionBar.setTitle(R.string.main_page_content); } @@ -201,16 +202,22 @@ private void addTab(final int tabId) { switch (type) { case KIOSK: SelectKioskFragment selectKioskFragment = new SelectKioskFragment(); - selectKioskFragment.setOnSelectedLisener((serviceId, kioskId, kioskName) -> + selectKioskFragment.setOnSelectedListener((serviceId, kioskId, kioskName) -> addTab(new Tab.KioskTab(serviceId, kioskId))); selectKioskFragment.show(requireFragmentManager(), "select_kiosk"); return; case CHANNEL: SelectChannelFragment selectChannelFragment = new SelectChannelFragment(); - selectChannelFragment.setOnSelectedLisener((serviceId, url, name) -> + selectChannelFragment.setOnSelectedListener((serviceId, url, name) -> addTab(new Tab.ChannelTab(serviceId, url, name))); selectChannelFragment.show(requireFragmentManager(), "select_channel"); return; + case FEED_GROUP: + SelectFeedGroupFragment selectFeedGroupFragment = new SelectFeedGroupFragment(); + selectFeedGroupFragment.setOnSelectedListener((id, name, thumbnailId) -> + addTab(new Tab.FeedGroupTab(id, name, thumbnailId))); + selectFeedGroupFragment.show(requireFragmentManager(), "select_feed_group"); + return; default: addTab(type.getTab()); break; @@ -247,6 +254,11 @@ private ChooseTabListItem[] getAvailableTabs(final Context context) { ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_hot))); } break; + case FEED_GROUP: + returnList.add(new ChooseTabListItem(tab.getTabId(), + getString(R.string.feed_group_page_summary), + ThemeHelper.resolveResourceIdFromAttr(context, R.attr.rss))); + break; default: if (!tabList.contains(tab)) { returnList.add(new ChooseTabListItem(context, tab)); @@ -336,7 +348,7 @@ public void swapItems(final int fromPosition, final int toPosition) { @Override public ChooseTabsFragment.SelectedTabsAdapter.TabViewHolder onCreateViewHolder( @NonNull final ViewGroup parent, final int viewType) { - View view = inflater.inflate(R.layout.list_choose_tabs, parent, false); + final View view = inflater.inflate(R.layout.list_choose_tabs, parent, false); return new ChooseTabsFragment.SelectedTabsAdapter.TabViewHolder(view); } diff --git a/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java b/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java index 07e1c1cc3cf..b0cea1a0d62 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java +++ b/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java @@ -33,7 +33,8 @@ public abstract class Tab { private static final String JSON_TAB_ID_KEY = "tab_id"; - Tab() { } + Tab() { + } Tab(@NonNull final JsonObject jsonObject) { readDataFromJson(jsonObject); @@ -83,6 +84,8 @@ private static Tab from(final int tabId, @Nullable final JsonObject jsonObject) return new KioskTab(jsonObject); case CHANNEL: return new ChannelTab(jsonObject); + case FEED_GROUP: + return new FeedGroupTab((jsonObject)); } } @@ -147,7 +150,8 @@ public enum Type { BOOKMARKS(new BookmarksTab()), HISTORY(new HistoryTab()), KIOSK(new KioskTab()), - CHANNEL(new ChannelTab()); + CHANNEL(new ChannelTab()), + FEED_GROUP(new FeedGroupTab()); private Tab tab; @@ -482,4 +486,87 @@ private String getDefaultKioskId(final Context context) { return kioskId; } } + + public static class FeedGroupTab extends Tab { + public static final int ID = 8; + private static final String JSON_FEED_GROUP_ID_KEY = "feed_group_id"; + private static final String JSON_FEED_GROUP_THUMBNAIL_ID_KEY = "feed_group_thumbnail_id"; + private static final String JSON_FEED_GROUP_NAME_KEY = "feed_group_name"; + private long feedGroupId; + + private int feedGroupThumbnailId; + private String feedGroupName; + + private FeedGroupTab() { + this(-1, "", -1); + } + + public FeedGroupTab(final long feedGroupId, final String feedGroupName, + final int feedGroupThumbnailId) { + this.feedGroupId = feedGroupId; + this.feedGroupName = feedGroupName; + this.feedGroupThumbnailId = feedGroupThumbnailId; + } + + public FeedGroupTab(final JsonObject jsonObject) { + super(jsonObject); + } + + @Override + public int getTabId() { + return ID; + } + + @Override + public String getTabName(final Context context) { + return feedGroupName; + } + + @DrawableRes + @Override + public int getTabIconRes(final Context context) { + return ThemeHelper.resolveResourceIdFromAttr(context, feedGroupThumbnailId); + } + + @Override + public FeedFragment getFragment(final Context context) { + return FeedFragment.newInstance(feedGroupId, feedGroupName); + } + + @Override + protected void writeDataToJson(final JsonSink writerSink) { + writerSink.value(JSON_FEED_GROUP_ID_KEY, feedGroupId) + .value(JSON_FEED_GROUP_NAME_KEY, feedGroupName) + .value(JSON_FEED_GROUP_THUMBNAIL_ID_KEY, feedGroupThumbnailId); + } + + @Override + protected void readDataFromJson(final JsonObject jsonObject) { + feedGroupId = jsonObject.getLong(JSON_FEED_GROUP_ID_KEY, -1); + feedGroupName = + jsonObject.getString(JSON_FEED_GROUP_NAME_KEY, ""); + feedGroupThumbnailId = jsonObject.getInt(JSON_FEED_GROUP_THUMBNAIL_ID_KEY, -1); + } + + @Override + public boolean equals(final Object obj) { + return super.equals(obj) + && Objects.equals(feedGroupId, ((FeedGroupTab) obj).feedGroupId) + && Objects.equals(feedGroupName, ((FeedGroupTab) obj).feedGroupName) + && Objects.equals(feedGroupThumbnailId, + ((FeedGroupTab) obj).feedGroupThumbnailId); + } + + public long getFeedGroupId() { + return feedGroupId; + } + + public String getFeedGroupName() { + return feedGroupName; + } + + public int getFeedGroupThumbnailId() { + return feedGroupThumbnailId; + } + } } diff --git a/app/src/main/res/layout/select_feed_group_fragment.xml b/app/src/main/res/layout/select_feed_group_fragment.xml new file mode 100644 index 00000000000..df93df65244 --- /dev/null +++ b/app/src/main/res/layout/select_feed_group_fragment.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/layout/select_feed_group_item.xml b/app/src/main/res/layout/select_feed_group_item.xml new file mode 100644 index 00000000000..bb7abe84e2d --- /dev/null +++ b/app/src/main/res/layout/select_feed_group_item.xml @@ -0,0 +1,35 @@ + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3b7bc8a4a7f..b96da812dfe 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -401,6 +401,7 @@ Subscription Page Feed Page Channel Page + Feed Group Page Select a channel No channel subscriptions yet Select a kiosk @@ -651,4 +652,6 @@ Disable fast mode Do you think feed loading is too slow? If so, try enabling fast loading (you can change it in settings or by pressing the button below).\n\nNewPipe offers two feed loading strategies:\n• Fetching the whole subscription channel, which is slow but complete.\n• Using a dedicated service endpoint, which is fast but usually not complete.\n\nThe difference between the two is that the fast one usually lacks some information, like the item\'s duration or type (can\'t distinguish between live videos and normal ones) and it may return less items.\n\nYouTube is an example of a service that offers this fast method with its RSS feed.\n\nSo the choice boils down to what you prefer: speed or precise information. This content is not yet supported by NewPipe.\n\nIt will hopefully be supported in a future version. + Select a feed group + No feed group created yet