diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt index 61eb4c8d2..91f98f5d2 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt @@ -269,7 +269,12 @@ class FeedFragment : BaseStateFragment() { override fun onDestroyOptionsMenu() { super.onDestroyOptionsMenu() - activity?.supportActionBar?.subtitle = null + if ( + (groupName != "") && + (activity?.supportActionBar?.subtitle == groupName) + ) { + activity?.supportActionBar?.subtitle = null + } } override fun onDestroy() { @@ -281,7 +286,13 @@ class FeedFragment : BaseStateFragment() { } super.onDestroy() - activity?.supportActionBar?.subtitle = null + + if ( + (groupName != "") && + (activity?.supportActionBar?.subtitle == groupName) + ) { + activity?.supportActionBar?.subtitle = null + } } override fun onDestroyView() { 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 000000000..662379369 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/settings/SelectFeedGroupFragment.java @@ -0,0 +1,214 @@ +package org.schabi.newpipe.settings; + +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.annotation.Nullable; +import androidx.fragment.app.DialogFragment; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import org.schabi.newpipe.NewPipeDatabase; +import org.schabi.newpipe.R; +import org.schabi.newpipe.database.AppDatabase; +import org.schabi.newpipe.database.feed.model.FeedGroupEntity; +import org.schabi.newpipe.error.ErrorUtil; +import org.schabi.newpipe.util.ThemeHelper; + +import java.util.List; +import java.util.Vector; + +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; +import io.reactivex.rxjava3.core.Observer; +import io.reactivex.rxjava3.disposables.Disposable; +import io.reactivex.rxjava3.schedulers.Schedulers; + +/** + * 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<>(); + + public void setOnSelectedListener(final OnSelectedListener listener) { + onSelectedListener = listener; + } + + public void setOnCancelListener(final OnCancelListener listener) { + onCancelListener = listener; + } + + /*////////////////////////////////////////////////////////////////////////// + // Init + //////////////////////////////////////////////////////////////////////////*/ + + @Override + public void onCreate(@Nullable final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setStyle(STYLE_NO_TITLE, ThemeHelper.getMinWidthDialogTheme(requireContext())); + } + + @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())); + final SelectFeedGroupAdapter feedGroupAdapter = new SelectFeedGroupAdapter(); + recyclerView.setAdapter(feedGroupAdapter); + + 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 AppDatabase database = NewPipeDatabase.getInstance(requireContext()); + database.feedGroupDAO().getAll().toObservable() + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(getFeedGroupObserver()); + + return v; + } + + /*////////////////////////////////////////////////////////////////////////// + // Handle actions + //////////////////////////////////////////////////////////////////////////*/ + + @Override + public void onCancel(@NonNull final DialogInterface dialogInterface) { + super.onCancel(dialogInterface); + if (onCancelListener != null) { + onCancelListener.onCancel(); + } + } + + private void clickedItem(final int position) { + if (onSelectedListener != null) { + final FeedGroupEntity entry = feedGroups.get(position); + onSelectedListener + .onFeedGroupSelected(entry.getUid(), entry.getName(), + entry.getIcon().getDrawableResource()); + } + 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); + + } + + private Observer> getFeedGroupObserver() { + return new Observer>() { + @Override + public void onSubscribe(@NonNull final Disposable disposable) { } + + @Override + public void onNext(@NonNull final List newGroups) { + displayFeedGroups(newGroups); + } + + @Override + public void onError(@NonNull final Throwable exception) { + ErrorUtil.showUiErrorSnackbar(SelectFeedGroupFragment.this, + "Loading Feed Groups", exception); + } + + @Override + public void onComplete() { } + }; + } + + /*////////////////////////////////////////////////////////////////////////// + // Interfaces + //////////////////////////////////////////////////////////////////////////*/ + + public interface OnSelectedListener { + void onFeedGroupSelected(Long groupId, String name, int icon); + } + + public interface OnCancelListener { + void onCancel(); + } + + private class SelectFeedGroupAdapter + extends RecyclerView.Adapter { + @NonNull + @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)); + holder.thumbnailView.setImageResource(entry.getIcon().getDrawableResource()); + } + + @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/tabs/ChooseTabsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/tabs/ChooseTabsFragment.java index 289c824ba..738a9c926 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 @@ -34,6 +34,7 @@ import org.schabi.newpipe.error.UserAction; import org.schabi.newpipe.settings.SelectChannelFragment; import org.schabi.newpipe.settings.SelectKioskFragment; import org.schabi.newpipe.settings.SelectPlaylistFragment; +import org.schabi.newpipe.settings.SelectFeedGroupFragment; import org.schabi.newpipe.settings.tabs.AddTabDialog.ChooseTabListItem; import org.schabi.newpipe.util.ThemeHelper; @@ -203,6 +204,14 @@ public class ChooseTabsFragment extends Fragment { }); selectPlaylistFragment.show(getParentFragmentManager(), "select_playlist"); return; + case FEEDGROUP: + final SelectFeedGroupFragment selectFeedGroupFragment = + new SelectFeedGroupFragment(); + selectFeedGroupFragment.setOnSelectedListener( + (groupId, name, iconId) -> + addTab(new Tab.FeedGroupTab(groupId, name, iconId))); + selectFeedGroupFragment.show(getParentFragmentManager(), "select_feed_group"); + return; default: addTab(type.getTab()); break; @@ -244,6 +253,11 @@ public class ChooseTabsFragment extends Fragment { getString(R.string.playlist_page_summary), tab.getTabIconRes(context))); break; + case FEEDGROUP: + returnList.add(new ChooseTabListItem(tab.getTabId(), + getString(R.string.feed_group_page_summary), + tab.getTabIconRes(context))); + break; default: if (!tabList.contains(tab)) { returnList.add(new ChooseTabListItem(context, tab)); @@ -396,6 +410,9 @@ public class ChooseTabsFragment extends Fragment { ? getString(R.string.local) : getNameOfServiceById(serviceId); return serviceName + "/" + tab.getTabName(requireContext()); + case FEEDGROUP: + return getString(R.string.feed_groups_header_title) + + "/" + ((Tab.FeedGroupTab) tab).getFeedGroupName(); default: return tab.getTabName(requireContext()); } 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 dd2ff0582..4c1f65df2 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 @@ -93,6 +93,8 @@ public abstract class Tab { return new ChannelTab(jsonObject); case PLAYLIST: return new PlaylistTab(jsonObject); + case FEEDGROUP: + return new FeedGroupTab(jsonObject); } } @@ -162,7 +164,8 @@ public abstract class Tab { HISTORY(new HistoryTab()), KIOSK(new KioskTab()), CHANNEL(new ChannelTab()), - PLAYLIST(new PlaylistTab()); + PLAYLIST(new PlaylistTab()), + FEEDGROUP(new FeedGroupTab()); private final Tab tab; @@ -652,4 +655,93 @@ public abstract class Tab { return playlistType; } } + public static class FeedGroupTab extends Tab { + public static final int ID = 9; + private static final String JSON_FEED_GROUP_ID_KEY = "feed_group_id"; + private static final String JSON_FEED_GROUP_NAME_KEY = "feed_group_name"; + private static final String JSON_FEED_GROUP_ICON_KEY = "feed_group_icon"; + private Long feedGroupId; + private String feedGroupName; + private int iconId; + + private FeedGroupTab() { + this((long) -1, NO_NAME, R.drawable.ic_asterisk); + } + + public FeedGroupTab(final Long feedGroupId, final String feedGroupName, + final int iconId) { + this.feedGroupId = feedGroupId; + this.feedGroupName = feedGroupName; + this.iconId = iconId; + + } + + public FeedGroupTab(final JsonObject jsonObject) { + super(jsonObject); + } + + @Override + public int getTabId() { + return ID; + } + + @Override + public String getTabName(final Context context) { + return context.getString(R.string.fragment_feed_title); + } + + @DrawableRes + @Override + public int getTabIconRes(final Context context) { + return this.iconId; + } + + @Override + public FeedFragment getFragment(final Context context) { + return FeedFragment.newInstance(feedGroupId, feedGroupName); + } + + @Override + protected void writeDataToJson(final JsonStringWriter writerSink) { + writerSink.value(JSON_FEED_GROUP_ID_KEY, feedGroupId) + .value(JSON_FEED_GROUP_NAME_KEY, feedGroupName) + .value(JSON_FEED_GROUP_ICON_KEY, iconId); + } + + @Override + protected void readDataFromJson(final JsonObject jsonObject) { + feedGroupId = jsonObject.getLong(JSON_FEED_GROUP_ID_KEY, -1); + feedGroupName = jsonObject.getString(JSON_FEED_GROUP_NAME_KEY, NO_NAME); + iconId = jsonObject.getInt(JSON_FEED_GROUP_ICON_KEY, R.drawable.ic_asterisk); + } + + @Override + public boolean equals(final Object obj) { + if (!(obj instanceof FeedGroupTab)) { + return false; + } + final FeedGroupTab other = (FeedGroupTab) obj; + return super.equals(obj) + && feedGroupId.equals(other.feedGroupId) + && feedGroupName.equals(other.feedGroupName) + && iconId == other.iconId; + } + + @Override + public int hashCode() { + return Objects.hash(getTabId(), feedGroupId, feedGroupName, iconId); + } + + public Long getFeedGroupId() { + return feedGroupId; + } + + public String getFeedGroupName() { + return feedGroupName; + } + + public int getIconId() { + return iconId; + } + } } 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 000000000..bb17d5f6e --- /dev/null +++ b/app/src/main/res/layout/select_feed_group_fragment.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + 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 000000000..ccce555f5 --- /dev/null +++ b/app/src/main/res/layout/select_feed_group_item.xml @@ -0,0 +1,38 @@ + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2232ddaff..1015dea08 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -412,6 +412,8 @@ Do you want to also import settings? Could not load comments The language will change once the app is restarted + Select a feed group + No feed group created yet Trending Top 50 @@ -687,6 +689,7 @@ What\'s New + Channel group page Channel groups Feed last updated: %s Not loaded: %d diff --git a/app/src/test/java/org/schabi/newpipe/settings/tabs/TabsJsonHelperTest.java b/app/src/test/java/org/schabi/newpipe/settings/tabs/TabsJsonHelperTest.java index bddb130fe..561a8cbec 100644 --- a/app/src/test/java/org/schabi/newpipe/settings/tabs/TabsJsonHelperTest.java +++ b/app/src/test/java/org/schabi/newpipe/settings/tabs/TabsJsonHelperTest.java @@ -93,9 +93,11 @@ public class TabsJsonHelperTest { final Tab.ChannelTab channelTab = new Tab.ChannelTab( 666, "https://example.org", "testName"); final Tab.KioskTab kioskTab = new Tab.KioskTab(123, "trending_key"); + final Tab.FeedGroupTab feedGroupTab = new Tab.FeedGroupTab( + 1L, "x", 123); final List tabs = Arrays.asList( - blankTab, defaultKioskTab, subscriptionsTab, channelTab, kioskTab); + blankTab, defaultKioskTab, subscriptionsTab, channelTab, kioskTab, feedGroupTab); final String returnedJson = TabsJsonHelper.getJsonToSave(tabs); // Reading @@ -130,5 +132,13 @@ public class TabsJsonHelperTest { assertEquals(kioskTab.getTabId(), kioskTabFromReturnedJson.getTabId()); assertEquals(kioskTab.getKioskServiceId(), kioskTabFromReturnedJson.getKioskServiceId()); assertEquals(kioskTab.getKioskId(), kioskTabFromReturnedJson.getKioskId()); + + final Tab.FeedGroupTab grpTabFromReturnedJson = requireNonNull( + (Tab.FeedGroupTab) Tab.from((JsonObject) tabsFromArray.get(5) + )); + assertEquals(feedGroupTab.getTabId(), grpTabFromReturnedJson.getTabId()); + assertEquals(feedGroupTab.getFeedGroupId(), grpTabFromReturnedJson.getFeedGroupId()); + assertEquals(feedGroupTab.getIconId(), grpTabFromReturnedJson.getIconId()); + assertEquals(feedGroupTab.getFeedGroupName(), grpTabFromReturnedJson.getFeedGroupName()); } }