diff --git a/README.md b/README.md index b154fad58..715e25b1e 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,22 @@ -

+

NewPipe

A free lightweight YouTube frontend for Android.

-

+

- - - - - - + + + + + +

-
+

ScreenshotsDescriptionFeaturesContributionDonateLicense

WebsiteBlogPress

-
-WARNING: PUTTING NEWPIPE OR ANY FORK OF IT INTO GOOGLE PLAYSTORE VIOLATES THEIR TERMS OF CONDITIONS. +
+ +WARNING: PUTTING NEWPIPE OR ANY FORK OF IT INTO GOOGLE PLAYSTORE VIOLATES THEIR TERMS OF CONDITIONS. ## Screenshots @@ -29,6 +30,8 @@ WARNING: PUTTING NEWPIPE OR ANY FORK OF IT INTO GOOGLE PLAYSTORE VIOLATES THEIR [](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_8.png) [](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_9.png) [](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_10.png) +[](fastlane/metadata/android/en-US/images/tenInchScreenshots/shot_11.png) +[](fastlane/metadata/android/en-US/images/tenInchScreenshots/shot_12.png) ## Description @@ -61,11 +64,11 @@ NewPipe does not use any Google framework libraries, or the YouTube API. It only * Queuing videos * Local playlists * Subtitles -* Multi-service support (eg. SoundCloud in NewPipe Beta) +* Multi-service support (eg. SoundCloud \[beta\]) +* Livestream support ### Coming Features -* Livestream support * Cast to UPnP and Cast * Show comments * ... and many more @@ -81,22 +84,27 @@ If you like NewPipe we'd be happy about a donation. You can either donate via Bi - - + + - - - + + + - - - + + +
BitcoinBitcoin QR CodeBitcoinBitcoin QR Code 16A9J59ahMRqkLSZjhYj33n9j3fMztFxnh
LiberapayVisit NewPipe at liberapay.comDonate via LiberapayLiberapayVisit NewPipe at liberapay.comDonate via Liberapay
BountysourceVisit NewPipe at bountysource.comCheck out how many bounties you can earn.BountysourceVisit NewPipe at bountysource.comCheck out how many bounties you can earn.
+## Privacy Policy + +The NewPipe project aims to provide a private, anonymous experience for using media web services. +Therefore, the app does not collect any data without your consent. NewPipe's privacy policy explains in detail what data is sent and stored when you send a crash report or leave a comment at our blog. You can find the document [here](https://newpipe.schabi.org/legal/privacy/). + ## License [![GNU GPLv3 Image](https://www.gnu.org/graphics/gplv3-127x51.png)](http://www.gnu.org/licenses/gpl-3.0.en.html) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index 2ca4fd78d..9ab40e81c 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -33,12 +33,14 @@ import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; +import android.view.ViewParent; import android.widget.AdapterView; import android.widget.FrameLayout; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.RelativeLayout; +import android.widget.ScrollView; import android.widget.Spinner; import android.widget.TextView; import android.widget.Toast; @@ -64,19 +66,17 @@ import org.schabi.newpipe.extractor.stream.StreamType; import org.schabi.newpipe.extractor.stream.VideoStream; import org.schabi.newpipe.fragments.BackPressable; import org.schabi.newpipe.fragments.BaseStateFragment; -import org.schabi.newpipe.local.history.HistoryRecordManager; -import org.schabi.newpipe.report.ErrorActivity; -import org.schabi.newpipe.util.StreamItemAdapter; -import org.schabi.newpipe.util.StreamItemAdapter.StreamSizeWrapper; -import org.schabi.newpipe.local.dialog.PlaylistAppendDialog; import org.schabi.newpipe.info_list.InfoItemBuilder; import org.schabi.newpipe.info_list.InfoItemDialog; +import org.schabi.newpipe.local.dialog.PlaylistAppendDialog; +import org.schabi.newpipe.local.history.HistoryRecordManager; import org.schabi.newpipe.player.MainVideoPlayer; import org.schabi.newpipe.player.PopupVideoPlayer; import org.schabi.newpipe.player.helper.PlayerHelper; import org.schabi.newpipe.player.old.PlayVideoActivity; import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.SinglePlayQueue; +import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.util.Constants; import org.schabi.newpipe.util.ExtractorHelper; @@ -87,6 +87,8 @@ import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.OnClickGesture; import org.schabi.newpipe.util.PermissionHelper; +import org.schabi.newpipe.util.StreamItemAdapter; +import org.schabi.newpipe.util.StreamItemAdapter.StreamSizeWrapper; import org.schabi.newpipe.util.ThemeHelper; import java.io.Serializable; @@ -154,6 +156,7 @@ public class VideoDetailFragment private View videoTitleRoot; private TextView videoTitleTextView; + @Nullable private ImageView videoTitleToggleArrow; private TextView videoCountView; @@ -415,14 +418,16 @@ public class VideoDetailFragment } private void toggleTitleAndDescription() { - if (videoDescriptionRootLayout.getVisibility() == View.VISIBLE) { - videoTitleTextView.setMaxLines(1); - videoDescriptionRootLayout.setVisibility(View.GONE); - videoTitleToggleArrow.setImageResource(R.drawable.arrow_down); - } else { - videoTitleTextView.setMaxLines(10); - videoDescriptionRootLayout.setVisibility(View.VISIBLE); - videoTitleToggleArrow.setImageResource(R.drawable.arrow_up); + if (videoTitleToggleArrow != null) { //it is null for tablets + if (videoDescriptionRootLayout.getVisibility() == View.VISIBLE) { + videoTitleTextView.setMaxLines(1); + videoDescriptionRootLayout.setVisibility(View.GONE); + videoTitleToggleArrow.setImageResource(R.drawable.arrow_down); + } else { + videoTitleTextView.setMaxLines(10); + videoDescriptionRootLayout.setVisibility(View.VISIBLE); + videoTitleToggleArrow.setImageResource(R.drawable.arrow_up); + } } } @@ -622,8 +627,11 @@ public class VideoDetailFragment relatedStreamsView.addView( infoItemBuilder.buildView(relatedStreamsView, info.getNextVideo())); relatedStreamsView.addView(getSeparatorView()); - relatedStreamRootLayout.setVisibility(View.VISIBLE); - } else nextStreamTitle.setVisibility(View.GONE); + setRelatedStreamsVisibility(View.VISIBLE); + } else { + nextStreamTitle.setVisibility(View.GONE); + setRelatedStreamsVisibility(View.GONE); + } if (info.getRelatedStreams() != null && !info.getRelatedStreams().isEmpty() && showRelatedStreams) { @@ -639,13 +647,13 @@ public class VideoDetailFragment } //if (DEBUG) Log.d(TAG, "Total time " + ((System.nanoTime() - first) / 1000000L) + "ms"); - relatedStreamRootLayout.setVisibility(View.VISIBLE); + setRelatedStreamsVisibility(View.VISIBLE); relatedStreamExpandButton.setVisibility(View.VISIBLE); relatedStreamExpandButton.setImageDrawable(ContextCompat.getDrawable( activity, ThemeHelper.resolveResourceIdFromAttr(activity, R.attr.expand))); } else { - if (info.getNextVideo() == null) relatedStreamRootLayout.setVisibility(View.GONE); + if (info.getNextVideo() == null) setRelatedStreamsVisibility(View.GONE); relatedStreamExpandButton.setVisibility(View.GONE); } } @@ -1114,8 +1122,16 @@ public class VideoDetailFragment animateView(videoTitleTextView, true, 0); videoDescriptionRootLayout.setVisibility(View.GONE); - videoTitleToggleArrow.setImageResource(R.drawable.arrow_down); - videoTitleToggleArrow.setVisibility(View.GONE); + if (videoTitleToggleArrow != null) { //phone + videoTitleToggleArrow.setImageResource(R.drawable.arrow_down); + videoTitleToggleArrow.setVisibility(View.GONE); + } else { //tablet + final View related = (View) relatedStreamRootLayout.getParent(); + //don`t need to hide it if related streams are disabled + if (related.getVisibility() == View.VISIBLE) { + related.setVisibility(View.INVISIBLE); + } + } videoTitleRoot.setClickable(false); imageLoader.cancelDisplayTask(thumbnailImageView); @@ -1190,11 +1206,15 @@ public class VideoDetailFragment detailDurationView.setVisibility(View.GONE); } - videoTitleRoot.setClickable(true); - videoTitleToggleArrow.setVisibility(View.VISIBLE); - videoTitleToggleArrow.setImageResource(R.drawable.arrow_down); videoDescriptionView.setVisibility(View.GONE); - videoDescriptionRootLayout.setVisibility(View.GONE); + if (videoTitleToggleArrow != null) { + videoTitleRoot.setClickable(true); + videoTitleToggleArrow.setVisibility(View.VISIBLE); + videoTitleToggleArrow.setImageResource(R.drawable.arrow_down); + videoDescriptionRootLayout.setVisibility(View.GONE); + } else { + videoDescriptionRootLayout.setVisibility(View.VISIBLE); + } if (!TextUtils.isEmpty(info.getUploadDate())) { videoUploadDateView.setText(Localization.localizeDate(activity, info.getUploadDate())); } @@ -1242,6 +1262,11 @@ public class VideoDetailFragment // Only auto play in the first open autoPlayEnabled = false; } + + final ViewParent related = relatedStreamRootLayout.getParent(); + if (related instanceof ScrollView) { + ((ScrollView) related).scrollTo(0, 0); + } } @@ -1299,4 +1324,13 @@ public class VideoDetailFragment showError(getString(R.string.blocked_by_gema), false, R.drawable.gruese_die_gema); } + + private void setRelatedStreamsVisibility(int visibility) { + final ViewParent parent = relatedStreamRootLayout.getParent(); + if (parent instanceof ScrollView) { + ((ScrollView) parent).setVisibility(visibility); + } else { + relatedStreamRootLayout.setVisibility(visibility); + } + } } \ No newline at end of file diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java index c70ea2b19..0816334ea 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java @@ -3,10 +3,15 @@ package org.schabi.newpipe.fragments.list; import android.app.Activity; import android.content.Context; import android.content.DialogInterface; +import android.content.SharedPreferences; +import android.content.res.Configuration; +import android.content.res.Resources; import android.os.Bundle; +import android.preference.PreferenceManager; import android.support.annotation.NonNull; import android.support.v7.app.ActionBar; import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.Log; @@ -21,9 +26,9 @@ import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem; import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.fragments.BaseStateFragment; import org.schabi.newpipe.fragments.OnScrollBelowItemsListener; -import org.schabi.newpipe.local.dialog.PlaylistAppendDialog; import org.schabi.newpipe.info_list.InfoItemDialog; import org.schabi.newpipe.info_list.InfoListAdapter; +import org.schabi.newpipe.local.dialog.PlaylistAppendDialog; import org.schabi.newpipe.player.playqueue.SinglePlayQueue; import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.util.NavigationHelper; @@ -36,7 +41,7 @@ import java.util.Queue; import static org.schabi.newpipe.util.AnimationUtils.animateView; -public abstract class BaseListFragment extends BaseStateFragment implements ListViewContract, StateSaver.WriteRead { +public abstract class BaseListFragment extends BaseStateFragment implements ListViewContract, StateSaver.WriteRead, SharedPreferences.OnSharedPreferenceChangeListener { /*////////////////////////////////////////////////////////////////////////// // Views @@ -44,6 +49,9 @@ public abstract class BaseListFragment extends BaseStateFragment implem protected InfoListAdapter infoListAdapter; protected RecyclerView itemsList; + private int updateFlags = 0; + + private static final int LIST_MODE_UPDATE_FLAG = 0x32; /*////////////////////////////////////////////////////////////////////////// // LifeCycle @@ -59,12 +67,31 @@ public abstract class BaseListFragment extends BaseStateFragment implem public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setHasOptionsMenu(true); + PreferenceManager.getDefaultSharedPreferences(activity) + .registerOnSharedPreferenceChangeListener(this); } @Override public void onDestroy() { super.onDestroy(); StateSaver.onDestroy(savedState); + PreferenceManager.getDefaultSharedPreferences(activity) + .unregisterOnSharedPreferenceChangeListener(this); + } + + @Override + public void onResume() { + super.onResume(); + + if (updateFlags != 0) { + if ((updateFlags & LIST_MODE_UPDATE_FLAG) != 0) { + final boolean useGrid = isGridLayout(); + itemsList.setLayoutManager(useGrid ? getGridLayoutManager() : getListLayoutManager()); + infoListAdapter.setGridItemVariants(useGrid); + infoListAdapter.notifyDataSetChanged(); + } + updateFlags = 0; + } } /*////////////////////////////////////////////////////////////////////////// @@ -119,13 +146,25 @@ public abstract class BaseListFragment extends BaseStateFragment implem return new LinearLayoutManager(activity); } + protected RecyclerView.LayoutManager getGridLayoutManager() { + final Resources resources = activity.getResources(); + int width = resources.getDimensionPixelSize(R.dimen.video_item_grid_thumbnail_image_width); + width += (24 * resources.getDisplayMetrics().density); + final int spanCount = (int) Math.floor(resources.getDisplayMetrics().widthPixels / (double)width); + final GridLayoutManager lm = new GridLayoutManager(activity, spanCount); + lm.setSpanSizeLookup(infoListAdapter.getSpanSizeLookup(spanCount)); + return lm; + } + @Override protected void initViews(View rootView, Bundle savedInstanceState) { super.initViews(rootView, savedInstanceState); + final boolean useGrid = isGridLayout(); itemsList = rootView.findViewById(R.id.items_list); - itemsList.setLayoutManager(getListLayoutManager()); + itemsList.setLayoutManager(useGrid ? getGridLayoutManager() : getListLayoutManager()); + infoListAdapter.setGridItemVariants(useGrid); infoListAdapter.setFooter(getListFooter()); infoListAdapter.setHeader(getListHeader()); @@ -308,4 +347,22 @@ public abstract class BaseListFragment extends BaseStateFragment implem public void handleNextItems(N result) { isLoading.set(false); } + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + if (key.equals(getString(R.string.list_view_mode_key))) { + updateFlags |= LIST_MODE_UPDATE_FLAG; + } + } + + protected boolean isGridLayout() { + final String list_mode = PreferenceManager.getDefaultSharedPreferences(activity).getString(getString(R.string.list_view_mode_key), getString(R.string.list_view_mode_value)); + if ("auto".equals(list_mode)) { + final Configuration configuration = getResources().getConfiguration(); + return configuration.orientation == Configuration.ORIENTATION_LANDSCAPE + && configuration.isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_LARGE); + } else { + return "grid".equals(list_mode); + } + } } diff --git a/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java b/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java index cf12deb6f..15fdcad05 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java @@ -1,6 +1,7 @@ package org.schabi.newpipe.info_list; import android.app.Activity; +import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.Log; import android.view.View; @@ -12,9 +13,12 @@ import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem; import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.info_list.holder.ChannelInfoItemHolder; import org.schabi.newpipe.info_list.holder.ChannelMiniInfoItemHolder; +import org.schabi.newpipe.info_list.holder.ChannelGridInfoItemHolder; import org.schabi.newpipe.info_list.holder.InfoItemHolder; +import org.schabi.newpipe.info_list.holder.PlaylistGridInfoItemHolder; import org.schabi.newpipe.info_list.holder.PlaylistInfoItemHolder; import org.schabi.newpipe.info_list.holder.PlaylistMiniInfoItemHolder; +import org.schabi.newpipe.info_list.holder.StreamGridInfoItemHolder; import org.schabi.newpipe.info_list.holder.StreamInfoItemHolder; import org.schabi.newpipe.info_list.holder.StreamMiniInfoItemHolder; import org.schabi.newpipe.util.FallbackViewHolder; @@ -52,14 +56,18 @@ public class InfoListAdapter extends RecyclerView.Adapter infoItemList; private boolean useMiniVariant = false; + private boolean useGridVariant = false; private boolean showFooter = false; private View header = null; private View footer = null; @@ -94,6 +102,10 @@ public class InfoListAdapter extends RecyclerView.Adapter data) { if (data != null) { if (DEBUG) { @@ -206,11 +218,11 @@ public class InfoListAdapter extends RecyclerView.Adapter { + if (itemBuilder.getOnChannelSelectedListener() != null) { + itemBuilder.getOnChannelSelectedListener().held(item); + } + return true; + }); } protected String getDetailLine(final ChannelInfoItem item) { diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistGridInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistGridInfoItemHolder.java new file mode 100644 index 000000000..96b9c90a7 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistGridInfoItemHolder.java @@ -0,0 +1,13 @@ +package org.schabi.newpipe.info_list.holder; + +import android.view.ViewGroup; + +import org.schabi.newpipe.R; +import org.schabi.newpipe.info_list.InfoItemBuilder; + +public class PlaylistGridInfoItemHolder extends PlaylistMiniInfoItemHolder { + + public PlaylistGridInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) { + super(infoItemBuilder, R.layout.list_playlist_grid_item, parent); + } +} \ No newline at end of file diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamGridInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamGridInfoItemHolder.java new file mode 100644 index 000000000..a2e585857 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamGridInfoItemHolder.java @@ -0,0 +1,13 @@ +package org.schabi.newpipe.info_list.holder; + +import android.view.ViewGroup; + +import org.schabi.newpipe.R; +import org.schabi.newpipe.info_list.InfoItemBuilder; + +public class StreamGridInfoItemHolder extends StreamMiniInfoItemHolder { + + public StreamGridInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) { + super(infoItemBuilder, R.layout.list_stream_grid_item, parent); + } +} diff --git a/app/src/main/java/org/schabi/newpipe/local/BaseLocalListFragment.java b/app/src/main/java/org/schabi/newpipe/local/BaseLocalListFragment.java index 5192aa2ab..abdf82353 100644 --- a/app/src/main/java/org/schabi/newpipe/local/BaseLocalListFragment.java +++ b/app/src/main/java/org/schabi/newpipe/local/BaseLocalListFragment.java @@ -1,8 +1,13 @@ package org.schabi.newpipe.local; +import android.content.SharedPreferences; +import android.content.res.Configuration; +import android.content.res.Resources; import android.os.Bundle; +import android.preference.PreferenceManager; import android.support.v4.app.Fragment; import android.support.v7.app.ActionBar; +import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.Log; @@ -25,7 +30,7 @@ import static org.schabi.newpipe.util.AnimationUtils.animateView; * called and is memory efficient when in backstack. * */ public abstract class BaseLocalListFragment extends BaseStateFragment - implements ListViewContract { + implements ListViewContract, SharedPreferences.OnSharedPreferenceChangeListener { /*////////////////////////////////////////////////////////////////////////// // Views @@ -36,6 +41,9 @@ public abstract class BaseLocalListFragment extends BaseStateFragment protected LocalItemListAdapter itemListAdapter; protected RecyclerView itemsList; + private int updateFlags = 0; + + private static final int LIST_MODE_UPDATE_FLAG = 0x32; /*////////////////////////////////////////////////////////////////////////// // Lifecycle - Creation @@ -45,6 +53,29 @@ public abstract class BaseLocalListFragment extends BaseStateFragment public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setHasOptionsMenu(true); + PreferenceManager.getDefaultSharedPreferences(activity) + .registerOnSharedPreferenceChangeListener(this); + } + + @Override + public void onDestroy() { + super.onDestroy(); + PreferenceManager.getDefaultSharedPreferences(activity) + .unregisterOnSharedPreferenceChangeListener(this); + } + + @Override + public void onResume() { + super.onResume(); + if (updateFlags != 0) { + if ((updateFlags & LIST_MODE_UPDATE_FLAG) != 0) { + final boolean useGrid = isGridLayout(); + itemsList.setLayoutManager(useGrid ? getGridLayoutManager() : getListLayoutManager()); + itemListAdapter.setGridItemVariants(useGrid); + itemListAdapter.notifyDataSetChanged(); + } + updateFlags = 0; + } } /*////////////////////////////////////////////////////////////////////////// @@ -59,6 +90,16 @@ public abstract class BaseLocalListFragment extends BaseStateFragment return activity.getLayoutInflater().inflate(R.layout.pignate_footer, itemsList, false); } + protected RecyclerView.LayoutManager getGridLayoutManager() { + final Resources resources = activity.getResources(); + int width = resources.getDimensionPixelSize(R.dimen.video_item_grid_thumbnail_image_width); + width += (24 * resources.getDisplayMetrics().density); + final int spanCount = (int) Math.floor(resources.getDisplayMetrics().widthPixels / (double)width); + final GridLayoutManager lm = new GridLayoutManager(activity, spanCount); + lm.setSpanSizeLookup(itemListAdapter.getSpanSizeLookup(spanCount)); + return lm; + } + protected RecyclerView.LayoutManager getListLayoutManager() { return new LinearLayoutManager(activity); } @@ -67,10 +108,13 @@ public abstract class BaseLocalListFragment extends BaseStateFragment protected void initViews(View rootView, Bundle savedInstanceState) { super.initViews(rootView, savedInstanceState); - itemsList = rootView.findViewById(R.id.items_list); - itemsList.setLayoutManager(getListLayoutManager()); - itemListAdapter = new LocalItemListAdapter(activity); + + final boolean useGrid = isGridLayout(); + itemsList = rootView.findViewById(R.id.items_list); + itemsList.setLayoutManager(useGrid ? getGridLayoutManager() : getListLayoutManager()); + + itemListAdapter.setGridItemVariants(useGrid); itemListAdapter.setHeader(headerRootView = getListHeader()); itemListAdapter.setFooter(footerRootView = getListFooter()); @@ -174,4 +218,22 @@ public abstract class BaseLocalListFragment extends BaseStateFragment resetFragment(); return super.onError(exception); } + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + if (key.equals(getString(R.string.list_view_mode_key))) { + updateFlags |= LIST_MODE_UPDATE_FLAG; + } + } + + protected boolean isGridLayout() { + final String list_mode = PreferenceManager.getDefaultSharedPreferences(activity).getString(getString(R.string.list_view_mode_key), getString(R.string.list_view_mode_value)); + if ("auto".equals(list_mode)) { + final Configuration configuration = getResources().getConfiguration(); + return configuration.orientation == Configuration.ORIENTATION_LANDSCAPE + && configuration.isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_LARGE); + } else { + return "grid".equals(list_mode); + } + } } diff --git a/app/src/main/java/org/schabi/newpipe/local/LocalItemListAdapter.java b/app/src/main/java/org/schabi/newpipe/local/LocalItemListAdapter.java index 99937b58c..e298dedd3 100644 --- a/app/src/main/java/org/schabi/newpipe/local/LocalItemListAdapter.java +++ b/app/src/main/java/org/schabi/newpipe/local/LocalItemListAdapter.java @@ -1,18 +1,21 @@ package org.schabi.newpipe.local; import android.app.Activity; +import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.Log; import android.view.View; import android.view.ViewGroup; import org.schabi.newpipe.database.LocalItem; -import org.schabi.newpipe.local.HeaderFooterHolder; -import org.schabi.newpipe.local.LocalItemBuilder; import org.schabi.newpipe.local.holder.LocalItemHolder; +import org.schabi.newpipe.local.holder.LocalPlaylistGridItemHolder; import org.schabi.newpipe.local.holder.LocalPlaylistItemHolder; +import org.schabi.newpipe.local.holder.LocalPlaylistStreamGridItemHolder; import org.schabi.newpipe.local.holder.LocalPlaylistStreamItemHolder; +import org.schabi.newpipe.local.holder.LocalStatisticStreamGridItemHolder; import org.schabi.newpipe.local.holder.LocalStatisticStreamItemHolder; +import org.schabi.newpipe.local.holder.RemotePlaylistGridItemHolder; import org.schabi.newpipe.local.holder.RemotePlaylistItemHolder; import org.schabi.newpipe.util.FallbackViewHolder; import org.schabi.newpipe.util.Localization; @@ -52,14 +55,19 @@ public class LocalItemListAdapter extends RecyclerView.Adapter localItems; private final DateFormat dateFormat; private boolean showFooter = false; + private boolean useGridVariant = false; private View header = null; private View footer = null; @@ -134,6 +142,10 @@ public class LocalItemListAdapter extends RecyclerView.Adapter> { +public class SubscriptionFragment extends BaseStateFragment> implements SharedPreferences.OnSharedPreferenceChangeListener { private static final int REQUEST_EXPORT_CODE = 666; private static final int REQUEST_IMPORT_CODE = 667; @@ -78,6 +89,9 @@ public class SubscriptionFragment extends BaseStateFragment() { - @Override + public void selected(ChannelInfoItem selectedItem) { final FragmentManager fragmentManager = getFM(); NavigationHelper.openChannelFragment(fragmentManager, @@ -326,6 +369,11 @@ public class SubscriptionFragment extends BaseStateFragment importExportOptions.switchState()); } + private void showLongTapDialog(ChannelInfoItem selectedItem) { + final Context context = getContext(); + final Activity activity = getActivity(); + if (context == null || context.getResources() == null || getActivity() == null) return; + + final String[] commands = new String[]{ + context.getResources().getString(R.string.share), + context.getResources().getString(R.string.unsubscribe) + }; + + final DialogInterface.OnClickListener actions = (dialogInterface, i) -> { + switch (i) { + case 0: + shareChannel(selectedItem); + break; + case 1: + deleteChannel(selectedItem); + break; + default: + break; + } + }; + + final View bannerView = View.inflate(activity, R.layout.dialog_title, null); + bannerView.setSelected(true); + + TextView titleView = bannerView.findViewById(R.id.itemTitleView); + titleView.setText(selectedItem.getName()); + + TextView detailsView = bannerView.findViewById(R.id.itemAdditionalDetails); + detailsView.setVisibility(View.GONE); + + new AlertDialog.Builder(activity) + .setCustomTitle(bannerView) + .setItems(commands, actions) + .create() + .show(); + + } + + private void shareChannel (ChannelInfoItem selectedItem) { + shareUrl(selectedItem.getName(), selectedItem.getUrl()); + } + + @SuppressLint("CheckResult") + private void deleteChannel (ChannelInfoItem selectedItem) { + subscriptionService.subscriptionTable() + .getSubscription(selectedItem.getServiceId(), selectedItem.getUrl()) + .toObservable() + .observeOn(Schedulers.io()) + .subscribe(getDeleteObserver()); + + Toast.makeText(activity, getString(R.string.channel_unsubscribed), Toast.LENGTH_SHORT).show(); + } + + + + private Observer> getDeleteObserver(){ + return new Observer>() { + @Override + public void onSubscribe(Disposable d) { + disposables.add(d); + } + + @Override + public void onNext(List subscriptionEntities) { + subscriptionService.subscriptionTable().delete(subscriptionEntities); + } + + @Override + public void onError(Throwable exception) { + SubscriptionFragment.this.onError(exception); + } + + @Override + public void onComplete() { } + }; + } + private void resetFragment() { if (disposables != null) disposables.clear(); if (infoListAdapter != null) infoListAdapter.clearStreamItemList(); @@ -445,4 +572,22 @@ public class SubscriptionFragment extends BaseStateFragment + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout-large-land/fragment_video_detail.xml b/app/src/main/res/layout-large-land/fragment_video_detail.xml new file mode 100644 index 000000000..73939d60a --- /dev/null +++ b/app/src/main/res/layout-large-land/fragment_video_detail.xml @@ -0,0 +1,491 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_subscription.xml b/app/src/main/res/layout/fragment_subscription.xml index a40059455..f2137074e 100644 --- a/app/src/main/res/layout/fragment_subscription.xml +++ b/app/src/main/res/layout/fragment_subscription.xml @@ -11,7 +11,6 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:scrollbars="vertical" - app:layoutManager="LinearLayoutManager" android:visibility="gone" tools:listitem="@layout/list_channel_item" tools:visibility="visible"/> diff --git a/app/src/main/res/layout/list_channel_grid_item.xml b/app/src/main/res/layout/list_channel_grid_item.xml new file mode 100644 index 000000000..3fe642974 --- /dev/null +++ b/app/src/main/res/layout/list_channel_grid_item.xml @@ -0,0 +1,48 @@ + + + + + + + + + + diff --git a/app/src/main/res/layout/list_playlist_grid_item.xml b/app/src/main/res/layout/list_playlist_grid_item.xml new file mode 100644 index 000000000..949b1159b --- /dev/null +++ b/app/src/main/res/layout/list_playlist_grid_item.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/list_stream_grid_item.xml b/app/src/main/res/layout/list_stream_grid_item.xml new file mode 100644 index 000000000..cf73bf9b1 --- /dev/null +++ b/app/src/main/res/layout/list_stream_grid_item.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/list_stream_playlist_grid_item.xml b/app/src/main/res/layout/list_stream_playlist_grid_item.xml new file mode 100644 index 000000000..4b31a452e --- /dev/null +++ b/app/src/main/res/layout/list_stream_playlist_grid_item.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 5e3e180b4..851262a73 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -490,6 +490,7 @@ Предел разрешения в мобильной сети Каналы Плейлисты + Видео Дорожки Пользователи Проматывать тишину @@ -502,4 +503,9 @@ Фоновый плеер Плеер в окне + Вид списка + Список + Сетка + Автоматически + diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index dd9431b46..1f95d2fbb 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -483,8 +483,12 @@ До тлового програвача Зменшити до віконного програвачу -Канали + Канали Плейлисти Стежки Користувачі - + + Вигляд списку + Список + Сiтка + diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index e7af3231e..229c00533 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -12,6 +12,8 @@ 124dp 70dp + 164dp + 92dp 94dp diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml index 2c45fa02d..54cc8b45b 100644 --- a/app/src/main/res/values/settings_keys.xml +++ b/app/src/main/res/values/settings_keys.xml @@ -19,7 +19,8 @@ autoplay_through_intent use_oldplayer - player_gesture_controls + volume_gesture_control + brightness_gesture_control resume_on_audio_focus_gain popup_remember_size_pos_key use_inexact_seek_key @@ -880,5 +881,18 @@ 144p + list_view_mode + auto - \ No newline at end of file + + auto + list + grid + + + @string/auto + @string/list + @string/grid + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 169d0b693..9fa90f47d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -5,7 +5,7 @@ %1$s views Published on %1$s No stream player found. Do you want to install VLC? - No stream player found (you can install VLC to play it) + No stream player found (you can install VLC to play it). Install Cancel https://f-droid.org/repository/browse/?fdfilter=vlc&fdid=org.videolan.vlc @@ -13,23 +13,24 @@ Open in popup mode Share Download - Download stream file. + Download stream file Search Settings - Did you mean: %1$s ? + Did you mean: %1$s? Share with Choose browser rotation Use external video player - Some resolutions will NOT have audio when this option is enabled + Removes audio at SOME resolutions Use external audio player NewPipe popup mode RSS Subscribe Subscribed + Unsubscribe Channel unsubscribed - Unable to change subscription - Unable to update subscription + Could not change subscription + Could not update subscription Show info Main @@ -48,8 +49,8 @@ Path to store downloaded videos in Enter download path for videos - Audio download path - Path to store downloaded audio in + Audio download folder + Downloaded audio is stored here Enter download path for audio files Autoplay @@ -75,15 +76,17 @@ Use fast inexact seek Inexact seek allows the player to seek to positions faster with reduced precision Load thumbnails - Disable to stop all thumbnails from loading and save on data and memory usage. Changing this will clear both in-memory and on-disk image cache. + When off no thumbnails load, saving data and memory usage. Changes clear both in-memory and on-disk image cache. Image cache wiped Wipe cached metadata Remove all cached webpage data Metadata cache wiped Auto-queue next stream Auto-append a related stream when playing the last stream in a non-repeating queue. - Player gesture controls - Use gestures to control the brightness and volume of the player + Volume gesture control + Use gestures to control the volume of the player + Brightness gesture control + Use gestures to control the brightness of the player Search suggestions Show suggestions when searching Search history @@ -93,18 +96,18 @@ Resume on focus gain Continue playing after interruptions (e.g. phone calls) Download - Next video - Show \'next\' and \'similar\' videos - Show \"hold to append\" tip + Next + Show \'Next\' and \'Similar\' videos + Show \"Hold to append\" tip Show tip when background or popup button is pressed on video details page - URL not supported + Unsupported URL Default content country Service Default content language Player Behavior - Video & Audio - History & Cache + Video & audio + History & cache Popup Appearance Other @@ -116,8 +119,8 @@ https://www.c3s.cc/ Play Content - Show age restricted content - Age Restricted Video. Allowing such material is possible from Settings. + Age restricted content + Show age Restricted Video. Allowing such material is possible from \"Settings\". live LIVE Downloads @@ -147,7 +150,7 @@ newpipe NewPipe Notification - Notifications for NewPipe Background and Popup Players + Notifications for NewPipe background and popup players [Unknown] @@ -158,29 +161,29 @@ Import database Export database - Will override your current history and subscriptions - Export history, subscriptions and playlists. + Overrides your current history and subscriptions + Export history, subscriptions and playlists Clear watch history - Deletes the history of played streams. - Delete whole watch history. + Deletes the history of played streams + Delete entire watch history? Watch history deleted. Clear search history - Deletes history of search keywords. - Delete whole search history. + Deletes history of search keywords + Delete entire search history? Search history deleted. Error - External storage not available. - Download to external SD Card is not possible yet. Should the download place be reset? + External storage unavailable + Download to external SD card is not possible yet. Reset download folder location? Network error Could not load all thumbnails Could not decrypt video URL signature Could not parse website Could not parse website completely - Content not available + Content unavailable Blocked by GEMA Could not set up download menu - This is a LIVE STREAM, which is not yet supported. + Live streams are not supported yet Could not get any stream Could not load image App/UI crashed @@ -191,10 +194,10 @@ Invalid URL No video streams found No audio streams found - Invalid directory - Invalid file/content source - File doesn\'t exist or insufficient permission to read or write to it - File name cannot be empty + No such folder + No such file/content source + The file doesn\'t exist or permission to read or write to it is lacking + Filename cannot be empty An error occurred: %1$s No streams available to download Using default tabs, error while reading saved tabs @@ -226,7 +229,7 @@ User report No results @string/no_videos - Nothing Here But Crickets + Nothing here but crickets Drag to reorder Cannot create download directory \'%1$s\' @@ -283,7 +286,7 @@ Filename Threads Error - Server unsupported + Unsupported server File already exists Malformed URL or Internet not available NewPipe Downloading @@ -298,8 +301,8 @@ MD5 SHA-1 reCAPTCHA - reCAPTCHA Challenge - reCAPTCHA Challenge requested + reCAPTCHA challenge + reCAPTCHA challenge requested @@ -344,7 +347,7 @@ https://newpipe.schabi.org/legal/privacy/ Read privacy policy NewPipe\'s License - NewPipe is copyleft libre software: You can use, study share and improve it at your will. Specifically you can redistribute and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + NewPipe is copyleft libre software: You can use, study share and improve it at will. Specifically you can redistribute and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Read license @@ -352,7 +355,7 @@ History Searched Watched - History is disabled + History is turned off History The history is empty History cleared @@ -373,10 +376,10 @@ Feed Page Channel Page Select a channel - No channel subscribed yet + No channel subscriptions yet Select a kiosk - Export complete - Import complete + Exported + Imported No valid ZIP file Warning: Could not import all files. This will override your current setup. @@ -390,17 +393,17 @@ %1$s/%2$s - Background Player - Popup Player + Background player + Popup player Remove Details Audio Settings - Hold To Enqueue - Enqueue on Background - Enqueue on Popup - Start Playing Here - Start Here on Background - Start Here on Popup + Hold To enqueue + Enqueue when backgrounded + Enqueue on new popup + Start playing here + Start here when backgrounded + Start here on new popup Open Drawer @@ -424,9 +427,9 @@ "Loading requested content" - Create New Playlist - Delete Playlist - Rename Playlist + New Playlist + Delete + Rename Name Add To Playlist Set as Playlist Thumbnail @@ -434,11 +437,11 @@ Bookmark Playlist Remove Bookmark - Do you want to delete this playlist? + Delete this playlist? Playlist created - Added to playlist - Playlist thumbnail changed - Could not delete playlist + Playlisted + Playlist thumbnail changed. + Could not delete playlist. No Captions @@ -457,11 +460,11 @@ Enable LeakCanary Memory leak monitoring may cause the app to become unresponsive when heap dumping - Report Out-of-lifecycle Errors + Report out-of-lifecycle errors Force reporting of undeliverable Rx exceptions outside of fragment or activity lifecycle after disposal - Import/Export + Import/export Import Import from Export to @@ -512,11 +515,14 @@ - Minimize on application switch - Action when switching to other application from main video player — %s + Minimize on app switch + Action when switching to other app from main video player — %s None Minimize to background player Minimize to popup player + List view mode + List + Grid + Auto Switch View - diff --git a/app/src/main/res/xml/appearance_settings.xml b/app/src/main/res/xml/appearance_settings.xml index 1f711b510..437736ab0 100644 --- a/app/src/main/res/xml/appearance_settings.xml +++ b/app/src/main/res/xml/appearance_settings.xml @@ -22,6 +22,14 @@ android:title="@string/show_hold_to_append_title" android:summary="@string/show_hold_to_append_summary"/> + + + android:key="@string/volume_gesture_control_key" + android:summary="@string/volume_gesture_control_summary" + android:title="@string/volume_gesture_control_title"/> + +