diff --git a/app/build.gradle b/app/build.gradle index d91a4bba7..64b73d77b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -66,6 +66,7 @@ android { compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 + encoding 'utf-8' } // Required and used only by groupie @@ -157,7 +158,7 @@ dependencies { exclude module: 'support-annotations' }) - implementation 'com.github.TeamNewPipe:NewPipeExtractor:665c69b5306d335985d5c0692f5119b5172c1b7a' + implementation 'com.github.TeamNewPipe:NewPipeExtractor:f3913e241e379adf0091319091e8f895c5fcfd07' testImplementation 'junit:junit:4.12' testImplementation 'org.mockito:mockito-core:2.23.0' 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 ec1c655a0..cf8be3213 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 @@ -175,6 +175,8 @@ public class VideoDetailFragment extends BaseStateFragment private View uploaderRootLayout; private TextView uploaderTextView; private ImageView uploaderThumb; + private TextView subChannelTextView; + private ImageView subChannelThumb; private TextView thumbsUpTextView; private ImageView thumbsUpImageView; @@ -419,18 +421,17 @@ public class VideoDetailFragment extends BaseStateFragment } break; case R.id.detail_uploader_root_layout: - if (TextUtils.isEmpty(currentInfo.getUploaderUrl())) { - Log.w(TAG, "Can't open channel because we got no channel URL"); - } else { - try { - NavigationHelper.openChannelFragment( - getFragmentManager(), - currentInfo.getServiceId(), - currentInfo.getUploaderUrl(), - currentInfo.getUploaderName()); - } catch (Exception e) { - ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e); + if (TextUtils.isEmpty(currentInfo.getSubChannelUrl())) { + if (!TextUtils.isEmpty(currentInfo.getUploaderUrl())) { + openChannel(currentInfo.getUploaderUrl(), currentInfo.getUploaderName()); } + + if (DEBUG) { + Log.i(TAG, "Can't open sub-channel because we got no channel URL"); + } + } else { + openChannel(currentInfo.getSubChannelUrl(), + currentInfo.getSubChannelName()); } break; case R.id.detail_thumbnail_root_layout: @@ -447,6 +448,18 @@ public class VideoDetailFragment extends BaseStateFragment } } + private void openChannel(final String subChannelUrl, final String subChannelName) { + try { + NavigationHelper.openChannelFragment( + getFragmentManager(), + currentInfo.getServiceId(), + subChannelUrl, + subChannelName); + } catch (Exception e) { + ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e); + } + } + @Override public boolean onLongClick(final View v) { if (isLoading.get() || currentInfo == null) { @@ -463,6 +476,15 @@ public class VideoDetailFragment extends BaseStateFragment case R.id.detail_controls_download: NavigationHelper.openDownloads(getActivity()); break; + + case R.id.detail_uploader_root_layout: + if (TextUtils.isEmpty(currentInfo.getSubChannelUrl())) { + Log.w(TAG, + "Can't open parent channel because we got no parent channel URL"); + } else { + openChannel(currentInfo.getUploaderUrl(), currentInfo.getUploaderName()); + } + break; } return true; @@ -525,6 +547,8 @@ public class VideoDetailFragment extends BaseStateFragment uploaderRootLayout = rootView.findViewById(R.id.detail_uploader_root_layout); uploaderTextView = rootView.findViewById(R.id.detail_uploader_text_view); uploaderThumb = rootView.findViewById(R.id.detail_uploader_thumbnail_view); + subChannelTextView = rootView.findViewById(R.id.detail_sub_channel_text_view); + subChannelThumb = rootView.findViewById(R.id.detail_sub_channel_thumbnail_view); appBarLayout = rootView.findViewById(R.id.appbarlayout); viewPager = rootView.findViewById(R.id.viewpager); @@ -554,8 +578,9 @@ public class VideoDetailFragment extends BaseStateFragment protected void initListeners() { super.initListeners(); - videoTitleRoot.setOnClickListener(this); uploaderRootLayout.setOnClickListener(this); + uploaderRootLayout.setOnLongClickListener(this); + videoTitleRoot.setOnClickListener(this); thumbnailBackgroundButton.setOnClickListener(this); detailControlsBackground.setOnClickListener(this); detailControlsPopup.setOnClickListener(this); @@ -603,6 +628,11 @@ public class VideoDetailFragment extends BaseStateFragment ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS, onFailListener); } + if (!TextUtils.isEmpty(info.getSubChannelAvatarUrl())) { + IMAGE_LOADER.displayImage(info.getSubChannelAvatarUrl(), subChannelThumb, + ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS); + } + if (!TextUtils.isEmpty(info.getUploaderAvatarUrl())) { IMAGE_LOADER.displayImage(info.getUploaderAvatarUrl(), uploaderThumb, ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS); @@ -964,7 +994,7 @@ public class VideoDetailFragment extends BaseStateFragment @NonNull final StreamInfo info, @NonNull final Stream selectedStream) { NavigationHelper.playOnExternalPlayer(context, currentInfo.getName(), - currentInfo.getUploaderName(), selectedStream); + currentInfo.getSubChannelName(), selectedStream); final HistoryRecordManager recordManager = new HistoryRecordManager(requireContext()); disposables.add(recordManager.onViewed(info).onErrorComplete() @@ -1097,9 +1127,9 @@ public class VideoDetailFragment extends BaseStateFragment } IMAGE_LOADER.cancelDisplayTask(thumbnailImageView); - IMAGE_LOADER.cancelDisplayTask(uploaderThumb); + IMAGE_LOADER.cancelDisplayTask(subChannelThumb); thumbnailImageView.setImageBitmap(null); - uploaderThumb.setImageBitmap(null); + subChannelThumb.setImageBitmap(null); } @Override @@ -1127,13 +1157,16 @@ public class VideoDetailFragment extends BaseStateFragment animateView(thumbnailPlayButton, true, 200); videoTitleTextView.setText(name); - if (!TextUtils.isEmpty(info.getUploaderName())) { - uploaderTextView.setText(info.getUploaderName()); - uploaderTextView.setVisibility(View.VISIBLE); - uploaderTextView.setSelected(true); + if (!TextUtils.isEmpty(info.getSubChannelName())) { + displayBothUploaderAndSubChannel(info); + } else if (!TextUtils.isEmpty(info.getUploaderName())) { + displayUploaderAsSubChannel(info); } else { uploaderTextView.setVisibility(View.GONE); + uploaderThumb.setVisibility(View.GONE); } + + subChannelThumb.setImageDrawable(ContextCompat.getDrawable(activity, R.drawable.buddy)); uploaderThumb.setImageDrawable(ContextCompat.getDrawable(activity, R.drawable.buddy)); if (info.getViewCount() >= 0) { @@ -1265,6 +1298,31 @@ public class VideoDetailFragment extends BaseStateFragment tabLayout.setVisibility(View.GONE); } + private void displayUploaderAsSubChannel(final StreamInfo info) { + subChannelTextView.setText(info.getUploaderName()); + subChannelTextView.setVisibility(View.VISIBLE); + subChannelTextView.setSelected(true); + uploaderTextView.setVisibility(View.GONE); + } + + private void displayBothUploaderAndSubChannel(final StreamInfo info) { + subChannelTextView.setText(info.getSubChannelName()); + subChannelTextView.setVisibility(View.VISIBLE); + subChannelTextView.setSelected(true); + + subChannelThumb.setVisibility(View.VISIBLE); + + if (!TextUtils.isEmpty(info.getUploaderName())) { + uploaderTextView.setText( + String.format(getString(R.string.video_detail_by), info.getUploaderName())); + uploaderTextView.setVisibility(View.VISIBLE); + uploaderTextView.setSelected(true); + } else { + uploaderTextView.setVisibility(View.GONE); + } + } + + public void openDownloadDialog() { try { DownloadDialog downloadDialog = DownloadDialog.newInstance(currentInfo); diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java index ad8d25d3a..a9b703b14 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java @@ -21,6 +21,7 @@ import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; import androidx.core.content.ContextCompat; import com.jakewharton.rxbinding2.view.RxView; @@ -38,6 +39,7 @@ import org.schabi.newpipe.fragments.list.BaseListInfoFragment; import org.schabi.newpipe.local.subscription.SubscriptionManager; import org.schabi.newpipe.player.playqueue.ChannelPlayQueue; import org.schabi.newpipe.player.playqueue.PlayQueue; +import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.util.AnimationUtils; import org.schabi.newpipe.util.ExtractorHelper; @@ -65,7 +67,8 @@ import static org.schabi.newpipe.util.AnimationUtils.animateBackgroundColor; import static org.schabi.newpipe.util.AnimationUtils.animateTextColor; import static org.schabi.newpipe.util.AnimationUtils.animateView; -public class ChannelFragment extends BaseListInfoFragment { +public class ChannelFragment extends BaseListInfoFragment + implements View.OnClickListener { private static final int BUTTON_DEBOUNCE_INTERVAL = 100; private final CompositeDisposable disposables = new CompositeDisposable(); private Disposable subscribeButtonMonitor; @@ -79,6 +82,8 @@ public class ChannelFragment extends BaseListInfoFragment { private ImageView headerChannelBanner; private ImageView headerAvatarView; private TextView headerTitleView; + private ImageView headerSubChannelAvatarView; + private TextView headerSubChannelTitleView; private TextView headerSubscribersTextView; private Button headerSubscribeButton; private View playlistCtrl; @@ -156,7 +161,10 @@ public class ChannelFragment extends BaseListInfoFragment { headerSubscribersTextView = headerRootLayout.findViewById(R.id.channel_subscriber_view); headerSubscribeButton = headerRootLayout.findViewById(R.id.channel_subscribe_button); playlistCtrl = headerRootLayout.findViewById(R.id.playlist_control); - + headerSubChannelAvatarView = + headerRootLayout.findViewById(R.id.sub_channel_avatar_view); + headerSubChannelTitleView = + headerRootLayout.findViewById(R.id.sub_channel_title_view); headerPlayAllButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_all_button); headerPopupButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_popup_button); @@ -165,6 +173,14 @@ public class ChannelFragment extends BaseListInfoFragment { return headerRootLayout; } + @Override + protected void initListeners() { + super.initListeners(); + + headerSubChannelTitleView.setOnClickListener(this); + headerSubChannelAvatarView.setOnClickListener(this); + } + /*////////////////////////////////////////////////////////////////////////// // Menu //////////////////////////////////////////////////////////////////////////*/ @@ -394,6 +410,34 @@ public class ChannelFragment extends BaseListInfoFragment { return ExtractorHelper.getChannelInfo(serviceId, url, forceLoad); } + /*////////////////////////////////////////////////////////////////////////// + // OnClick + //////////////////////////////////////////////////////////////////////////*/ + + @Override + public void onClick(final View v) { + if (isLoading.get() || currentInfo == null) { + return; + } + + switch (v.getId()) { + case R.id.sub_channel_avatar_view: + case R.id.sub_channel_title_view: + if (!TextUtils.isEmpty(currentInfo.getParentChannelUrl())) { + try { + NavigationHelper.openChannelFragment(getFragmentManager(), + currentInfo.getServiceId(), currentInfo.getParentChannelUrl(), + currentInfo.getParentChannelName()); + } catch (Exception e) { + ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e); + } + } else if (DEBUG) { + Log.i(TAG, "Can't open parent channel because we got no channel URL"); + } + break; + } + } + /*////////////////////////////////////////////////////////////////////////// // Contract //////////////////////////////////////////////////////////////////////////*/ @@ -404,6 +448,7 @@ public class ChannelFragment extends BaseListInfoFragment { IMAGE_LOADER.cancelDisplayTask(headerChannelBanner); IMAGE_LOADER.cancelDisplayTask(headerAvatarView); + IMAGE_LOADER.cancelDisplayTask(headerSubChannelAvatarView); animateView(headerSubscribeButton, false, 100); } @@ -416,6 +461,8 @@ public class ChannelFragment extends BaseListInfoFragment { ImageDisplayConstants.DISPLAY_BANNER_OPTIONS); IMAGE_LOADER.displayImage(result.getAvatarUrl(), headerAvatarView, ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS); + IMAGE_LOADER.displayImage(result.getParentChannelAvatarUrl(), headerSubChannelAvatarView, + ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS); headerSubscribersTextView.setVisibility(View.VISIBLE); if (result.getSubscriberCount() >= 0) { @@ -425,6 +472,17 @@ public class ChannelFragment extends BaseListInfoFragment { headerSubscribersTextView.setText(R.string.subscribers_count_not_available); } + if (!TextUtils.isEmpty(currentInfo.getParentChannelName())) { + headerSubChannelTitleView.setText(String.format( + getString(R.string.channel_created_by), + currentInfo.getParentChannelName()) + ); + headerSubChannelTitleView.setVisibility(View.VISIBLE); + headerSubChannelAvatarView.setVisibility(View.VISIBLE); + } else { + headerSubChannelTitleView.setVisibility(View.GONE); + } + if (menuRssButton != null) { menuRssButton.setVisible(!TextUtils.isEmpty(result.getFeedUrl())); } diff --git a/app/src/main/java/org/schabi/newpipe/streams/Mp4FromDashWriter.java b/app/src/main/java/org/schabi/newpipe/streams/Mp4FromDashWriter.java index 6f7d27c2c..eb208280e 100644 --- a/app/src/main/java/org/schabi/newpipe/streams/Mp4FromDashWriter.java +++ b/app/src/main/java/org/schabi/newpipe/streams/Mp4FromDashWriter.java @@ -711,7 +711,8 @@ public class Mp4FromDashWriter { for (int i = 0; i < tracks.length; i++) { if (tracks[i].trak.tkhd.matrix.length != 36) { - throw new RuntimeException("bad track matrix length (expected 36) in track n°" + i); + throw + new RuntimeException("bad track matrix length (expected 36) in track n°" + i); } makeTrak(i, durations[i], defaultMediaTime[i], tablesInfo[i], is64); } 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 index 10a622d55..b119bf761 100644 --- a/app/src/main/res/layout-large-land/fragment_video_detail.xml +++ b/app/src/main/res/layout-large-land/fragment_video_detail.xml @@ -1,280 +1,319 @@ + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/video_item_detail" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:baselineAligned="false" + android:focusableInTouchMode="true" + android:orientation="horizontal" + tools:ignore="RtlHardcoded"> - + - + - + - - + + - + - + - + - + - + - + - + - + - - + + - - + + - + - + - + - - + + - - + + - - + + - - + + - - + + - + - + - - + - - + + - + - + - + - + - + - - - + + + - + - - + + - + - + - + - + - + - - + + - + - + - + - + - - + + - + - + - + - + - + - + - + diff --git a/app/src/main/res/layout/channel_header.xml b/app/src/main/res/layout/channel_header.xml index 4a0e261c5..0554db48e 100644 --- a/app/src/main/res/layout/channel_header.xml +++ b/app/src/main/res/layout/channel_header.xml @@ -1,6 +1,5 @@ - + tools:ignore="ContentDescription" /> - + android:layout_marginTop="50dp"> + + + + + + tools:text="Lorem ipsum dolor" /> + + + tools:visibility="visible" /> + tools:visibility="visible" /> - \ No newline at end of file + diff --git a/app/src/main/res/layout/fragment_video_detail.xml b/app/src/main/res/layout/fragment_video_detail.xml index cc57e09d4..dfe6435f7 100644 --- a/app/src/main/res/layout/fragment_video_detail.xml +++ b/app/src/main/res/layout/fragment_video_detail.xml @@ -1,528 +1,567 @@ + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/video_item_detail" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:focusableInTouchMode="true"> - + - + - + - - + + - + - + - + - + - + - + - + - + - - + + - - + + - + - + - + - - + + - - + + - - + + - - + + - - + + - + - + - - + - - + - + - + - + - + - + + - - - + + - + - - + - + - + - + - + + + - + - - + + - + - + - + - + - - + - + + + + + + + + + + + + + + + - + - + - + - + diff --git a/app/src/main/res/values-land/dimens.xml b/app/src/main/res/values-land/dimens.xml index eea6c5cf0..e097fb035 100644 --- a/app/src/main/res/values-land/dimens.xml +++ b/app/src/main/res/values-land/dimens.xml @@ -25,7 +25,8 @@ 16sp 14sp 13sp - 16sp + 12sp + 16sp 14sp 14sp 17sp @@ -33,9 +34,11 @@ 14sp 42dp + 21dp 20sp 20sp 90dp + 45dp 8dp 4dp @@ -48,4 +51,4 @@ 16sp - \ No newline at end of file + diff --git a/app/src/main/res/values-sw600dp-land/dimens.xml b/app/src/main/res/values-sw600dp-land/dimens.xml index 321ee69cc..b578744d7 100644 --- a/app/src/main/res/values-sw600dp-land/dimens.xml +++ b/app/src/main/res/values-sw600dp-land/dimens.xml @@ -18,4 +18,4 @@ 4dp - \ No newline at end of file + diff --git a/app/src/main/res/values-sw600dp/dimens.xml b/app/src/main/res/values-sw600dp/dimens.xml index d745cdfb0..15b6d214e 100644 --- a/app/src/main/res/values-sw600dp/dimens.xml +++ b/app/src/main/res/values-sw600dp/dimens.xml @@ -8,15 +8,17 @@ 16sp 16sp 14sp - 16sp + 16sp + 12sp 16sp 16sp 18sp - 45dp + 40dp + 20dp 18sp 18sp 10dp - \ No newline at end of file + diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index d97444f5b..5f58f4c90 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -54,17 +54,20 @@ 15sp 13sp 12sp - 14sp + 12sp + 14sp 13sp 13sp 15sp - 12sp + 12sp 12sp - 28dp + 32dp + 16dp 18sp 18sp 70dp + 35dp 5dp 50dp diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3b7bc8a4a..bfd848d10 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -651,4 +651,7 @@ Disable fast mode Do you think feed loading is too slow? If so, try enabling fast loading (you can change it in settings or by pressing the button below).\n\nNewPipe offers two feed loading strategies:\n• Fetching the whole subscription channel, which is slow but complete.\n• Using a dedicated service endpoint, which is fast but usually not complete.\n\nThe difference between the two is that the fast one usually lacks some information, like the item\'s duration or type (can\'t distinguish between live videos and normal ones) and it may return less items.\n\nYouTube is an example of a service that offers this fast method with its RSS feed.\n\nSo the choice boils down to what you prefer: speed or precise information. This content is not yet supported by NewPipe.\n\nIt will hopefully be supported in a future version. + Channel\'s avatar thumbnail + Created by %s + By %s diff --git a/settings.gradle b/settings.gradle index e7b4def49..9d495b34f 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -include ':app' +include ':app' \ No newline at end of file