mirror of
https://github.com/TeamNewPipe/NewPipe.git
synced 2024-11-22 19:12:45 +01:00
Merge branch 'dev' into separate-gesture-options
This commit is contained in:
commit
afa257e79a
@ -8,8 +8,8 @@ android {
|
|||||||
applicationId "org.schabi.newpipe"
|
applicationId "org.schabi.newpipe"
|
||||||
minSdkVersion 15
|
minSdkVersion 15
|
||||||
targetSdkVersion 27
|
targetSdkVersion 27
|
||||||
versionCode 67
|
versionCode 68
|
||||||
versionName "0.14.0"
|
versionName "0.14.1"
|
||||||
|
|
||||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = true
|
||||||
@ -55,7 +55,7 @@ dependencies {
|
|||||||
exclude module: 'support-annotations'
|
exclude module: 'support-annotations'
|
||||||
}
|
}
|
||||||
|
|
||||||
implementation 'com.github.TeamNewPipe:NewPipeExtractor:834382111b98e629'
|
implementation 'com.github.TeamNewPipe:NewPipeExtractor:66c3c3f45241d4b0c909'
|
||||||
|
|
||||||
testImplementation 'junit:junit:4.12'
|
testImplementation 'junit:junit:4.12'
|
||||||
testImplementation 'org.mockito:mockito-core:2.8.9'
|
testImplementation 'org.mockito:mockito-core:2.8.9'
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
package org.schabi.newpipe.fragments;
|
package org.schabi.newpipe.fragments;
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
@ -12,7 +10,6 @@ import android.support.v4.app.FragmentPagerAdapter;
|
|||||||
import android.support.v4.view.ViewPager;
|
import android.support.v4.view.ViewPager;
|
||||||
import android.support.v7.app.ActionBar;
|
import android.support.v7.app.ActionBar;
|
||||||
import android.support.v7.app.AppCompatActivity;
|
import android.support.v7.app.AppCompatActivity;
|
||||||
import android.support.v7.preference.PreferenceManager;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
@ -23,42 +20,26 @@ import android.view.ViewGroup;
|
|||||||
|
|
||||||
import org.schabi.newpipe.BaseFragment;
|
import org.schabi.newpipe.BaseFragment;
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.fragments.list.channel.ChannelFragment;
|
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||||
import org.schabi.newpipe.fragments.list.kiosk.KioskFragment;
|
|
||||||
import org.schabi.newpipe.local.bookmark.BookmarkFragment;
|
|
||||||
import org.schabi.newpipe.local.feed.FeedFragment;
|
|
||||||
import org.schabi.newpipe.local.history.StatisticsPlaylistFragment;
|
|
||||||
import org.schabi.newpipe.local.subscription.SubscriptionFragment;
|
|
||||||
import org.schabi.newpipe.report.ErrorActivity;
|
import org.schabi.newpipe.report.ErrorActivity;
|
||||||
import org.schabi.newpipe.report.UserAction;
|
import org.schabi.newpipe.report.UserAction;
|
||||||
import org.schabi.newpipe.util.KioskTranslator;
|
import org.schabi.newpipe.settings.tabs.Tab;
|
||||||
|
import org.schabi.newpipe.settings.tabs.TabsManager;
|
||||||
import org.schabi.newpipe.util.NavigationHelper;
|
import org.schabi.newpipe.util.NavigationHelper;
|
||||||
import org.schabi.newpipe.util.ServiceHelper;
|
import org.schabi.newpipe.util.ServiceHelper;
|
||||||
import org.schabi.newpipe.util.ThemeHelper;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class MainFragment extends BaseFragment implements TabLayout.OnTabSelectedListener {
|
public class MainFragment extends BaseFragment implements TabLayout.OnTabSelectedListener {
|
||||||
|
|
||||||
public int currentServiceId = -1;
|
|
||||||
private ViewPager viewPager;
|
private ViewPager viewPager;
|
||||||
private List<String> tabs = new ArrayList<>();
|
private SelectedTabsPagerAdapter pagerAdapter;
|
||||||
static PagerAdapter adapter;
|
private TabLayout tabLayout;
|
||||||
TabLayout tabLayout;
|
|
||||||
private SharedPreferences prefs;
|
|
||||||
private Bundle savedInstanceStateBundle;
|
|
||||||
|
|
||||||
private static final String TAB_NUMBER_BLANK = "0";
|
private List<Tab> tabsList = new ArrayList<>();
|
||||||
private static final String TAB_NUMBER_KIOSK = "1";
|
private TabsManager tabsManager;
|
||||||
private static final String TAB_NUMBER_SUBSCIRPTIONS = "2";
|
|
||||||
private static final String TAB_NUMBER_FEED = "3";
|
|
||||||
private static final String TAB_NUMBER_BOOKMARKS = "4";
|
|
||||||
private static final String TAB_NUMBER_HISTORY = "5";
|
|
||||||
private static final String TAB_NUMBER_CHANNEL = "6";
|
|
||||||
|
|
||||||
SharedPreferences.OnSharedPreferenceChangeListener listener;
|
private boolean hasTabsChanged = false;
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Fragment's LifeCycle
|
// Fragment's LifeCycle
|
||||||
@ -66,23 +47,24 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
savedInstanceStateBundle = savedInstanceState;
|
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setHasOptionsMenu(true);
|
setHasOptionsMenu(true);
|
||||||
listener = (prefs, key) -> {
|
|
||||||
if(key.equals("saveUsedTabs")) {
|
tabsManager = TabsManager.getManager(activity);
|
||||||
mainPageChanged();
|
tabsManager.setSavedTabsListener(() -> {
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "TabsManager.SavedTabsChangeListener: onTabsChanged called, isResumed = " + isResumed());
|
||||||
}
|
}
|
||||||
};
|
if (isResumed()) {
|
||||||
|
updateTabs();
|
||||||
|
} else {
|
||||||
|
hasTabsChanged = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||||
currentServiceId = ServiceHelper.getSelectedServiceId(activity);
|
|
||||||
|
|
||||||
prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
|
|
||||||
prefs.registerOnSharedPreferenceChangeListener(listener);
|
|
||||||
|
|
||||||
return inflater.inflate(R.layout.fragment_main, container, false);
|
return inflater.inflate(R.layout.fragment_main, container, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,110 +76,28 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
|||||||
viewPager = rootView.findViewById(R.id.pager);
|
viewPager = rootView.findViewById(R.id.pager);
|
||||||
|
|
||||||
/* Nested fragment, use child fragment here to maintain backstack in view pager. */
|
/* Nested fragment, use child fragment here to maintain backstack in view pager. */
|
||||||
adapter = new PagerAdapter(getChildFragmentManager());
|
pagerAdapter = new SelectedTabsPagerAdapter(getChildFragmentManager());
|
||||||
viewPager.setAdapter(adapter);
|
viewPager.setAdapter(pagerAdapter);
|
||||||
|
|
||||||
tabLayout.setupWithViewPager(viewPager);
|
tabLayout.setupWithViewPager(viewPager);
|
||||||
|
tabLayout.addOnTabSelectedListener(this);
|
||||||
mainPageChanged();
|
updateTabs();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
|
||||||
public void mainPageChanged() {
|
if (hasTabsChanged) {
|
||||||
getTabOrder();
|
hasTabsChanged = false;
|
||||||
adapter.notifyDataSetChanged();
|
updateTabs();
|
||||||
viewPager.setOffscreenPageLimit(adapter.getCount());
|
|
||||||
setIcons();
|
|
||||||
setFirstTitle();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setFirstTitle() {
|
|
||||||
if((tabs.size() > 0)
|
|
||||||
&& activity != null) {
|
|
||||||
String tabInformation = tabs.get(0);
|
|
||||||
if (tabInformation.startsWith(TAB_NUMBER_KIOSK + "\t")) {
|
|
||||||
String kiosk[] = tabInformation.split("\t");
|
|
||||||
if (kiosk.length == 3) {
|
|
||||||
setTitle(kiosk[1]);
|
|
||||||
}
|
|
||||||
} else if (tabInformation.startsWith(TAB_NUMBER_CHANNEL + "\t")) {
|
|
||||||
|
|
||||||
String channelInfo[] = tabInformation.split("\t");
|
|
||||||
if(channelInfo.length==4) {
|
|
||||||
setTitle(channelInfo[2]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
switch (tabInformation) {
|
|
||||||
case TAB_NUMBER_BLANK:
|
|
||||||
setTitle(getString(R.string.app_name));
|
|
||||||
break;
|
|
||||||
case TAB_NUMBER_SUBSCIRPTIONS:
|
|
||||||
setTitle(getString(R.string.tab_subscriptions));
|
|
||||||
break;
|
|
||||||
case TAB_NUMBER_FEED:
|
|
||||||
setTitle(getString(R.string.fragment_whats_new));
|
|
||||||
break;
|
|
||||||
case TAB_NUMBER_BOOKMARKS:
|
|
||||||
setTitle(getString(R.string.tab_bookmarks));
|
|
||||||
break;
|
|
||||||
case TAB_NUMBER_HISTORY:
|
|
||||||
setTitle(getString(R.string.title_activity_history));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setIcons() {
|
@Override
|
||||||
for (int i = 0; i < tabs.size(); i++) {
|
public void onDestroy() {
|
||||||
String tabInformation = tabs.get(i);
|
super.onDestroy();
|
||||||
|
tabsManager.unsetSavedTabsListener();
|
||||||
TabLayout.Tab tabToSet = tabLayout.getTabAt(i);
|
|
||||||
Context c = getContext();
|
|
||||||
|
|
||||||
if (tabToSet != null && c != null) {
|
|
||||||
|
|
||||||
if (tabInformation.startsWith(TAB_NUMBER_KIOSK + "\t")) {
|
|
||||||
String kiosk[] = tabInformation.split("\t");
|
|
||||||
if (kiosk.length == 3) {
|
|
||||||
tabToSet.setIcon(KioskTranslator.getKioskIcons(kiosk[1], getContext()));
|
|
||||||
}
|
|
||||||
} else if (tabInformation.startsWith(TAB_NUMBER_CHANNEL + "\t")) {
|
|
||||||
tabToSet.setIcon(ThemeHelper.resolveResourceIdFromAttr(getContext(), R.attr.ic_channel));
|
|
||||||
} else {
|
|
||||||
switch (tabInformation) {
|
|
||||||
case TAB_NUMBER_BLANK:
|
|
||||||
tabToSet.setIcon(ThemeHelper.resolveResourceIdFromAttr(getContext(), R.attr.ic_hot));
|
|
||||||
break;
|
|
||||||
case TAB_NUMBER_SUBSCIRPTIONS:
|
|
||||||
tabToSet.setIcon(ThemeHelper.resolveResourceIdFromAttr(getContext(), R.attr.ic_channel));
|
|
||||||
break;
|
|
||||||
case TAB_NUMBER_FEED:
|
|
||||||
tabToSet.setIcon(ThemeHelper.resolveResourceIdFromAttr(getContext(), R.attr.rss));
|
|
||||||
break;
|
|
||||||
case TAB_NUMBER_BOOKMARKS:
|
|
||||||
tabToSet.setIcon(ThemeHelper.resolveResourceIdFromAttr(getContext(), R.attr.ic_bookmark));
|
|
||||||
break;
|
|
||||||
case TAB_NUMBER_HISTORY:
|
|
||||||
tabToSet.setIcon(ThemeHelper.resolveResourceIdFromAttr(getContext(), R.attr.history));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void getTabOrder() {
|
|
||||||
tabs.clear();
|
|
||||||
|
|
||||||
String save = prefs.getString("saveUsedTabs", "1\tTrending\t0\n2\n4\n");
|
|
||||||
String tabsArray[] = save.trim().split("\n");
|
|
||||||
|
|
||||||
Collections.addAll(tabs, tabsArray);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
@ -237,9 +137,33 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
|||||||
// Tabs
|
// Tabs
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
public void updateTabs() {
|
||||||
|
tabsList.clear();
|
||||||
|
tabsList.addAll(tabsManager.getTabs());
|
||||||
|
pagerAdapter.notifyDataSetChanged();
|
||||||
|
|
||||||
|
viewPager.setOffscreenPageLimit(pagerAdapter.getCount());
|
||||||
|
updateTabsIcon();
|
||||||
|
updateCurrentTitle();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateTabsIcon() {
|
||||||
|
for (int i = 0; i < tabsList.size(); i++) {
|
||||||
|
final TabLayout.Tab tabToSet = tabLayout.getTabAt(i);
|
||||||
|
if (tabToSet != null) {
|
||||||
|
tabToSet.setIcon(tabsList.get(i).getTabIconRes(activity));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateCurrentTitle() {
|
||||||
|
setTitle(tabsList.get(viewPager.getCurrentItem()).getTabName(requireContext()));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTabSelected(TabLayout.Tab tab) {
|
public void onTabSelected(TabLayout.Tab selectedTab) {
|
||||||
viewPager.setCurrentItem(tab.getPosition());
|
if (DEBUG) Log.d(TAG, "onTabSelected() called with: selectedTab = [" + selectedTab + "]");
|
||||||
|
updateCurrentTitle();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -248,68 +172,40 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTabReselected(TabLayout.Tab tab) {
|
public void onTabReselected(TabLayout.Tab tab) {
|
||||||
|
if (DEBUG) Log.d(TAG, "onTabReselected() called with: tab = [" + tab + "]");
|
||||||
|
updateCurrentTitle();
|
||||||
}
|
}
|
||||||
|
|
||||||
private class PagerAdapter extends FragmentPagerAdapter {
|
private class SelectedTabsPagerAdapter extends FragmentPagerAdapter {
|
||||||
PagerAdapter(FragmentManager fm) {
|
private SelectedTabsPagerAdapter(FragmentManager fragmentManager) {
|
||||||
super(fm);
|
super(fragmentManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Fragment getItem(int position) {
|
public Fragment getItem(int position) {
|
||||||
String tabInformation = tabs.get(position);
|
final Tab tab = tabsList.get(position);
|
||||||
|
|
||||||
if(tabInformation.startsWith(TAB_NUMBER_KIOSK + "\t")) {
|
Throwable throwable = null;
|
||||||
String kiosk[] = tabInformation.split("\t");
|
Fragment fragment = null;
|
||||||
if(kiosk.length==3) {
|
try {
|
||||||
KioskFragment fragment = null;
|
fragment = tab.getFragment();
|
||||||
try {
|
} catch (ExtractionException e) {
|
||||||
fragment = KioskFragment.getInstance(Integer.parseInt(kiosk[2]), kiosk[1]);
|
throwable = e;
|
||||||
fragment.useAsFrontPage(true);
|
|
||||||
return fragment;
|
|
||||||
} catch (Exception e) {
|
|
||||||
ErrorActivity.reportError(activity, e,
|
|
||||||
activity.getClass(),
|
|
||||||
null,
|
|
||||||
ErrorActivity.ErrorInfo.make(UserAction.UI_ERROR,
|
|
||||||
"none", "", R.string.app_ui_crash));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if(tabInformation.startsWith(TAB_NUMBER_CHANNEL + "\t")) {
|
|
||||||
String channelInfo[] = tabInformation.split("\t");
|
|
||||||
if(channelInfo.length==4) {
|
|
||||||
ChannelFragment fragment = ChannelFragment.getInstance(Integer.parseInt(channelInfo[3]), channelInfo[1], channelInfo[2]);
|
|
||||||
fragment.useAsFrontPage(true);
|
|
||||||
return fragment;
|
|
||||||
} else {
|
|
||||||
return new BlankFragment();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
switch (tabInformation) {
|
|
||||||
case TAB_NUMBER_BLANK:
|
|
||||||
return new BlankFragment();
|
|
||||||
case TAB_NUMBER_SUBSCIRPTIONS:
|
|
||||||
SubscriptionFragment sFragment = new SubscriptionFragment();
|
|
||||||
sFragment.useAsFrontPage(true);
|
|
||||||
return sFragment;
|
|
||||||
case TAB_NUMBER_FEED:
|
|
||||||
FeedFragment fFragment = new FeedFragment();
|
|
||||||
fFragment.useAsFrontPage(true);
|
|
||||||
return fFragment;
|
|
||||||
case TAB_NUMBER_BOOKMARKS:
|
|
||||||
BookmarkFragment bFragment = new BookmarkFragment();
|
|
||||||
bFragment.useAsFrontPage(true);
|
|
||||||
return bFragment;
|
|
||||||
case TAB_NUMBER_HISTORY:
|
|
||||||
StatisticsPlaylistFragment cFragment = new StatisticsPlaylistFragment();
|
|
||||||
cFragment.useAsFrontPage(true);
|
|
||||||
return cFragment;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new BlankFragment();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (throwable != null) {
|
||||||
|
ErrorActivity.reportError(activity, throwable, activity.getClass(), null,
|
||||||
|
ErrorActivity.ErrorInfo.make(UserAction.UI_ERROR, "none", "", R.string.app_ui_crash));
|
||||||
|
return new BlankFragment();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fragment instanceof BaseFragment) {
|
||||||
|
((BaseFragment) fragment).useAsFrontPage(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return fragment;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getItemPosition(Object object) {
|
public int getItemPosition(Object object) {
|
||||||
// Causes adapter to reload all Fragments when
|
// Causes adapter to reload all Fragments when
|
||||||
@ -319,14 +215,14 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getCount() {
|
public int getCount() {
|
||||||
return tabs.size();
|
return tabsList.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void destroyItem(ViewGroup container, int position, Object object) {
|
public void destroyItem(ViewGroup container, int position, Object object) {
|
||||||
getFragmentManager()
|
getChildFragmentManager()
|
||||||
.beginTransaction()
|
.beginTransaction()
|
||||||
.remove((Fragment)object)
|
.remove((Fragment) object)
|
||||||
.commitNowAllowingStateLoss();
|
.commitNowAllowingStateLoss();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,15 +33,14 @@ import org.schabi.newpipe.extractor.NewPipe;
|
|||||||
import org.schabi.newpipe.extractor.channel.ChannelInfo;
|
import org.schabi.newpipe.extractor.channel.ChannelInfo;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
|
|
||||||
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
|
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
|
||||||
import org.schabi.newpipe.info_list.InfoItemDialog;
|
import org.schabi.newpipe.info_list.InfoItemDialog;
|
||||||
import org.schabi.newpipe.local.dialog.PlaylistAppendDialog;
|
import org.schabi.newpipe.local.dialog.PlaylistAppendDialog;
|
||||||
|
import org.schabi.newpipe.local.subscription.SubscriptionService;
|
||||||
import org.schabi.newpipe.player.playqueue.ChannelPlayQueue;
|
import org.schabi.newpipe.player.playqueue.ChannelPlayQueue;
|
||||||
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
||||||
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
|
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
|
||||||
import org.schabi.newpipe.report.UserAction;
|
import org.schabi.newpipe.report.UserAction;
|
||||||
import org.schabi.newpipe.local.subscription.SubscriptionService;
|
|
||||||
import org.schabi.newpipe.util.AnimationUtils;
|
import org.schabi.newpipe.util.AnimationUtils;
|
||||||
import org.schabi.newpipe.util.ExtractorHelper;
|
import org.schabi.newpipe.util.ExtractorHelper;
|
||||||
import org.schabi.newpipe.util.ImageDisplayConstants;
|
import org.schabi.newpipe.util.ImageDisplayConstants;
|
||||||
@ -422,10 +421,12 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
|||||||
imageLoader.displayImage(result.getAvatarUrl(), headerAvatarView,
|
imageLoader.displayImage(result.getAvatarUrl(), headerAvatarView,
|
||||||
ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS);
|
ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS);
|
||||||
|
|
||||||
if (result.getSubscriberCount() != -1) {
|
headerSubscribersTextView.setVisibility(View.VISIBLE);
|
||||||
|
if (result.getSubscriberCount() >= 0) {
|
||||||
headerSubscribersTextView.setText(Localization.localizeSubscribersCount(activity, result.getSubscriberCount()));
|
headerSubscribersTextView.setText(Localization.localizeSubscribersCount(activity, result.getSubscriberCount()));
|
||||||
headerSubscribersTextView.setVisibility(View.VISIBLE);
|
} else {
|
||||||
} else headerSubscribersTextView.setVisibility(View.GONE);
|
headerSubscribersTextView.setText(R.string.subscribers_count_not_available);
|
||||||
|
}
|
||||||
|
|
||||||
if (menuRssButton != null) menuRssButton.setVisible(!TextUtils.isEmpty(result.getFeedUrl()));
|
if (menuRssButton != null) menuRssButton.setVisible(!TextUtils.isEmpty(result.getFeedUrl()));
|
||||||
|
|
||||||
|
@ -70,7 +70,6 @@ import org.schabi.newpipe.player.playqueue.PlayQueue;
|
|||||||
import org.schabi.newpipe.player.playqueue.PlayQueueAdapter;
|
import org.schabi.newpipe.player.playqueue.PlayQueueAdapter;
|
||||||
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
||||||
import org.schabi.newpipe.player.resolver.MediaSourceTag;
|
import org.schabi.newpipe.player.resolver.MediaSourceTag;
|
||||||
import org.schabi.newpipe.report.ErrorActivity;
|
|
||||||
import org.schabi.newpipe.util.ImageDisplayConstants;
|
import org.schabi.newpipe.util.ImageDisplayConstants;
|
||||||
import org.schabi.newpipe.util.SerializedCache;
|
import org.schabi.newpipe.util.SerializedCache;
|
||||||
|
|
||||||
@ -88,7 +87,6 @@ import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_INTERNAL
|
|||||||
import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_PERIOD_TRANSITION;
|
import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_PERIOD_TRANSITION;
|
||||||
import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_SEEK;
|
import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_SEEK;
|
||||||
import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT;
|
import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT;
|
||||||
import static org.schabi.newpipe.report.UserAction.PLAY_STREAM;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base for the players, joining the common properties
|
* Base for the players, joining the common properties
|
||||||
@ -175,7 +173,6 @@ public abstract class BasePlayer implements
|
|||||||
};
|
};
|
||||||
this.intentFilter = new IntentFilter();
|
this.intentFilter = new IntentFilter();
|
||||||
setupBroadcastReceiver(intentFilter);
|
setupBroadcastReceiver(intentFilter);
|
||||||
context.registerReceiver(broadcastReceiver, intentFilter);
|
|
||||||
|
|
||||||
this.recordManager = new HistoryRecordManager(context);
|
this.recordManager = new HistoryRecordManager(context);
|
||||||
|
|
||||||
@ -212,6 +209,8 @@ public abstract class BasePlayer implements
|
|||||||
audioReactor = new AudioReactor(context, simpleExoPlayer);
|
audioReactor = new AudioReactor(context, simpleExoPlayer);
|
||||||
mediaSessionManager = new MediaSessionManager(context, simpleExoPlayer,
|
mediaSessionManager = new MediaSessionManager(context, simpleExoPlayer,
|
||||||
new BasePlayerMediaSession(this));
|
new BasePlayerMediaSession(this));
|
||||||
|
|
||||||
|
registerBroadcastReceiver();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initListeners() {}
|
public void initListeners() {}
|
||||||
@ -362,11 +361,17 @@ public abstract class BasePlayer implements
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void unregisterBroadcastReceiver() {
|
protected void registerBroadcastReceiver() {
|
||||||
|
// Try to unregister current first
|
||||||
|
unregisterBroadcastReceiver();
|
||||||
|
context.registerReceiver(broadcastReceiver, intentFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void unregisterBroadcastReceiver() {
|
||||||
try {
|
try {
|
||||||
context.unregisterReceiver(broadcastReceiver);
|
context.unregisterReceiver(broadcastReceiver);
|
||||||
} catch (final IllegalArgumentException unregisteredException) {
|
} catch (final IllegalArgumentException unregisteredException) {
|
||||||
Log.e(TAG, "Broadcast receiver already unregistered.", unregisteredException);
|
Log.w(TAG, "Broadcast receiver already unregistered (" + unregisteredException.getMessage() + ")");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -460,7 +460,7 @@ public final class MainVideoPlayer extends AppCompatActivity
|
|||||||
public void initListeners() {
|
public void initListeners() {
|
||||||
super.initListeners();
|
super.initListeners();
|
||||||
|
|
||||||
MySimpleOnGestureListener listener = new MySimpleOnGestureListener();
|
PlayerGestureListener listener = new PlayerGestureListener();
|
||||||
gestureDetector = new GestureDetector(context, listener);
|
gestureDetector = new GestureDetector(context, listener);
|
||||||
gestureDetector.setIsLongpressEnabled(false);
|
gestureDetector.setIsLongpressEnabled(false);
|
||||||
getRootView().setOnTouchListener(listener);
|
getRootView().setOnTouchListener(listener);
|
||||||
@ -489,6 +489,8 @@ public final class MainVideoPlayer extends AppCompatActivity
|
|||||||
|
|
||||||
volumeProgressBar.setMax(maxGestureLength);
|
volumeProgressBar.setMax(maxGestureLength);
|
||||||
brightnessProgressBar.setMax(maxGestureLength);
|
brightnessProgressBar.setMax(maxGestureLength);
|
||||||
|
|
||||||
|
setInitialGestureValues();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -799,6 +801,13 @@ public final class MainVideoPlayer extends AppCompatActivity
|
|||||||
// Utils
|
// Utils
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
private void setInitialGestureValues() {
|
||||||
|
if (getAudioReactor() != null) {
|
||||||
|
final float currentVolumeNormalized = (float) getAudioReactor().getVolume() / getAudioReactor().getMaxVolume();
|
||||||
|
volumeProgressBar.setProgress((int) (volumeProgressBar.getMax() * currentVolumeNormalized));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void showControlsThenHide() {
|
public void showControlsThenHide() {
|
||||||
if (queueVisible) return;
|
if (queueVisible) return;
|
||||||
@ -939,7 +948,7 @@ public final class MainVideoPlayer extends AppCompatActivity
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class MySimpleOnGestureListener extends GestureDetector.SimpleOnGestureListener implements View.OnTouchListener {
|
private class PlayerGestureListener extends GestureDetector.SimpleOnGestureListener implements View.OnTouchListener {
|
||||||
private boolean isMoving;
|
private boolean isMoving;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -978,33 +987,34 @@ public final class MainVideoPlayer extends AppCompatActivity
|
|||||||
return super.onDown(e);
|
return super.onDown(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final int MOVEMENT_THRESHOLD = 40;
|
||||||
|
|
||||||
private final boolean isVolumeGestureEnabled = PlayerHelper.isVolumeGestureEnabled(getApplicationContext());
|
private final boolean isVolumeGestureEnabled = PlayerHelper.isVolumeGestureEnabled(getApplicationContext());
|
||||||
private final boolean isBrightnessGestureEnabled = PlayerHelper.isBrightnessGestureEnabled(getApplicationContext());
|
private final boolean isBrightnessGestureEnabled = PlayerHelper.isBrightnessGestureEnabled(getApplicationContext());
|
||||||
|
|
||||||
|
private final boolean isPlayerGestureEnabled = PlayerHelper.isPlayerGestureEnabled(getApplicationContext());
|
||||||
private final int maxVolume = playerImpl.getAudioReactor().getMaxVolume();
|
private final int maxVolume = playerImpl.getAudioReactor().getMaxVolume();
|
||||||
|
|
||||||
private final int MOVEMENT_THRESHOLD = 40;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
|
public boolean onScroll(MotionEvent initialEvent, MotionEvent movingEvent, float distanceX, float distanceY) {
|
||||||
if (!isVolumeGestureEnabled && !isBrightnessGestureEnabled) return false;
|
if (!isVolumeGestureEnabled && !isBrightnessGestureEnabled) return false;
|
||||||
|
|
||||||
//noinspection PointlessBooleanExpression
|
//noinspection PointlessBooleanExpression
|
||||||
if (DEBUG && false) Log.d(TAG, "MainVideoPlayer.onScroll = " +
|
if (DEBUG && false) Log.d(TAG, "MainVideoPlayer.onScroll = " +
|
||||||
", e1.getRaw = [" + e1.getRawX() + ", " + e1.getRawY() + "]" +
|
", e1.getRaw = [" + initialEvent.getRawX() + ", " + initialEvent.getRawY() + "]" +
|
||||||
", e2.getRaw = [" + e2.getRawX() + ", " + e2.getRawY() + "]" +
|
", e2.getRaw = [" + movingEvent.getRawX() + ", " + movingEvent.getRawY() + "]" +
|
||||||
", distanceXy = [" + distanceX + ", " + distanceY + "]");
|
", distanceXy = [" + distanceX + ", " + distanceY + "]");
|
||||||
|
|
||||||
if (!isMoving && (
|
final boolean insideThreshold = Math.abs(movingEvent.getY() - initialEvent.getY()) <= MOVEMENT_THRESHOLD;
|
||||||
Math.abs(e2.getY() - e1.getY()) <= MOVEMENT_THRESHOLD
|
if (!isMoving && (insideThreshold || Math.abs(distanceX) > Math.abs(distanceY))
|
||||||
|| Math.abs(distanceX) > Math.abs(distanceY)
|
|| playerImpl.getCurrentState() == BasePlayer.STATE_COMPLETED) {
|
||||||
) || playerImpl.getCurrentState() == BasePlayer.STATE_COMPLETED)
|
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
isMoving = true;
|
isMoving = true;
|
||||||
|
|
||||||
boolean acceptAnyArea = isVolumeGestureEnabled != isBrightnessGestureEnabled;
|
boolean acceptAnyArea = isVolumeGestureEnabled != isBrightnessGestureEnabled;
|
||||||
boolean acceptVolumeArea = acceptAnyArea || e1.getX() > playerImpl.getRootView().getWidth() / 2;
|
boolean acceptVolumeArea = acceptAnyArea || initialEvent.getX() > playerImpl.getRootView().getWidth() / 2;
|
||||||
boolean acceptBrightnessArea = acceptAnyArea || !acceptVolumeArea;
|
boolean acceptBrightnessArea = acceptAnyArea || !acceptVolumeArea;
|
||||||
|
|
||||||
if (isVolumeGestureEnabled && acceptVolumeArea) {
|
if (isVolumeGestureEnabled && acceptVolumeArea) {
|
||||||
|
@ -1,41 +0,0 @@
|
|||||||
package org.schabi.newpipe.settings;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.app.AlertDialog;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import org.schabi.newpipe.R;
|
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
|
||||||
|
|
||||||
public class AddTabsDialog {
|
|
||||||
private final AlertDialog dialog;
|
|
||||||
|
|
||||||
public AddTabsDialog(@NonNull final Context context,
|
|
||||||
@NonNull final String title,
|
|
||||||
@NonNull final String[] commands,
|
|
||||||
@NonNull final DialogInterface.OnClickListener actions) {
|
|
||||||
|
|
||||||
final View bannerView = View.inflate(context, R.layout.dialog_title, null);
|
|
||||||
bannerView.setSelected(true);
|
|
||||||
|
|
||||||
TextView titleView = bannerView.findViewById(R.id.itemTitleView);
|
|
||||||
titleView.setText(title);
|
|
||||||
|
|
||||||
TextView detailsView = bannerView.findViewById(R.id.itemAdditionalDetails);
|
|
||||||
detailsView.setVisibility(View.GONE);
|
|
||||||
|
|
||||||
dialog = new AlertDialog.Builder(context)
|
|
||||||
.setCustomTitle(bannerView)
|
|
||||||
.setItems(commands, actions)
|
|
||||||
.create();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void show() {
|
|
||||||
dialog.show();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,291 +0,0 @@
|
|||||||
package org.schabi.newpipe.settings;
|
|
||||||
|
|
||||||
import android.app.Dialog;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.content.res.ColorStateList;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
import android.support.design.widget.FloatingActionButton;
|
|
||||||
import android.support.v4.app.Fragment;
|
|
||||||
import android.support.v7.app.AppCompatActivity;
|
|
||||||
import android.support.v7.widget.CardView;
|
|
||||||
import android.support.v7.widget.LinearLayoutManager;
|
|
||||||
import android.support.v7.widget.RecyclerView;
|
|
||||||
import android.support.v7.widget.helper.ItemTouchHelper;
|
|
||||||
import android.util.TypedValue;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.MotionEvent;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.ImageView;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import org.schabi.newpipe.R;
|
|
||||||
import org.schabi.newpipe.util.ThemeHelper;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class ChoseTabsFragment extends Fragment {
|
|
||||||
|
|
||||||
public ChoseTabsFragment.SelectedTabsAdapter selectedTabsAdapter;
|
|
||||||
|
|
||||||
RecyclerView selectedTabsView;
|
|
||||||
|
|
||||||
List<String> selectedTabs = new ArrayList<>();
|
|
||||||
private String saveString;
|
|
||||||
public String[] availableTabs = new String[7];
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
|
||||||
Bundle savedInstanceState) {
|
|
||||||
((AppCompatActivity)getContext()).getSupportActionBar().setTitle(R.string.main_page_content);
|
|
||||||
return inflater.inflate(R.layout.fragment_chose_tabs, container, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onViewCreated(@NonNull View rootView, @Nullable Bundle savedInstanceState) {
|
|
||||||
super.onViewCreated(rootView, savedInstanceState);
|
|
||||||
|
|
||||||
tabNames();
|
|
||||||
initUsedTabs();
|
|
||||||
initButton(rootView);
|
|
||||||
|
|
||||||
selectedTabsView = rootView.findViewById(R.id.usedTabs);
|
|
||||||
selectedTabsView.setLayoutManager(new LinearLayoutManager(getContext()));
|
|
||||||
selectedTabsAdapter = new ChoseTabsFragment.SelectedTabsAdapter();
|
|
||||||
|
|
||||||
|
|
||||||
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(getItemTouchCallback());
|
|
||||||
itemTouchHelper.attachToRecyclerView(selectedTabsView);
|
|
||||||
selectedTabsAdapter.setOnItemSelectedListener(itemTouchHelper);
|
|
||||||
|
|
||||||
selectedTabsView.setAdapter(selectedTabsAdapter);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void saveChanges() {
|
|
||||||
StringBuilder save = new StringBuilder();
|
|
||||||
if(selectedTabs.size()==0) {
|
|
||||||
save = new StringBuilder("0");
|
|
||||||
} else {
|
|
||||||
for(String s: selectedTabs) {
|
|
||||||
save.append(s);
|
|
||||||
save.append("\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
saveString = save.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPause() {
|
|
||||||
saveChanges();
|
|
||||||
SharedPreferences sharedPreferences = android.preference.PreferenceManager.getDefaultSharedPreferences(getContext());
|
|
||||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
|
||||||
editor.putString("saveUsedTabs", saveString);
|
|
||||||
editor.commit();
|
|
||||||
super.onPause();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initUsedTabs() {
|
|
||||||
String save = android.preference.PreferenceManager.getDefaultSharedPreferences(getContext()).getString("saveUsedTabs", "1\tTrending\t0\n2\n4\n");
|
|
||||||
String tabs[] = save.trim().split("\n");
|
|
||||||
selectedTabs.addAll(Arrays.asList(tabs));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void tabNames() {
|
|
||||||
availableTabs[0] = getString(R.string.blank_page_summary);
|
|
||||||
availableTabs[1] = getString(R.string.kiosk_page_summary);
|
|
||||||
availableTabs[2] = getString(R.string.subscription_page_summary);
|
|
||||||
availableTabs[3] = getString(R.string.feed_page_summary);
|
|
||||||
availableTabs[4] = getString(R.string.tab_bookmarks);
|
|
||||||
availableTabs[5] = getString(R.string.title_activity_history);
|
|
||||||
availableTabs[6] = getString(R.string.channel_page_summary);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initButton(View rootView) {
|
|
||||||
FloatingActionButton fab = rootView.findViewById(R.id.floatingActionButton);
|
|
||||||
fab.setImageResource(ThemeHelper.getIconByAttr(R.attr.ic_add, getContext()));
|
|
||||||
fab.setOnClickListener(v -> {
|
|
||||||
Dialog.OnClickListener onClickListener = (dialog, which) -> addTab(which);
|
|
||||||
|
|
||||||
new AddTabsDialog(getContext(),
|
|
||||||
getString(R.string.tab_chose),
|
|
||||||
availableTabs,
|
|
||||||
onClickListener)
|
|
||||||
.show();
|
|
||||||
});
|
|
||||||
|
|
||||||
TypedValue typedValue = new TypedValue();
|
|
||||||
getActivity().getTheme().resolveAttribute(R.attr.colorPrimary, typedValue, true);
|
|
||||||
int color = typedValue.data;
|
|
||||||
fab.setBackgroundTintList(ColorStateList.valueOf(color));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void addTab(int position) {
|
|
||||||
if(position==6) {
|
|
||||||
SelectChannelFragment selectChannelFragment = new SelectChannelFragment();
|
|
||||||
selectChannelFragment.setOnSelectedLisener((String url, String name, int service) -> {
|
|
||||||
selectedTabs.add(position+"\t"+url+"\t"+name+"\t"+service);
|
|
||||||
selectedTabsAdapter.notifyDataSetChanged();
|
|
||||||
saveChanges();
|
|
||||||
});
|
|
||||||
selectChannelFragment.show(getFragmentManager(), "select_channel");
|
|
||||||
} else if(position==1) {
|
|
||||||
SelectKioskFragment selectKioskFragment = new SelectKioskFragment();
|
|
||||||
selectKioskFragment.setOnSelectedLisener((String kioskId, int service_id) -> {
|
|
||||||
selectedTabs.add(position+"\t"+kioskId+"\t"+service_id);
|
|
||||||
selectedTabsAdapter.notifyDataSetChanged();
|
|
||||||
saveChanges();
|
|
||||||
});
|
|
||||||
selectKioskFragment.show(getFragmentManager(), "select_kiosk");
|
|
||||||
} else {
|
|
||||||
selectedTabs.add(String.valueOf(position));
|
|
||||||
selectedTabsAdapter.notifyDataSetChanged();
|
|
||||||
saveChanges();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class SelectedTabsAdapter extends RecyclerView.Adapter<ChoseTabsFragment.SelectedTabsAdapter.TabViewHolder>{
|
|
||||||
private ItemTouchHelper itemTouchHelper;
|
|
||||||
|
|
||||||
public void setOnItemSelectedListener(ItemTouchHelper mItemTouchHelper) {
|
|
||||||
itemTouchHelper = mItemTouchHelper;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void swapItems(int fromPosition, int toPosition) {
|
|
||||||
String temp = selectedTabs.get(fromPosition);
|
|
||||||
selectedTabs.set(fromPosition, selectedTabs.get(toPosition));
|
|
||||||
selectedTabs.set(toPosition, temp);
|
|
||||||
notifyItemMoved(fromPosition, toPosition);
|
|
||||||
saveChanges();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChoseTabsFragment.SelectedTabsAdapter.TabViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
|
||||||
|
|
||||||
LayoutInflater inflater = LayoutInflater.from(getContext());
|
|
||||||
View view = inflater.inflate(R.layout.viewholder_chose_tabs, parent, false);
|
|
||||||
return new ChoseTabsFragment.SelectedTabsAdapter.TabViewHolder(view);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBindViewHolder(@NonNull ChoseTabsFragment.SelectedTabsAdapter.TabViewHolder holder, int position) {
|
|
||||||
holder.bind(position, holder);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getItemCount() {
|
|
||||||
return selectedTabs.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
class TabViewHolder extends RecyclerView.ViewHolder {
|
|
||||||
|
|
||||||
TextView text;
|
|
||||||
View view;
|
|
||||||
CardView cardView;
|
|
||||||
ImageView handle;
|
|
||||||
|
|
||||||
public TabViewHolder(View itemView) {
|
|
||||||
super(itemView);
|
|
||||||
|
|
||||||
text = itemView.findViewById(R.id.tabName);
|
|
||||||
cardView = itemView.findViewById(R.id.layoutCard);
|
|
||||||
handle = itemView.findViewById(R.id.handle);
|
|
||||||
view = itemView;
|
|
||||||
}
|
|
||||||
|
|
||||||
void bind(int position, TabViewHolder holder) {
|
|
||||||
handle.setImageResource(ThemeHelper.getIconByAttr(R.attr.drag_handle, getContext()));
|
|
||||||
handle.setOnTouchListener(getOnTouchListener(holder));
|
|
||||||
|
|
||||||
view.setOnLongClickListener(getOnLongClickListener(holder));
|
|
||||||
|
|
||||||
if(selectedTabs.get(position).startsWith("6\t")) {
|
|
||||||
String channelInfo[] = selectedTabs.get(position).split("\t");
|
|
||||||
String channelName = "";
|
|
||||||
if (channelInfo.length == 4) channelName = channelInfo[2];
|
|
||||||
String textToSet = availableTabs[6] + ": " + channelName;
|
|
||||||
text.setText(textToSet);
|
|
||||||
} else if(selectedTabs.get(position).startsWith("1\t")) {
|
|
||||||
String kioskInfo[] = selectedTabs.get(position).split("\t");
|
|
||||||
String kioskName = "";
|
|
||||||
if (kioskInfo.length == 3) kioskName = kioskInfo[1];
|
|
||||||
String textToSet = availableTabs[1] + ": " + kioskName;
|
|
||||||
text.setText(textToSet);
|
|
||||||
} else {
|
|
||||||
text.setText(availableTabs[Integer.parseInt(selectedTabs.get(position))]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private View.OnTouchListener getOnTouchListener(final RecyclerView.ViewHolder item) {
|
|
||||||
return (view, motionEvent) -> {
|
|
||||||
view.performClick();
|
|
||||||
if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) {
|
|
||||||
if(itemTouchHelper != null) itemTouchHelper.startDrag(item);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private View.OnLongClickListener getOnLongClickListener(TabViewHolder holder) {
|
|
||||||
return (view) -> {
|
|
||||||
if(itemTouchHelper != null) itemTouchHelper.startSwipe(holder);
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private ItemTouchHelper.SimpleCallback getItemTouchCallback() {
|
|
||||||
return new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN,
|
|
||||||
ItemTouchHelper.START | ItemTouchHelper.END) {
|
|
||||||
@Override
|
|
||||||
public int interpolateOutOfBoundsScroll(RecyclerView recyclerView, int viewSize,
|
|
||||||
int viewSizeOutOfBounds, int totalSize,
|
|
||||||
long msSinceStartScroll) {
|
|
||||||
final int standardSpeed = super.interpolateOutOfBoundsScroll(recyclerView, viewSize,
|
|
||||||
viewSizeOutOfBounds, totalSize, msSinceStartScroll);
|
|
||||||
final int minimumAbsVelocity = Math.max(12,
|
|
||||||
Math.abs(standardSpeed));
|
|
||||||
return minimumAbsVelocity * (int) Math.signum(viewSizeOutOfBounds);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder source,
|
|
||||||
RecyclerView.ViewHolder target) {
|
|
||||||
if (source.getItemViewType() != target.getItemViewType() ||
|
|
||||||
selectedTabsAdapter == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
final int sourceIndex = source.getAdapterPosition();
|
|
||||||
final int targetIndex = target.getAdapterPosition();
|
|
||||||
selectedTabsAdapter.swapItems(sourceIndex, targetIndex);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isLongPressDragEnabled() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isItemViewSwipeEnabled() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSwiped(RecyclerView.ViewHolder viewHolder, int swipeDir) {
|
|
||||||
int position = viewHolder.getAdapterPosition();
|
|
||||||
selectedTabs.remove(position);
|
|
||||||
selectedTabsAdapter.notifyItemRemoved(position);
|
|
||||||
saveChanges();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
@ -66,7 +66,7 @@ public class SelectChannelFragment extends DialogFragment {
|
|||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
public interface OnSelectedLisener {
|
public interface OnSelectedLisener {
|
||||||
void onChannelSelected(String url, String name, int service);
|
void onChannelSelected(int serviceId, String url, String name);
|
||||||
}
|
}
|
||||||
OnSelectedLisener onSelectedLisener = null;
|
OnSelectedLisener onSelectedLisener = null;
|
||||||
public void setOnSelectedLisener(OnSelectedLisener listener) {
|
public void setOnSelectedLisener(OnSelectedLisener listener) {
|
||||||
@ -126,7 +126,7 @@ public class SelectChannelFragment extends DialogFragment {
|
|||||||
private void clickedItem(int position) {
|
private void clickedItem(int position) {
|
||||||
if(onSelectedLisener != null) {
|
if(onSelectedLisener != null) {
|
||||||
SubscriptionEntity entry = subscriptions.get(position);
|
SubscriptionEntity entry = subscriptions.get(position);
|
||||||
onSelectedLisener.onChannelSelected(entry.getUrl(), entry.getName(), entry.getServiceId());
|
onSelectedLisener.onChannelSelected(entry.getServiceId(), entry.getUrl(), entry.getName());
|
||||||
}
|
}
|
||||||
dismiss();
|
dismiss();
|
||||||
}
|
}
|
||||||
|
@ -56,7 +56,7 @@ public class SelectKioskFragment extends DialogFragment {
|
|||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
public interface OnSelectedLisener {
|
public interface OnSelectedLisener {
|
||||||
void onKioskSelected(String kioskId, int service_id);
|
void onKioskSelected(int serviceId, String kioskId, String kioskName);
|
||||||
}
|
}
|
||||||
|
|
||||||
OnSelectedLisener onSelectedLisener = null;
|
OnSelectedLisener onSelectedLisener = null;
|
||||||
@ -101,7 +101,7 @@ public class SelectKioskFragment extends DialogFragment {
|
|||||||
|
|
||||||
private void clickedItem(SelectKioskAdapter.Entry entry) {
|
private void clickedItem(SelectKioskAdapter.Entry entry) {
|
||||||
if(onSelectedLisener != null) {
|
if(onSelectedLisener != null) {
|
||||||
onSelectedLisener.onKioskSelected(entry.kioskId, entry.serviceId);
|
onSelectedLisener.onKioskSelected(entry.serviceId, entry.kioskId, entry.kioskName);
|
||||||
}
|
}
|
||||||
dismiss();
|
dismiss();
|
||||||
}
|
}
|
||||||
|
@ -77,7 +77,8 @@ public class SettingsActivity extends AppCompatActivity implements BasePreferenc
|
|||||||
finish();
|
finish();
|
||||||
} else getSupportFragmentManager().popBackStack();
|
} else getSupportFragmentManager().popBackStack();
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -0,0 +1,94 @@
|
|||||||
|
package org.schabi.newpipe.settings.tabs;
|
||||||
|
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.support.annotation.DrawableRes;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.v7.widget.AppCompatImageView;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.BaseAdapter;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.R;
|
||||||
|
import org.schabi.newpipe.util.ThemeHelper;
|
||||||
|
|
||||||
|
public class AddTabDialog {
|
||||||
|
private final AlertDialog dialog;
|
||||||
|
|
||||||
|
AddTabDialog(@NonNull final Context context,
|
||||||
|
@NonNull final ChooseTabListItem[] items,
|
||||||
|
@NonNull final DialogInterface.OnClickListener actions) {
|
||||||
|
|
||||||
|
dialog = new AlertDialog.Builder(context)
|
||||||
|
.setTitle(context.getString(R.string.tab_choose))
|
||||||
|
.setAdapter(new DialogListAdapter(context, items), actions)
|
||||||
|
.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void show() {
|
||||||
|
dialog.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class ChooseTabListItem {
|
||||||
|
final int tabId;
|
||||||
|
final String itemName;
|
||||||
|
@DrawableRes final int itemIcon;
|
||||||
|
|
||||||
|
ChooseTabListItem(Context context, Tab tab) {
|
||||||
|
this(tab.getTabId(), tab.getTabName(context), tab.getTabIconRes(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
ChooseTabListItem(int tabId, String itemName, @DrawableRes int itemIcon) {
|
||||||
|
this.tabId = tabId;
|
||||||
|
this.itemName = itemName;
|
||||||
|
this.itemIcon = itemIcon;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class DialogListAdapter extends BaseAdapter {
|
||||||
|
private final LayoutInflater inflater;
|
||||||
|
private final ChooseTabListItem[] items;
|
||||||
|
|
||||||
|
@DrawableRes private final int fallbackIcon;
|
||||||
|
|
||||||
|
private DialogListAdapter(Context context, ChooseTabListItem[] items) {
|
||||||
|
this.inflater = LayoutInflater.from(context);
|
||||||
|
this.items = items;
|
||||||
|
this.fallbackIcon = ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_hot);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCount() {
|
||||||
|
return items.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChooseTabListItem getItem(int position) {
|
||||||
|
return items[position];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getItemId(int position) {
|
||||||
|
return getItem(position).tabId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View getView(int position, View convertView, ViewGroup parent) {
|
||||||
|
if (convertView == null) {
|
||||||
|
convertView = inflater.inflate(R.layout.list_choose_tabs_dialog, parent, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
final ChooseTabListItem item = getItem(position);
|
||||||
|
final AppCompatImageView tabIconView = convertView.findViewById(R.id.tabIcon);
|
||||||
|
final TextView tabNameView = convertView.findViewById(R.id.tabName);
|
||||||
|
|
||||||
|
tabIconView.setImageResource(item.itemIcon > 0 ? item.itemIcon : fallbackIcon);
|
||||||
|
tabNameView.setText(item.itemName);
|
||||||
|
|
||||||
|
return convertView;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,386 @@
|
|||||||
|
package org.schabi.newpipe.settings.tabs;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.app.Dialog;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.design.widget.FloatingActionButton;
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
|
import android.support.v7.app.ActionBar;
|
||||||
|
import android.support.v7.app.AlertDialog;
|
||||||
|
import android.support.v7.app.AppCompatActivity;
|
||||||
|
import android.support.v7.content.res.AppCompatResources;
|
||||||
|
import android.support.v7.widget.AppCompatImageView;
|
||||||
|
import android.support.v7.widget.LinearLayoutManager;
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
import android.support.v7.widget.helper.ItemTouchHelper;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuInflater;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.R;
|
||||||
|
import org.schabi.newpipe.extractor.NewPipe;
|
||||||
|
import org.schabi.newpipe.report.ErrorActivity;
|
||||||
|
import org.schabi.newpipe.report.UserAction;
|
||||||
|
import org.schabi.newpipe.settings.SelectChannelFragment;
|
||||||
|
import org.schabi.newpipe.settings.SelectKioskFragment;
|
||||||
|
import org.schabi.newpipe.settings.tabs.AddTabDialog.ChooseTabListItem;
|
||||||
|
import org.schabi.newpipe.util.ThemeHelper;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.settings.tabs.Tab.typeFrom;
|
||||||
|
|
||||||
|
public class ChooseTabsFragment extends Fragment {
|
||||||
|
|
||||||
|
private TabsManager tabsManager;
|
||||||
|
private List<Tab> tabList = new ArrayList<>();
|
||||||
|
public ChooseTabsFragment.SelectedTabsAdapter selectedTabsAdapter;
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
// Lifecycle
|
||||||
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
tabsManager = TabsManager.getManager(requireContext());
|
||||||
|
updateTabList();
|
||||||
|
|
||||||
|
setHasOptionsMenu(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
|
return inflater.inflate(R.layout.fragment_choose_tabs, container, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(@NonNull View rootView, @Nullable Bundle savedInstanceState) {
|
||||||
|
super.onViewCreated(rootView, savedInstanceState);
|
||||||
|
|
||||||
|
initButton(rootView);
|
||||||
|
|
||||||
|
RecyclerView listSelectedTabs = rootView.findViewById(R.id.selectedTabs);
|
||||||
|
listSelectedTabs.setLayoutManager(new LinearLayoutManager(requireContext()));
|
||||||
|
|
||||||
|
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(getItemTouchCallback());
|
||||||
|
itemTouchHelper.attachToRecyclerView(listSelectedTabs);
|
||||||
|
|
||||||
|
selectedTabsAdapter = new SelectedTabsAdapter(requireContext(), itemTouchHelper);
|
||||||
|
listSelectedTabs.setAdapter(selectedTabsAdapter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
updateTitle();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPause() {
|
||||||
|
super.onPause();
|
||||||
|
saveChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
// Menu
|
||||||
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
private final int MENU_ITEM_RESTORE_ID = 123456;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||||
|
super.onCreateOptionsMenu(menu, inflater);
|
||||||
|
|
||||||
|
final MenuItem restoreItem = menu.add(Menu.NONE, MENU_ITEM_RESTORE_ID, Menu.NONE, R.string.restore_defaults);
|
||||||
|
restoreItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
|
||||||
|
|
||||||
|
final int restoreIcon = ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_restore_defaults);
|
||||||
|
restoreItem.setIcon(AppCompatResources.getDrawable(requireContext(), restoreIcon));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
if (item.getItemId() == MENU_ITEM_RESTORE_ID) {
|
||||||
|
restoreDefaults();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
// Utils
|
||||||
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
private void updateTabList() {
|
||||||
|
tabList.clear();
|
||||||
|
tabList.addAll(tabsManager.getTabs());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateTitle() {
|
||||||
|
if (getActivity() instanceof AppCompatActivity) {
|
||||||
|
ActionBar actionBar = ((AppCompatActivity) getActivity()).getSupportActionBar();
|
||||||
|
if (actionBar != null) actionBar.setTitle(R.string.main_page_content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveChanges() {
|
||||||
|
tabsManager.saveTabs(tabList);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void restoreDefaults() {
|
||||||
|
new AlertDialog.Builder(requireContext(), ThemeHelper.getDialogTheme(requireContext()))
|
||||||
|
.setTitle(R.string.restore_defaults)
|
||||||
|
.setMessage(R.string.restore_defaults_confirmation)
|
||||||
|
.setNegativeButton(R.string.cancel, null)
|
||||||
|
.setPositiveButton(R.string.yes, (dialog, which) -> {
|
||||||
|
tabsManager.resetTabs();
|
||||||
|
updateTabList();
|
||||||
|
selectedTabsAdapter.notifyDataSetChanged();
|
||||||
|
})
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initButton(View rootView) {
|
||||||
|
final FloatingActionButton fab = rootView.findViewById(R.id.addTabsButton);
|
||||||
|
fab.setOnClickListener(v -> {
|
||||||
|
final ChooseTabListItem[] availableTabs = getAvailableTabs(requireContext());
|
||||||
|
|
||||||
|
if (availableTabs.length == 0) {
|
||||||
|
//Toast.makeText(requireContext(), "No available tabs", Toast.LENGTH_SHORT).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Dialog.OnClickListener actionListener = (dialog, which) -> {
|
||||||
|
final ChooseTabListItem selected = availableTabs[which];
|
||||||
|
addTab(selected.tabId);
|
||||||
|
};
|
||||||
|
|
||||||
|
new AddTabDialog(requireContext(), availableTabs, actionListener)
|
||||||
|
.show();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addTab(final Tab tab) {
|
||||||
|
tabList.add(tab);
|
||||||
|
selectedTabsAdapter.notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addTab(int tabId) {
|
||||||
|
final Tab.Type type = typeFrom(tabId);
|
||||||
|
|
||||||
|
if (type == null) {
|
||||||
|
ErrorActivity.reportError(requireContext(), new IllegalStateException("Tab id not found: " + tabId), null, null,
|
||||||
|
ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none", "Choosing tabs on settings", 0));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case KIOSK: {
|
||||||
|
SelectKioskFragment selectFragment = new SelectKioskFragment();
|
||||||
|
selectFragment.setOnSelectedLisener((serviceId, kioskId, kioskName) ->
|
||||||
|
addTab(new Tab.KioskTab(serviceId, kioskId)));
|
||||||
|
selectFragment.show(requireFragmentManager(), "select_kiosk");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case CHANNEL: {
|
||||||
|
SelectChannelFragment selectFragment = new SelectChannelFragment();
|
||||||
|
selectFragment.setOnSelectedLisener((serviceId, url, name) ->
|
||||||
|
addTab(new Tab.ChannelTab(serviceId, url, name)));
|
||||||
|
selectFragment.show(requireFragmentManager(), "select_channel");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
addTab(type.getTab());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChooseTabListItem[] getAvailableTabs(Context context) {
|
||||||
|
final ArrayList<ChooseTabListItem> returnList = new ArrayList<>();
|
||||||
|
|
||||||
|
for (Tab.Type type : Tab.Type.values()) {
|
||||||
|
final Tab tab = type.getTab();
|
||||||
|
switch (type) {
|
||||||
|
case BLANK:
|
||||||
|
if (!tabList.contains(tab)) {
|
||||||
|
returnList.add(new ChooseTabListItem(tab.getTabId(), getString(R.string.blank_page_summary),
|
||||||
|
tab.getTabIconRes(context)));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case KIOSK:
|
||||||
|
returnList.add(new ChooseTabListItem(tab.getTabId(), getString(R.string.kiosk_page_summary),
|
||||||
|
ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_hot)));
|
||||||
|
break;
|
||||||
|
case CHANNEL:
|
||||||
|
returnList.add(new ChooseTabListItem(tab.getTabId(), getString(R.string.channel_page_summary),
|
||||||
|
tab.getTabIconRes(context)));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (!tabList.contains(tab)) {
|
||||||
|
returnList.add(new ChooseTabListItem(context, tab));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnList.toArray(new ChooseTabListItem[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
// List Handling
|
||||||
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
private class SelectedTabsAdapter extends RecyclerView.Adapter<ChooseTabsFragment.SelectedTabsAdapter.TabViewHolder> {
|
||||||
|
private ItemTouchHelper itemTouchHelper;
|
||||||
|
private final LayoutInflater inflater;
|
||||||
|
|
||||||
|
SelectedTabsAdapter(Context context, ItemTouchHelper itemTouchHelper) {
|
||||||
|
this.itemTouchHelper = itemTouchHelper;
|
||||||
|
this.inflater = LayoutInflater.from(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void swapItems(int fromPosition, int toPosition) {
|
||||||
|
Collections.swap(tabList, fromPosition, toPosition);
|
||||||
|
notifyItemMoved(fromPosition, toPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public ChooseTabsFragment.SelectedTabsAdapter.TabViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||||
|
View view = inflater.inflate(R.layout.list_choose_tabs, parent, false);
|
||||||
|
return new ChooseTabsFragment.SelectedTabsAdapter.TabViewHolder(view);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(@NonNull ChooseTabsFragment.SelectedTabsAdapter.TabViewHolder holder, int position) {
|
||||||
|
holder.bind(position, holder);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return tabList.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
class TabViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
private AppCompatImageView tabIconView;
|
||||||
|
private TextView tabNameView;
|
||||||
|
private ImageView handle;
|
||||||
|
|
||||||
|
TabViewHolder(View itemView) {
|
||||||
|
super(itemView);
|
||||||
|
|
||||||
|
tabNameView = itemView.findViewById(R.id.tabName);
|
||||||
|
tabIconView = itemView.findViewById(R.id.tabIcon);
|
||||||
|
handle = itemView.findViewById(R.id.handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
|
void bind(int position, TabViewHolder holder) {
|
||||||
|
handle.setOnTouchListener(getOnTouchListener(holder));
|
||||||
|
|
||||||
|
final Tab tab = tabList.get(position);
|
||||||
|
final Tab.Type type = Tab.typeFrom(tab.getTabId());
|
||||||
|
|
||||||
|
if (type == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String tabName = tab.getTabName(requireContext());
|
||||||
|
switch (type) {
|
||||||
|
case BLANK:
|
||||||
|
tabName = requireContext().getString(R.string.blank_page_summary);
|
||||||
|
break;
|
||||||
|
case KIOSK:
|
||||||
|
tabName = NewPipe.getNameOfService(((Tab.KioskTab) tab).getKioskServiceId()) + "/" + tabName;
|
||||||
|
break;
|
||||||
|
case CHANNEL:
|
||||||
|
tabName = NewPipe.getNameOfService(((Tab.ChannelTab) tab).getChannelServiceId()) + "/" + tabName;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
tabNameView.setText(tabName);
|
||||||
|
tabIconView.setImageResource(tab.getTabIconRes(requireContext()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
|
private View.OnTouchListener getOnTouchListener(final RecyclerView.ViewHolder item) {
|
||||||
|
return (view, motionEvent) -> {
|
||||||
|
if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) {
|
||||||
|
if (itemTouchHelper != null && getItemCount() > 1) {
|
||||||
|
itemTouchHelper.startDrag(item);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ItemTouchHelper.SimpleCallback getItemTouchCallback() {
|
||||||
|
return new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN,
|
||||||
|
ItemTouchHelper.START | ItemTouchHelper.END) {
|
||||||
|
@Override
|
||||||
|
public int interpolateOutOfBoundsScroll(RecyclerView recyclerView, int viewSize,
|
||||||
|
int viewSizeOutOfBounds, int totalSize,
|
||||||
|
long msSinceStartScroll) {
|
||||||
|
final int standardSpeed = super.interpolateOutOfBoundsScroll(recyclerView, viewSize,
|
||||||
|
viewSizeOutOfBounds, totalSize, msSinceStartScroll);
|
||||||
|
final int minimumAbsVelocity = Math.max(12,
|
||||||
|
Math.abs(standardSpeed));
|
||||||
|
return minimumAbsVelocity * (int) Math.signum(viewSizeOutOfBounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder source,
|
||||||
|
RecyclerView.ViewHolder target) {
|
||||||
|
if (source.getItemViewType() != target.getItemViewType() ||
|
||||||
|
selectedTabsAdapter == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final int sourceIndex = source.getAdapterPosition();
|
||||||
|
final int targetIndex = target.getAdapterPosition();
|
||||||
|
selectedTabsAdapter.swapItems(sourceIndex, targetIndex);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isLongPressDragEnabled() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isItemViewSwipeEnabled() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSwiped(RecyclerView.ViewHolder viewHolder, int swipeDir) {
|
||||||
|
int position = viewHolder.getAdapterPosition();
|
||||||
|
tabList.remove(position);
|
||||||
|
selectedTabsAdapter.notifyItemRemoved(position);
|
||||||
|
|
||||||
|
if (tabList.isEmpty()) {
|
||||||
|
tabList.add(Tab.Type.BLANK.getTab());
|
||||||
|
selectedTabsAdapter.notifyItemInserted(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
416
app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java
Normal file
416
app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.java
Normal file
@ -0,0 +1,416 @@
|
|||||||
|
package org.schabi.newpipe.settings.tabs;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.support.annotation.DrawableRes;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
|
|
||||||
|
import com.grack.nanojson.JsonObject;
|
||||||
|
import com.grack.nanojson.JsonSink;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.R;
|
||||||
|
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||||
|
import org.schabi.newpipe.fragments.BlankFragment;
|
||||||
|
import org.schabi.newpipe.fragments.list.channel.ChannelFragment;
|
||||||
|
import org.schabi.newpipe.fragments.list.kiosk.KioskFragment;
|
||||||
|
import org.schabi.newpipe.local.bookmark.BookmarkFragment;
|
||||||
|
import org.schabi.newpipe.local.feed.FeedFragment;
|
||||||
|
import org.schabi.newpipe.local.history.StatisticsPlaylistFragment;
|
||||||
|
import org.schabi.newpipe.local.subscription.SubscriptionFragment;
|
||||||
|
import org.schabi.newpipe.util.KioskTranslator;
|
||||||
|
import org.schabi.newpipe.util.ThemeHelper;
|
||||||
|
|
||||||
|
public abstract class Tab {
|
||||||
|
Tab() {
|
||||||
|
}
|
||||||
|
|
||||||
|
Tab(@NonNull JsonObject jsonObject) {
|
||||||
|
readDataFromJson(jsonObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract int getTabId();
|
||||||
|
public abstract String getTabName(Context context);
|
||||||
|
@DrawableRes public abstract int getTabIconRes(Context context);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a instance of the fragment that this tab represent.
|
||||||
|
*/
|
||||||
|
public abstract Fragment getFragment() throws ExtractionException;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
return obj instanceof Tab && obj.getClass().equals(this.getClass())
|
||||||
|
&& ((Tab) obj).getTabId() == this.getTabId();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
// JSON Handling
|
||||||
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
private static final String JSON_TAB_ID_KEY = "tab_id";
|
||||||
|
|
||||||
|
public void writeJsonOn(JsonSink jsonSink) {
|
||||||
|
jsonSink.object();
|
||||||
|
|
||||||
|
jsonSink.value(JSON_TAB_ID_KEY, getTabId());
|
||||||
|
writeDataToJson(jsonSink);
|
||||||
|
|
||||||
|
jsonSink.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void writeDataToJson(JsonSink writerSink) {
|
||||||
|
// No-op
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void readDataFromJson(JsonObject jsonObject) {
|
||||||
|
// No-op
|
||||||
|
}
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
// Tab Handling
|
||||||
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static Tab from(@NonNull JsonObject jsonObject) {
|
||||||
|
final int tabId = jsonObject.getInt(Tab.JSON_TAB_ID_KEY, -1);
|
||||||
|
|
||||||
|
if (tabId == -1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return from(tabId, jsonObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static Tab from(final int tabId) {
|
||||||
|
return from(tabId, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static Type typeFrom(int tabId) {
|
||||||
|
for (Type available : Type.values()) {
|
||||||
|
if (available.getTabId() == tabId) {
|
||||||
|
return available;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static Tab from(final int tabId, @Nullable JsonObject jsonObject) {
|
||||||
|
final Type type = typeFrom(tabId);
|
||||||
|
|
||||||
|
if (type == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (jsonObject != null) {
|
||||||
|
switch (type) {
|
||||||
|
case KIOSK:
|
||||||
|
return new KioskTab(jsonObject);
|
||||||
|
case CHANNEL:
|
||||||
|
return new ChannelTab(jsonObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return type.getTab();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
// Implementations
|
||||||
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
public enum Type {
|
||||||
|
BLANK(new BlankTab()),
|
||||||
|
SUBSCRIPTIONS(new SubscriptionsTab()),
|
||||||
|
FEED(new FeedTab()),
|
||||||
|
BOOKMARKS(new BookmarksTab()),
|
||||||
|
HISTORY(new HistoryTab()),
|
||||||
|
KIOSK(new KioskTab()),
|
||||||
|
CHANNEL(new ChannelTab());
|
||||||
|
|
||||||
|
private Tab tab;
|
||||||
|
|
||||||
|
Type(Tab tab) {
|
||||||
|
this.tab = tab;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTabId() {
|
||||||
|
return tab.getTabId();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Tab getTab() {
|
||||||
|
return tab;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class BlankTab extends Tab {
|
||||||
|
public static final int ID = 0;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getTabId() {
|
||||||
|
return ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getTabName(Context context) {
|
||||||
|
return "NewPipe"; //context.getString(R.string.blank_page_summary);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DrawableRes
|
||||||
|
@Override
|
||||||
|
public int getTabIconRes(Context context) {
|
||||||
|
return ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_blank_page);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BlankFragment getFragment() {
|
||||||
|
return new BlankFragment();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class SubscriptionsTab extends Tab {
|
||||||
|
public static final int ID = 1;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getTabId() {
|
||||||
|
return ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getTabName(Context context) {
|
||||||
|
return context.getString(R.string.tab_subscriptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DrawableRes
|
||||||
|
@Override
|
||||||
|
public int getTabIconRes(Context context) {
|
||||||
|
return ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SubscriptionFragment getFragment() {
|
||||||
|
return new SubscriptionFragment();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class FeedTab extends Tab {
|
||||||
|
public static final int ID = 2;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getTabId() {
|
||||||
|
return ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getTabName(Context context) {
|
||||||
|
return context.getString(R.string.fragment_whats_new);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DrawableRes
|
||||||
|
@Override
|
||||||
|
public int getTabIconRes(Context context) {
|
||||||
|
return ThemeHelper.resolveResourceIdFromAttr(context, R.attr.rss);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FeedFragment getFragment() {
|
||||||
|
return new FeedFragment();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class BookmarksTab extends Tab {
|
||||||
|
public static final int ID = 3;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getTabId() {
|
||||||
|
return ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getTabName(Context context) {
|
||||||
|
return context.getString(R.string.tab_bookmarks);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DrawableRes
|
||||||
|
@Override
|
||||||
|
public int getTabIconRes(Context context) {
|
||||||
|
return ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_bookmark);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BookmarkFragment getFragment() {
|
||||||
|
return new BookmarkFragment();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class HistoryTab extends Tab {
|
||||||
|
public static final int ID = 4;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getTabId() {
|
||||||
|
return ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getTabName(Context context) {
|
||||||
|
return context.getString(R.string.title_activity_history);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DrawableRes
|
||||||
|
@Override
|
||||||
|
public int getTabIconRes(Context context) {
|
||||||
|
return ThemeHelper.resolveResourceIdFromAttr(context, R.attr.history);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StatisticsPlaylistFragment getFragment() {
|
||||||
|
return new StatisticsPlaylistFragment();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class KioskTab extends Tab {
|
||||||
|
public static final int ID = 5;
|
||||||
|
|
||||||
|
private int kioskServiceId;
|
||||||
|
private String kioskId;
|
||||||
|
|
||||||
|
private static final String JSON_KIOSK_SERVICE_ID_KEY = "service_id";
|
||||||
|
private static final String JSON_KIOSK_ID_KEY = "kiosk_id";
|
||||||
|
|
||||||
|
private KioskTab() {
|
||||||
|
this(-1, "<no-id>");
|
||||||
|
}
|
||||||
|
|
||||||
|
public KioskTab(int kioskServiceId, String kioskId) {
|
||||||
|
this.kioskServiceId = kioskServiceId;
|
||||||
|
this.kioskId = kioskId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public KioskTab(JsonObject jsonObject) {
|
||||||
|
super(jsonObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getTabId() {
|
||||||
|
return ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getTabName(Context context) {
|
||||||
|
return KioskTranslator.getTranslatedKioskName(kioskId, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DrawableRes
|
||||||
|
@Override
|
||||||
|
public int getTabIconRes(Context context) {
|
||||||
|
final int kioskIcon = KioskTranslator.getKioskIcons(kioskId, context);
|
||||||
|
|
||||||
|
if (kioskIcon <= 0) {
|
||||||
|
throw new IllegalStateException("Kiosk ID is not valid: \"" + kioskId + "\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
return kioskIcon;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public KioskFragment getFragment() throws ExtractionException {
|
||||||
|
return KioskFragment.getInstance(kioskServiceId, kioskId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void writeDataToJson(JsonSink writerSink) {
|
||||||
|
writerSink.value(JSON_KIOSK_SERVICE_ID_KEY, kioskServiceId)
|
||||||
|
.value(JSON_KIOSK_ID_KEY, kioskId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void readDataFromJson(JsonObject jsonObject) {
|
||||||
|
kioskServiceId = jsonObject.getInt(JSON_KIOSK_SERVICE_ID_KEY, -1);
|
||||||
|
kioskId = jsonObject.getString(JSON_KIOSK_ID_KEY, "<no-id>");
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getKioskServiceId() {
|
||||||
|
return kioskServiceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getKioskId() {
|
||||||
|
return kioskId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ChannelTab extends Tab {
|
||||||
|
public static final int ID = 6;
|
||||||
|
|
||||||
|
private int channelServiceId;
|
||||||
|
private String channelUrl;
|
||||||
|
private String channelName;
|
||||||
|
|
||||||
|
private static final String JSON_CHANNEL_SERVICE_ID_KEY = "channel_service_id";
|
||||||
|
private static final String JSON_CHANNEL_URL_KEY = "channel_url";
|
||||||
|
private static final String JSON_CHANNEL_NAME_KEY = "channel_name";
|
||||||
|
|
||||||
|
private ChannelTab() {
|
||||||
|
this(-1, "<no-url>", "<no-name>");
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChannelTab(int channelServiceId, String channelUrl, String channelName) {
|
||||||
|
this.channelServiceId = channelServiceId;
|
||||||
|
this.channelUrl = channelUrl;
|
||||||
|
this.channelName = channelName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChannelTab(JsonObject jsonObject) {
|
||||||
|
super(jsonObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getTabId() {
|
||||||
|
return ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getTabName(Context context) {
|
||||||
|
return channelName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@DrawableRes
|
||||||
|
@Override
|
||||||
|
public int getTabIconRes(Context context) {
|
||||||
|
return ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFragment getFragment() {
|
||||||
|
return ChannelFragment.getInstance(channelServiceId, channelUrl, channelName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void writeDataToJson(JsonSink writerSink) {
|
||||||
|
writerSink.value(JSON_CHANNEL_SERVICE_ID_KEY, channelServiceId)
|
||||||
|
.value(JSON_CHANNEL_URL_KEY, channelUrl)
|
||||||
|
.value(JSON_CHANNEL_NAME_KEY, channelName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void readDataFromJson(JsonObject jsonObject) {
|
||||||
|
channelServiceId = jsonObject.getInt(JSON_CHANNEL_SERVICE_ID_KEY, -1);
|
||||||
|
channelUrl = jsonObject.getString(JSON_CHANNEL_URL_KEY, "<no-url>");
|
||||||
|
channelName = jsonObject.getString(JSON_CHANNEL_NAME_KEY, "<no-name>");
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getChannelServiceId() {
|
||||||
|
return channelServiceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getChannelUrl() {
|
||||||
|
return channelUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getChannelName() {
|
||||||
|
return channelName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,114 @@
|
|||||||
|
package org.schabi.newpipe.settings.tabs;
|
||||||
|
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.grack.nanojson.JsonArray;
|
||||||
|
import com.grack.nanojson.JsonObject;
|
||||||
|
import com.grack.nanojson.JsonParser;
|
||||||
|
import com.grack.nanojson.JsonParserException;
|
||||||
|
import com.grack.nanojson.JsonStringWriter;
|
||||||
|
import com.grack.nanojson.JsonWriter;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.settings.tabs.Tab.Type;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.extractor.ServiceList.YouTube;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class to get a JSON representation of a list of tabs, and the other way around.
|
||||||
|
*/
|
||||||
|
public class TabsJsonHelper {
|
||||||
|
private static final String JSON_TABS_ARRAY_KEY = "tabs";
|
||||||
|
|
||||||
|
protected static final List<Tab> FALLBACK_INITIAL_TABS_LIST = Collections.unmodifiableList(Arrays.asList(
|
||||||
|
new Tab.KioskTab(YouTube.getServiceId(), "Trending"),
|
||||||
|
Type.SUBSCRIPTIONS.getTab(),
|
||||||
|
Type.BOOKMARKS.getTab()
|
||||||
|
));
|
||||||
|
|
||||||
|
public static class InvalidJsonException extends Exception {
|
||||||
|
private InvalidJsonException() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
private InvalidJsonException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private InvalidJsonException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to reads the passed JSON and returns the list of tabs if no error were encountered.
|
||||||
|
* <p>
|
||||||
|
* If the JSON is null or empty, or the list of tabs that it represents is empty, the
|
||||||
|
* {@link #FALLBACK_INITIAL_TABS_LIST fallback list} will be returned.
|
||||||
|
* <p>
|
||||||
|
* Tabs with invalid ids (i.e. not in the {@link Tab.Type} enum) will be ignored.
|
||||||
|
*
|
||||||
|
* @param tabsJson a JSON string got from {@link #getJsonToSave(List)}.
|
||||||
|
* @return a list of {@link Tab tabs}.
|
||||||
|
* @throws InvalidJsonException if the JSON string is not valid
|
||||||
|
*/
|
||||||
|
public static List<Tab> getTabsFromJson(@Nullable String tabsJson) throws InvalidJsonException {
|
||||||
|
if (tabsJson == null || tabsJson.isEmpty()) {
|
||||||
|
return FALLBACK_INITIAL_TABS_LIST;
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<Tab> returnTabs = new ArrayList<>();
|
||||||
|
|
||||||
|
final JsonObject outerJsonObject;
|
||||||
|
try {
|
||||||
|
outerJsonObject = JsonParser.object().from(tabsJson);
|
||||||
|
final JsonArray tabsArray = outerJsonObject.getArray(JSON_TABS_ARRAY_KEY);
|
||||||
|
|
||||||
|
if (tabsArray == null) {
|
||||||
|
throw new InvalidJsonException("JSON doesn't contain \"" + JSON_TABS_ARRAY_KEY + "\" array");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Object o : tabsArray) {
|
||||||
|
if (!(o instanceof JsonObject)) continue;
|
||||||
|
|
||||||
|
final Tab tab = Tab.from((JsonObject) o);
|
||||||
|
|
||||||
|
if (tab != null) {
|
||||||
|
returnTabs.add(tab);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (JsonParserException e) {
|
||||||
|
throw new InvalidJsonException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (returnTabs.isEmpty()) {
|
||||||
|
return FALLBACK_INITIAL_TABS_LIST;
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnTabs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a JSON representation from a list of tabs.
|
||||||
|
*
|
||||||
|
* @param tabList a list of {@link Tab tabs}.
|
||||||
|
* @return a JSON string representing the list of tabs
|
||||||
|
*/
|
||||||
|
public static String getJsonToSave(@Nullable List<Tab> tabList) {
|
||||||
|
final JsonStringWriter jsonWriter = JsonWriter.string();
|
||||||
|
jsonWriter.object();
|
||||||
|
|
||||||
|
jsonWriter.array(JSON_TABS_ARRAY_KEY);
|
||||||
|
if (tabList != null) for (Tab tab : tabList) {
|
||||||
|
tab.writeJsonOn(jsonWriter);
|
||||||
|
}
|
||||||
|
jsonWriter.end();
|
||||||
|
|
||||||
|
jsonWriter.end();
|
||||||
|
return jsonWriter.done();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,93 @@
|
|||||||
|
package org.schabi.newpipe.settings.tabs;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.preference.PreferenceManager;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.R;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class TabsManager {
|
||||||
|
private final SharedPreferences sharedPreferences;
|
||||||
|
private final String savedTabsKey;
|
||||||
|
private final Context context;
|
||||||
|
|
||||||
|
public static TabsManager getManager(Context context) {
|
||||||
|
return new TabsManager(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TabsManager(Context context) {
|
||||||
|
this.context = context;
|
||||||
|
this.sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
this.savedTabsKey = context.getString(R.string.saved_tabs_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Tab> getTabs() {
|
||||||
|
final String savedJson = sharedPreferences.getString(savedTabsKey, null);
|
||||||
|
try {
|
||||||
|
return TabsJsonHelper.getTabsFromJson(savedJson);
|
||||||
|
} catch (TabsJsonHelper.InvalidJsonException e) {
|
||||||
|
Toast.makeText(context, R.string.saved_tabs_invalid_json, Toast.LENGTH_SHORT).show();
|
||||||
|
return getDefaultTabs();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void saveTabs(List<Tab> tabList) {
|
||||||
|
final String jsonToSave = TabsJsonHelper.getJsonToSave(tabList);
|
||||||
|
sharedPreferences.edit().putString(savedTabsKey, jsonToSave).apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resetTabs() {
|
||||||
|
sharedPreferences.edit().remove(savedTabsKey).apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Tab> getDefaultTabs() {
|
||||||
|
return TabsJsonHelper.FALLBACK_INITIAL_TABS_LIST;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
// Listener
|
||||||
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
public interface SavedTabsChangeListener {
|
||||||
|
void onTabsChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private SavedTabsChangeListener savedTabsChangeListener;
|
||||||
|
private SharedPreferences.OnSharedPreferenceChangeListener preferenceChangeListener;
|
||||||
|
|
||||||
|
public void setSavedTabsListener(SavedTabsChangeListener listener) {
|
||||||
|
if (preferenceChangeListener != null) {
|
||||||
|
sharedPreferences.unregisterOnSharedPreferenceChangeListener(preferenceChangeListener);
|
||||||
|
}
|
||||||
|
savedTabsChangeListener = listener;
|
||||||
|
preferenceChangeListener = getPreferenceChangeListener();
|
||||||
|
sharedPreferences.registerOnSharedPreferenceChangeListener(preferenceChangeListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unsetSavedTabsListener() {
|
||||||
|
if (preferenceChangeListener != null) {
|
||||||
|
sharedPreferences.unregisterOnSharedPreferenceChangeListener(preferenceChangeListener);
|
||||||
|
}
|
||||||
|
preferenceChangeListener = null;
|
||||||
|
savedTabsChangeListener = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SharedPreferences.OnSharedPreferenceChangeListener getPreferenceChangeListener() {
|
||||||
|
return (sharedPreferences, key) -> {
|
||||||
|
if (key.equals(savedTabsKey)) {
|
||||||
|
if (savedTabsChangeListener != null) savedTabsChangeListener.onTabsChanged();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
9
app/src/main/res/drawable/ic_blank_page_black_24dp.xml
Normal file
9
app/src/main/res/drawable/ic_blank_page_black_24dp.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24.0"
|
||||||
|
android:viewportHeight="24.0">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M17,3L7,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2L19,5c0,-1.1 -0.9,-2 -2,-2zM17,19L7,19L7,5h10v14z"/>
|
||||||
|
</vector>
|
9
app/src/main/res/drawable/ic_blank_page_white_24dp.xml
Normal file
9
app/src/main/res/drawable/ic_blank_page_white_24dp.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24.0"
|
||||||
|
android:viewportHeight="24.0">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFFFF"
|
||||||
|
android:pathData="M17,3L7,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2L19,5c0,-1.1 -0.9,-2 -2,-2zM17,19L7,19L7,5h10v14z"/>
|
||||||
|
</vector>
|
@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24.0"
|
||||||
|
android:viewportHeight="24.0">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M14,12c0,-1.1 -0.9,-2 -2,-2s-2,0.9 -2,2 0.9,2 2,2 2,-0.9 2,-2zM12,3c-4.97,0 -9,4.03 -9,9L0,12l4,4 4,-4L5,12c0,-3.87 3.13,-7 7,-7s7,3.13 7,7 -3.13,7 -7,7c-1.51,0 -2.91,-0.49 -4.06,-1.3l-1.42,1.44C8.04,20.3 9.94,21 12,21c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9z"/>
|
||||||
|
</vector>
|
@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24.0"
|
||||||
|
android:viewportHeight="24.0">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFFFF"
|
||||||
|
android:pathData="M14,12c0,-1.1 -0.9,-2 -2,-2s-2,0.9 -2,2 0.9,2 2,2 2,-0.9 2,-2zM12,3c-4.97,0 -9,4.03 -9,9L0,12l4,4 4,-4L5,12c0,-3.87 3.13,-7 7,-7s7,3.13 7,7 -3.13,7 -7,7c-1.51,0 -2.91,-0.49 -4.06,-1.3l-1.42,1.44C8.04,20.3 9.94,21 12,21c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9z"/>
|
||||||
|
</vector>
|
@ -61,7 +61,7 @@
|
|||||||
android:layout_below="@+id/channel_title_view"
|
android:layout_below="@+id/channel_title_view"
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:gravity="left|center"
|
android:gravity="left|center"
|
||||||
android:lines="1"
|
android:maxLines="2"
|
||||||
android:textSize="@dimen/channel_subscribers_text_size"
|
android:textSize="@dimen/channel_subscribers_text_size"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
tools:ignore="RtlHardcoded"
|
tools:ignore="RtlHardcoded"
|
||||||
|
@ -1,32 +1,32 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<RelativeLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:id="@+id/relLay"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<android.support.v7.widget.RecyclerView
|
<android.support.v7.widget.RecyclerView
|
||||||
android:id="@+id/usedTabs"
|
android:id="@+id/selectedTabs"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_margin="0dp"
|
tools:listitem="@layout/list_choose_tabs"/>
|
||||||
android:paddingBottom="0dp"
|
|
||||||
android:paddingTop="0dp" >
|
|
||||||
|
|
||||||
</android.support.v7.widget.RecyclerView>
|
|
||||||
|
|
||||||
<android.support.design.widget.FloatingActionButton
|
<android.support.design.widget.FloatingActionButton
|
||||||
android:id="@+id/floatingActionButton"
|
android:id="@+id/addTabsButton"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignParentBottom="true"
|
android:layout_alignParentBottom="true"
|
||||||
android:layout_alignParentEnd="true"
|
android:layout_alignParentEnd="true"
|
||||||
|
android:layout_alignParentRight="true"
|
||||||
android:layout_marginBottom="16dp"
|
android:layout_marginBottom="16dp"
|
||||||
android:layout_marginEnd="16dp"
|
android:layout_marginEnd="16dp"
|
||||||
android:clickable="true"
|
|
||||||
android:layout_alignParentRight="true"
|
|
||||||
android:layout_marginRight="16dp"
|
android:layout_marginRight="16dp"
|
||||||
android:focusable="true" />
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
app:backgroundTint="?attr/colorPrimary"
|
||||||
|
app:fabSize="auto"
|
||||||
|
app:srcCompat="?attr/ic_add"/>
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
62
app/src/main/res/layout/list_choose_tabs.xml
Normal file
62
app/src/main/res/layout/list_choose_tabs.xml
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<android.support.v7.widget.CardView
|
||||||
|
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/layoutCard"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="3dp"
|
||||||
|
android:layout_marginLeft="5dp"
|
||||||
|
android:layout_marginRight="5dp"
|
||||||
|
android:layout_marginTop="3dp"
|
||||||
|
android:minHeight="?listPreferredItemHeightSmall"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
app:cardCornerRadius="5dp"
|
||||||
|
app:cardElevation="4dp">
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_gravity="center_vertical">
|
||||||
|
|
||||||
|
<android.support.v7.widget.AppCompatImageView
|
||||||
|
android:id="@+id/tabIcon"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_alignParentLeft="true"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:layout_marginLeft="16dp"
|
||||||
|
tools:ignore="ContentDescription,RtlHardcoded"
|
||||||
|
tools:src="?attr/ic_hot"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tabName"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:layout_marginBottom="6dp"
|
||||||
|
android:layout_marginLeft="16dp"
|
||||||
|
android:layout_marginTop="6dp"
|
||||||
|
android:layout_toLeftOf="@+id/handle"
|
||||||
|
android:layout_toRightOf="@+id/tabIcon"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="2"
|
||||||
|
android:textAppearance="?textAppearanceListItem"
|
||||||
|
tools:ignore="RtlHardcoded"
|
||||||
|
tools:text="Lorem ipsum dolor sit amet"/>
|
||||||
|
|
||||||
|
<android.support.v7.widget.AppCompatImageView
|
||||||
|
android:id="@+id/handle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentRight="true"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:paddingBottom="12dp"
|
||||||
|
android:paddingLeft="16dp"
|
||||||
|
android:paddingRight="16dp"
|
||||||
|
android:paddingTop="12dp"
|
||||||
|
android:src="?attr/drag_handle"
|
||||||
|
tools:ignore="ContentDescription,RtlHardcoded"/>
|
||||||
|
</RelativeLayout>
|
||||||
|
</android.support.v7.widget.CardView>
|
33
app/src/main/res/layout/list_choose_tabs_dialog.xml
Normal file
33
app/src/main/res/layout/list_choose_tabs_dialog.xml
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RelativeLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:minHeight="?listPreferredItemHeightSmall"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<android.support.v7.widget.AppCompatImageView
|
||||||
|
android:id="@+id/tabIcon"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_alignParentLeft="true"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:layout_marginLeft="?dialogPreferredPadding"
|
||||||
|
tools:ignore="ContentDescription,RtlHardcoded"
|
||||||
|
tools:src="?attr/ic_hot"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tabName"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:layout_marginLeft="?dialogPreferredPadding"
|
||||||
|
android:layout_marginRight="?dialogPreferredPadding"
|
||||||
|
android:layout_toRightOf="@+id/tabIcon"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="2"
|
||||||
|
android:textAppearance="?textAppearanceListItem"
|
||||||
|
tools:ignore="RtlHardcoded"
|
||||||
|
tools:text="Lorem ipsum dolor sit amet"/>
|
||||||
|
</RelativeLayout>
|
@ -1,40 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:id="@+id/layoutCard"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginBottom="3dp"
|
|
||||||
android:layout_marginLeft="5dp"
|
|
||||||
android:layout_marginRight="5dp"
|
|
||||||
android:layout_marginTop="3dp"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:paddingBottom="4dp"
|
|
||||||
android:paddingTop="4dp"
|
|
||||||
app:cardCornerRadius="5dp"
|
|
||||||
app:cardElevation="4dp">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/handle"
|
|
||||||
android:layout_width="30dp"
|
|
||||||
android:layout_height="30dp"
|
|
||||||
android:layout_gravity="center_vertical|end"
|
|
||||||
android:layout_marginRight="10dp" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tabName"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_alignParentEnd="true"
|
|
||||||
android:layout_alignParentLeft="true"
|
|
||||||
android:layout_alignParentStart="true"
|
|
||||||
android:layout_centerVertical="true"
|
|
||||||
android:paddingBottom="9dp"
|
|
||||||
android:paddingLeft="15dp"
|
|
||||||
android:paddingRight="15dp"
|
|
||||||
android:paddingStart="3dp"
|
|
||||||
android:paddingTop="9dp"
|
|
||||||
android:textAppearance="?android:attr/textAppearanceListItem"
|
|
||||||
android:textSize="16sp" />
|
|
||||||
|
|
||||||
</android.support.v7.widget.CardView>
|
|
@ -36,6 +36,8 @@
|
|||||||
<attr name="ic_save" format="reference"/>
|
<attr name="ic_save" format="reference"/>
|
||||||
<attr name="ic_backup" format="reference"/>
|
<attr name="ic_backup" format="reference"/>
|
||||||
<attr name="ic_add" format="reference"/>
|
<attr name="ic_add" format="reference"/>
|
||||||
|
<attr name="ic_restore_defaults" format="reference"/>
|
||||||
|
<attr name="ic_blank_page" format="reference"/>
|
||||||
|
|
||||||
<!-- Can't refer to colors directly in drawable's xml-->
|
<!-- Can't refer to colors directly in drawable's xml-->
|
||||||
<attr name="toolbar_shadow_drawable" format="reference"/>
|
<attr name="toolbar_shadow_drawable" format="reference"/>
|
||||||
|
@ -8,6 +8,8 @@
|
|||||||
<string name="current_service_key" translatable="false">service</string>
|
<string name="current_service_key" translatable="false">service</string>
|
||||||
<string name="default_service_value" translatable="false">@string/youtube</string>
|
<string name="default_service_value" translatable="false">@string/youtube</string>
|
||||||
|
|
||||||
|
<string name="saved_tabs_key" translatable="false">saved_tabs_key</string>
|
||||||
|
|
||||||
<!-- Key values -->
|
<!-- Key values -->
|
||||||
<string name="download_path_key" translatable="false">download_path</string>
|
<string name="download_path_key" translatable="false">download_path</string>
|
||||||
<string name="download_path_audio_key" translatable="false">download_path_audio</string>
|
<string name="download_path_audio_key" translatable="false">download_path_audio</string>
|
||||||
@ -144,22 +146,7 @@
|
|||||||
<string name="enable_search_history_key" translatable="false">enable_search_history</string>
|
<string name="enable_search_history_key" translatable="false">enable_search_history</string>
|
||||||
<string name="enable_watch_history_key" translatable="false">enable_watch_history</string>
|
<string name="enable_watch_history_key" translatable="false">enable_watch_history</string>
|
||||||
<string name="main_page_content_key" translatable="false">main_page_content</string>
|
<string name="main_page_content_key" translatable="false">main_page_content</string>
|
||||||
<string name="blank_page_key" translatable="false">blank_page</string>
|
|
||||||
<string name="feed_page_key" translatable="false">feed_page</string>
|
|
||||||
<string name="subscription_page_key" translatable="false">subscription_page_key</string>
|
|
||||||
<string name="kiosk_page_key" translatable="false">kiosk_page</string>
|
|
||||||
<string name="channel_page_key" translatable="false">channel_page</string>
|
|
||||||
<string-array name="main_page_content_pages" translatable="false">
|
|
||||||
<item>@string/blank_page_key</item>
|
|
||||||
<item>@string/kiosk_page_key</item>
|
|
||||||
<item>@string/feed_page_key</item>
|
|
||||||
<item>@string/subscription_page_key</item>
|
|
||||||
<item>@string/channel_page_key</item>
|
|
||||||
</string-array>
|
|
||||||
<string name="main_page_selected_service" translatable="false">main_page_selected_service</string>
|
|
||||||
<string name="main_page_selected_channel_name" translatable="false">main_page_selected_channel_name</string>
|
|
||||||
<string name="main_page_selected_channel_url" translatable="false">main_page_selected_channel_url</string>
|
|
||||||
<string name="main_page_selectd_kiosk_id" translatable="false">main_page_selectd_kiosk_id</string>
|
|
||||||
<string name="import_data">import_data</string>
|
<string name="import_data">import_data</string>
|
||||||
<string name="export_data">export_data</string>
|
<string name="export_data">export_data</string>
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@
|
|||||||
<string name="tab_subscriptions">Subscriptions</string>
|
<string name="tab_subscriptions">Subscriptions</string>
|
||||||
<string name="tab_bookmarks">Bookmarks</string>
|
<string name="tab_bookmarks">Bookmarks</string>
|
||||||
<string name="tab_new">New Tab</string>
|
<string name="tab_new">New Tab</string>
|
||||||
<string name="tab_chose">Chose Tab</string>
|
<string name="tab_choose">Choose Tab</string>
|
||||||
|
|
||||||
<string name="fragment_whats_new">What\'s New</string>
|
<string name="fragment_whats_new">What\'s New</string>
|
||||||
|
|
||||||
@ -199,6 +199,9 @@
|
|||||||
<string name="file_name_empty_error">File name cannot be empty</string>
|
<string name="file_name_empty_error">File name cannot be empty</string>
|
||||||
<string name="error_occurred_detail">An error occurred: %1$s</string>
|
<string name="error_occurred_detail">An error occurred: %1$s</string>
|
||||||
<string name="no_streams_available_download">No streams available to download</string>
|
<string name="no_streams_available_download">No streams available to download</string>
|
||||||
|
<string name="saved_tabs_invalid_json">Using default tabs, error while reading saved tabs</string>
|
||||||
|
<string name="restore_defaults">Restore defaults</string>
|
||||||
|
<string name="restore_defaults_confirmation">Do you want to restore the defaults?</string>
|
||||||
|
|
||||||
<!-- error activity -->
|
<!-- error activity -->
|
||||||
<string name="sorry_string">Sorry, that should not have happened.</string>
|
<string name="sorry_string">Sorry, that should not have happened.</string>
|
||||||
@ -248,6 +251,7 @@
|
|||||||
<item quantity="one">%s subscriber</item>
|
<item quantity="one">%s subscriber</item>
|
||||||
<item quantity="other">%s subscribers</item>
|
<item quantity="other">%s subscribers</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
|
<string name="subscribers_count_not_available">Subscribers count not available</string>
|
||||||
|
|
||||||
<string name="no_views">No views</string>
|
<string name="no_views">No views</string>
|
||||||
<plurals name="views">
|
<plurals name="views">
|
||||||
@ -365,19 +369,11 @@
|
|||||||
<string name="main_page_content">Content of main page</string>
|
<string name="main_page_content">Content of main page</string>
|
||||||
<string name="main_page_content_summary">What tabs are shown on the main page</string>
|
<string name="main_page_content_summary">What tabs are shown on the main page</string>
|
||||||
<string name="selection">Selection</string>
|
<string name="selection">Selection</string>
|
||||||
<string name="chosenTabs">Your tabs</string>
|
|
||||||
<string name="blank_page_summary">Blank Page</string>
|
<string name="blank_page_summary">Blank Page</string>
|
||||||
<string name="kiosk_page_summary">Kiosk Page</string>
|
<string name="kiosk_page_summary">Kiosk Page</string>
|
||||||
<string name="subscription_page_summary">Subscription Page</string>
|
<string name="subscription_page_summary">Subscription Page</string>
|
||||||
<string name="feed_page_summary">Feed Page</string>
|
<string name="feed_page_summary">Feed Page</string>
|
||||||
<string name="channel_page_summary">Channel Page</string>
|
<string name="channel_page_summary">Channel Page</string>
|
||||||
<string-array name="main_page_content_names">
|
|
||||||
<item>@string/blank_page_summary</item>
|
|
||||||
<item>@string/kiosk_page_summary</item>
|
|
||||||
<item>@string/feed_page_summary</item>
|
|
||||||
<item>@string/subscription_page_summary</item>
|
|
||||||
<item>@string/channel_page_summary</item>
|
|
||||||
</string-array>
|
|
||||||
<string name="select_a_channel">Select a channel</string>
|
<string name="select_a_channel">Select a channel</string>
|
||||||
<string name="no_channel_subscribed_yet">No channel subscribed yet</string>
|
<string name="no_channel_subscribed_yet">No channel subscribed yet</string>
|
||||||
<string name="select_a_kiosk">Select a kiosk</string>
|
<string name="select_a_kiosk">Select a kiosk</string>
|
||||||
|
@ -52,6 +52,8 @@
|
|||||||
<item name="ic_save">@drawable/ic_save_black_24dp</item>
|
<item name="ic_save">@drawable/ic_save_black_24dp</item>
|
||||||
<item name="ic_backup">@drawable/ic_backup_black_24dp</item>
|
<item name="ic_backup">@drawable/ic_backup_black_24dp</item>
|
||||||
<item name="ic_add">@drawable/ic_add_black_24dp</item>
|
<item name="ic_add">@drawable/ic_add_black_24dp</item>
|
||||||
|
<item name="ic_restore_defaults">@drawable/ic_settings_backup_restore_black_24dp</item>
|
||||||
|
<item name="ic_blank_page">@drawable/ic_blank_page_black_24dp</item>
|
||||||
|
|
||||||
<item name="separator_color">@color/light_separator_color</item>
|
<item name="separator_color">@color/light_separator_color</item>
|
||||||
<item name="contrast_background_color">@color/light_contrast_background_color</item>
|
<item name="contrast_background_color">@color/light_contrast_background_color</item>
|
||||||
@ -110,6 +112,8 @@
|
|||||||
<item name="ic_save">@drawable/ic_save_white_24dp</item>
|
<item name="ic_save">@drawable/ic_save_white_24dp</item>
|
||||||
<item name="ic_backup">@drawable/ic_backup_white_24dp</item>
|
<item name="ic_backup">@drawable/ic_backup_white_24dp</item>
|
||||||
<item name="ic_add">@drawable/ic_add_white_24dp</item>
|
<item name="ic_add">@drawable/ic_add_white_24dp</item>
|
||||||
|
<item name="ic_restore_defaults">@drawable/ic_settings_backup_restore_white_24dp</item>
|
||||||
|
<item name="ic_blank_page">@drawable/ic_blank_page_white_24dp</item>
|
||||||
|
|
||||||
<item name="separator_color">@color/dark_separator_color</item>
|
<item name="separator_color">@color/dark_separator_color</item>
|
||||||
<item name="contrast_background_color">@color/dark_contrast_background_color</item>
|
<item name="contrast_background_color">@color/dark_contrast_background_color</item>
|
||||||
|
@ -28,7 +28,7 @@
|
|||||||
android:summary="@string/caption_setting_description"/>
|
android:summary="@string/caption_setting_description"/>
|
||||||
|
|
||||||
<PreferenceScreen
|
<PreferenceScreen
|
||||||
android:fragment="org.schabi.newpipe.settings.ChoseTabsFragment"
|
android:fragment="org.schabi.newpipe.settings.tabs.ChooseTabsFragment"
|
||||||
android:summary="@string/main_page_content_summary"
|
android:summary="@string/main_page_content_summary"
|
||||||
android:key="@string/main_page_content_key"
|
android:key="@string/main_page_content_key"
|
||||||
android:title="@string/main_page_content"/>
|
android:title="@string/main_page_content"/>
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
package org.schabi.newpipe.settings.tabs;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
public class TabTest {
|
||||||
|
@Test
|
||||||
|
public void checkIdDuplication() {
|
||||||
|
final Set<Integer> usedIds = new HashSet<>();
|
||||||
|
|
||||||
|
for (Tab.Type type : Tab.Type.values()) {
|
||||||
|
final boolean added = usedIds.add(type.getTabId());
|
||||||
|
assertTrue("Id was already used: " + type.getTabId(), added);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,120 @@
|
|||||||
|
package org.schabi.newpipe.settings.tabs;
|
||||||
|
|
||||||
|
import com.grack.nanojson.JsonArray;
|
||||||
|
import com.grack.nanojson.JsonObject;
|
||||||
|
import com.grack.nanojson.JsonParser;
|
||||||
|
import com.grack.nanojson.JsonParserException;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static java.util.Objects.requireNonNull;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
public class TabsJsonHelperTest {
|
||||||
|
private static final String JSON_TABS_ARRAY_KEY = "tabs";
|
||||||
|
private static final String JSON_TAB_ID_KEY = "tab_id";
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEmptyAndNullRead() throws TabsJsonHelper.InvalidJsonException {
|
||||||
|
final String emptyTabsJson = "{\"" + JSON_TABS_ARRAY_KEY + "\":[]}";
|
||||||
|
List<Tab> items = TabsJsonHelper.getTabsFromJson(emptyTabsJson);
|
||||||
|
// Check if instance is the same
|
||||||
|
assertTrue(items == TabsJsonHelper.FALLBACK_INITIAL_TABS_LIST);
|
||||||
|
|
||||||
|
final String nullSource = null;
|
||||||
|
items = TabsJsonHelper.getTabsFromJson(nullSource);
|
||||||
|
assertTrue(items == TabsJsonHelper.FALLBACK_INITIAL_TABS_LIST);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInvalidIdRead() throws TabsJsonHelper.InvalidJsonException {
|
||||||
|
final int blankTabId = Tab.Type.BLANK.getTabId();
|
||||||
|
final String emptyTabsJson = "{\"" + JSON_TABS_ARRAY_KEY + "\":[" +
|
||||||
|
"{\"" + JSON_TAB_ID_KEY + "\":" + blankTabId + "}," +
|
||||||
|
"{\"" + JSON_TAB_ID_KEY + "\":" + 12345678 + "}" +
|
||||||
|
"]}";
|
||||||
|
final List<Tab> items = TabsJsonHelper.getTabsFromJson(emptyTabsJson);
|
||||||
|
|
||||||
|
assertEquals("Should ignore the tab with invalid id", 1, items.size());
|
||||||
|
assertEquals(blankTabId, items.get(0).getTabId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInvalidRead() {
|
||||||
|
final List<String> invalidList = Arrays.asList(
|
||||||
|
"{\"notTabsArray\":[]}",
|
||||||
|
"{invalidJSON]}",
|
||||||
|
"{}"
|
||||||
|
);
|
||||||
|
|
||||||
|
for (String invalidContent : invalidList) {
|
||||||
|
try {
|
||||||
|
TabsJsonHelper.getTabsFromJson(invalidContent);
|
||||||
|
|
||||||
|
fail("didn't throw exception");
|
||||||
|
} catch (Exception e) {
|
||||||
|
boolean isExpectedException = e instanceof TabsJsonHelper.InvalidJsonException;
|
||||||
|
assertTrue("\"" + e.getClass().getSimpleName() + "\" is not the expected exception", isExpectedException);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEmptyAndNullSave() throws JsonParserException {
|
||||||
|
final List<Tab> emptyList = Collections.emptyList();
|
||||||
|
String returnedJson = TabsJsonHelper.getJsonToSave(emptyList);
|
||||||
|
assertTrue(isTabsArrayEmpty(returnedJson));
|
||||||
|
|
||||||
|
final List<Tab> nullList = null;
|
||||||
|
returnedJson = TabsJsonHelper.getJsonToSave(nullList);
|
||||||
|
assertTrue(isTabsArrayEmpty(returnedJson));
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isTabsArrayEmpty(String returnedJson) throws JsonParserException {
|
||||||
|
JsonObject jsonObject = JsonParser.object().from(returnedJson);
|
||||||
|
assertTrue(jsonObject.containsKey(JSON_TABS_ARRAY_KEY));
|
||||||
|
return jsonObject.getArray(JSON_TABS_ARRAY_KEY).size() == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSaveAndReading() throws JsonParserException {
|
||||||
|
// Saving
|
||||||
|
final Tab.BlankTab blankTab = new Tab.BlankTab();
|
||||||
|
final Tab.SubscriptionsTab subscriptionsTab = new Tab.SubscriptionsTab();
|
||||||
|
final Tab.ChannelTab channelTab = new Tab.ChannelTab(666, "https://example.org", "testName");
|
||||||
|
final Tab.KioskTab kioskTab = new Tab.KioskTab(123, "trending_key");
|
||||||
|
|
||||||
|
final List<Tab> tabs = Arrays.asList(blankTab, subscriptionsTab, channelTab, kioskTab);
|
||||||
|
String returnedJson = TabsJsonHelper.getJsonToSave(tabs);
|
||||||
|
|
||||||
|
// Reading
|
||||||
|
final JsonObject jsonObject = JsonParser.object().from(returnedJson);
|
||||||
|
assertTrue(jsonObject.containsKey(JSON_TABS_ARRAY_KEY));
|
||||||
|
final JsonArray tabsFromArray = jsonObject.getArray(JSON_TABS_ARRAY_KEY);
|
||||||
|
|
||||||
|
assertEquals(tabs.size(), tabsFromArray.size());
|
||||||
|
|
||||||
|
final Tab.BlankTab blankTabFromReturnedJson = requireNonNull((Tab.BlankTab) Tab.from(((JsonObject) tabsFromArray.get(0))));
|
||||||
|
assertEquals(blankTab.getTabId(), blankTabFromReturnedJson.getTabId());
|
||||||
|
|
||||||
|
final Tab.SubscriptionsTab subscriptionsTabFromReturnedJson = requireNonNull((Tab.SubscriptionsTab) Tab.from(((JsonObject) tabsFromArray.get(1))));
|
||||||
|
assertEquals(subscriptionsTab.getTabId(), subscriptionsTabFromReturnedJson.getTabId());
|
||||||
|
|
||||||
|
final Tab.ChannelTab channelTabFromReturnedJson = requireNonNull((Tab.ChannelTab) Tab.from(((JsonObject) tabsFromArray.get(2))));
|
||||||
|
assertEquals(channelTab.getTabId(), channelTabFromReturnedJson.getTabId());
|
||||||
|
assertEquals(channelTab.getChannelServiceId(), channelTabFromReturnedJson.getChannelServiceId());
|
||||||
|
assertEquals(channelTab.getChannelUrl(), channelTabFromReturnedJson.getChannelUrl());
|
||||||
|
assertEquals(channelTab.getChannelName(), channelTabFromReturnedJson.getChannelName());
|
||||||
|
|
||||||
|
final Tab.KioskTab kioskTabFromReturnedJson = requireNonNull((Tab.KioskTab) Tab.from(((JsonObject) tabsFromArray.get(3))));
|
||||||
|
assertEquals(kioskTab.getTabId(), kioskTabFromReturnedJson.getTabId());
|
||||||
|
assertEquals(kioskTab.getKioskServiceId(), kioskTabFromReturnedJson.getKioskServiceId());
|
||||||
|
assertEquals(kioskTab.getKioskId(), kioskTabFromReturnedJson.getKioskId());
|
||||||
|
}
|
||||||
|
}
|
31
fastlane/metadata/android/en-US/changelogs/68.txt
Normal file
31
fastlane/metadata/android/en-US/changelogs/68.txt
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
# changes of v0.14.1
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fixed failed to decrypt video url #1659
|
||||||
|
- Fixed description link not extract well #1657
|
||||||
|
|
||||||
|
# changes of v0.14.0
|
||||||
|
|
||||||
|
### New
|
||||||
|
- New Drawer design #1461
|
||||||
|
- New customizable front page #1461
|
||||||
|
|
||||||
|
### Improvements
|
||||||
|
- Reworked Gesture controls #1604
|
||||||
|
- New way to close the popup player #1597
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fix error when subscription count is not available. Closes #1649.
|
||||||
|
- Show "Subscriber count not available" in those cases
|
||||||
|
- Fix NPE when a YouTube playlist is empty
|
||||||
|
- Quick fix for the kiosks in SoundCloud
|
||||||
|
- Refactor and bugfix #1623
|
||||||
|
- Fix Cyclic search result #1562
|
||||||
|
- Fix Seek bar not statically lay outed
|
||||||
|
- Fix YT Premium video are not blocked correctly
|
||||||
|
- Fix Videos sometimes not loading (due to DASH parsing)
|
||||||
|
- Fix links in video description
|
||||||
|
- Show warning when someone tries to download to external sdcard
|
||||||
|
- fix nothing shown exception triggers report
|
||||||
|
- thumbnail not shown in background player for android 8.1 [see here](https://github.com/TeamNewPipe/NewPipe/issues/943)
|
||||||
|
- Fix registering of broadcast receiver. Closes #1641.
|
Loading…
Reference in New Issue
Block a user