diff --git a/app/build.gradle b/app/build.gradle index 4ae9f0fb7..d409532bb 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -55,7 +55,7 @@ dependencies { exclude module: 'support-annotations' } - implementation 'com.github.TeamNewPipe:NewPipeExtractor:217d13b1028' + implementation 'com.github.yausername:NewPipeExtractor:5242bda' testImplementation 'junit:junit:4.12' testImplementation 'org.mockito:mockito-core:2.8.9' diff --git a/app/src/main/java/org/schabi/newpipe/Downloader.java b/app/src/main/java/org/schabi/newpipe/Downloader.java index 17dc5859d..d51d31e11 100644 --- a/app/src/main/java/org/schabi/newpipe/Downloader.java +++ b/app/src/main/java/org/schabi/newpipe/Downloader.java @@ -3,17 +3,22 @@ package org.schabi.newpipe; import android.support.annotation.Nullable; import android.text.TextUtils; +import org.schabi.newpipe.extractor.DownloadResponse; import org.schabi.newpipe.extractor.exceptions.ReCaptchaException; import java.io.IOException; import java.io.InputStream; +import java.io.Serializable; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; +import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.Request; +import okhttp3.RequestBody; import okhttp3.Response; import okhttp3.ResponseBody; @@ -137,13 +142,16 @@ public class Downloader implements org.schabi.newpipe.extractor.Downloader { private ResponseBody getBody(String siteUrl, Map customProperties) throws IOException, ReCaptchaException { final Request.Builder requestBuilder = new Request.Builder() - .method("GET", null).url(siteUrl) - .addHeader("User-Agent", USER_AGENT); + .method("GET", null).url(siteUrl); for (Map.Entry header : customProperties.entrySet()) { requestBuilder.addHeader(header.getKey(), header.getValue()); } + if (!customProperties.containsKey("User-Agent")) { + requestBuilder.header("User-Agent", USER_AGENT); + } + if (!TextUtils.isEmpty(mCookies)) { requestBuilder.addHeader("Cookie", mCookies); } @@ -175,4 +183,91 @@ public class Downloader implements org.schabi.newpipe.extractor.Downloader { public String download(String siteUrl) throws IOException, ReCaptchaException { return download(siteUrl, Collections.emptyMap()); } -} + + + @Override + public DownloadResponse get(String siteUrl, Map> requestHeaders) throws IOException, ReCaptchaException { + final Request.Builder requestBuilder = new Request.Builder() + .method("GET", null).url(siteUrl); + + // set custom headers in request + for (Map.Entry> pair : requestHeaders.entrySet()) { + for(String value : pair.getValue()){ + requestBuilder.addHeader(pair.getKey(), value); + } + } + + if (!requestHeaders.containsKey("User-Agent")) { + requestBuilder.header("User-Agent", USER_AGENT); + } + + if (!TextUtils.isEmpty(mCookies)) { + requestBuilder.addHeader("Cookie", mCookies); + } + + final Request request = requestBuilder.build(); + final Response response = client.newCall(request).execute(); + final ResponseBody body = response.body(); + + if (response.code() == 429) { + throw new ReCaptchaException("reCaptcha Challenge requested"); + } + + if (body == null) { + response.close(); + return null; + } + + return new DownloadResponse(body.string(), response.headers().toMultimap()); + } + + @Override + public DownloadResponse get(String siteUrl) throws IOException, ReCaptchaException { + return get(siteUrl, Collections.emptyMap()); + } + + @Override + public DownloadResponse post(String siteUrl, String requestBody, Map> requestHeaders) throws IOException, ReCaptchaException { + + if(null == requestHeaders.get("Content-Type") || requestHeaders.get("Content-Type").isEmpty()){ + // content type header is required. maybe throw an exception here + return null; + } + + String contentType = requestHeaders.get("Content-Type").get(0); + + RequestBody okRequestBody = RequestBody.create(MediaType.parse(contentType), requestBody); + final Request.Builder requestBuilder = new Request.Builder() + .method("POST", okRequestBody).url(siteUrl); + + // set custom headers in request + for (Map.Entry> pair : requestHeaders.entrySet()) { + for(String value : pair.getValue()){ + requestBuilder.addHeader(pair.getKey(), value); + } + } + + if (!requestHeaders.containsKey("User-Agent")) { + requestBuilder.header("User-Agent", USER_AGENT); + } + + if (!TextUtils.isEmpty(mCookies)) { + requestBuilder.addHeader("Cookie", mCookies); + } + + final Request request = requestBuilder.build(); + final Response response = client.newCall(request).execute(); + final ResponseBody body = response.body(); + + if (response.code() == 429) { + throw new ReCaptchaException("reCaptcha Challenge requested"); + } + + if (body == null) { + response.close(); + return null; + } + + return new DownloadResponse(body.string(), response.headers().toMultimap()); + } +} \ No newline at end of file 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 c726f8cee..cbea6803e 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 @@ -40,6 +40,7 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.RelativeLayout; import android.widget.Spinner; +import android.widget.TabHost; import android.widget.TextView; import android.widget.Toast; @@ -53,6 +54,7 @@ import org.schabi.newpipe.ReCaptchaActivity; import org.schabi.newpipe.download.DownloadDialog; import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.NewPipe; +import org.schabi.newpipe.extractor.comments.CommentsInfoItem; import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException; import org.schabi.newpipe.extractor.exceptions.ParsingException; import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubeStreamExtractor; @@ -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; @@ -114,6 +116,8 @@ public class VideoDetailFragment // Amount of videos to show on start private static final int INITIAL_RELATED_VIDEOS = 8; + // Amount of comments to show on start + private static final int INITIAL_COMMENTS = 8; private InfoItemBuilder infoItemBuilder = null; @@ -121,18 +125,24 @@ public class VideoDetailFragment private static final int RELATED_STREAMS_UPDATE_FLAG = 0x1; private static final int RESOLUTIONS_MENU_UPDATE_FLAG = 0x2; private static final int TOOLBAR_ITEMS_UPDATE_FLAG = 0x4; + private static final int COMMENTS_UPDATE_FLAG = 0x4; private boolean autoPlayEnabled; private boolean showRelatedStreams; + private boolean showComments; private boolean wasRelatedStreamsExpanded = false; - @State protected int serviceId = Constants.NO_SERVICE_ID; - @State protected String name; - @State protected String url; + @State + protected int serviceId = Constants.NO_SERVICE_ID; + @State + protected String name; + @State + protected String url; private StreamInfo currentInfo; private Disposable currentWorker; - @NonNull private CompositeDisposable disposables = new CompositeDisposable(); + @NonNull + private CompositeDisposable disposables = new CompositeDisposable(); private List sortedVideoStreams; private int selectedVideoStreamIndex = -1; @@ -183,6 +193,16 @@ public class VideoDetailFragment private LinearLayout relatedStreamsView; private ImageButton relatedStreamExpandButton; + private LinearLayout commentsRootLayout; + private LinearLayout commentsView; + private ImageButton commentsExpandButton; + private Disposable commentsDisposable; + + private TabHost tabHost; + private static final String COMMENTS_TAB_TAG = "CommentsTab"; + private static final String RELATED_TAB_TAG = "RelatedTab"; + + /*////////////////////////////////////////////////////////////////////////*/ @@ -197,12 +217,17 @@ public class VideoDetailFragment //////////////////////////////////////////////////////////////////////////*/ @Override - public void onCreate(Bundle savedInstanceState) { + public void + onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setHasOptionsMenu(true); showRelatedStreams = PreferenceManager.getDefaultSharedPreferences(activity) .getBoolean(getString(R.string.show_next_video_key), true); + + showComments = PreferenceManager.getDefaultSharedPreferences(activity) + .getBoolean(getString(R.string.show_comments), true); + PreferenceManager.getDefaultSharedPreferences(activity) .registerOnSharedPreferenceChangeListener(this); } @@ -224,14 +249,17 @@ public class VideoDetailFragment if (updateFlags != 0) { if (!isLoading.get() && currentInfo != null) { - if ((updateFlags & RELATED_STREAMS_UPDATE_FLAG) != 0) initRelatedVideos(currentInfo); + if ((updateFlags & RELATED_STREAMS_UPDATE_FLAG) != 0) + initRelatedVideos(currentInfo); if ((updateFlags & RESOLUTIONS_MENU_UPDATE_FLAG) != 0) setupActionBar(currentInfo); + if ((updateFlags & COMMENTS_UPDATE_FLAG) != 0) initComments(currentInfo); } if ((updateFlags & TOOLBAR_ITEMS_UPDATE_FLAG) != 0 && menu != null) { updateMenuItemVisibility(); } + updateFlags = 0; } @@ -288,6 +316,9 @@ public class VideoDetailFragment updateFlags |= RESOLUTIONS_MENU_UPDATE_FLAG; } else if (key.equals(getString(R.string.show_play_with_kodi_key))) { updateFlags |= TOOLBAR_ITEMS_UPDATE_FLAG; + } else if (key.equals(R.string.show_comments)) { + showComments = sharedPreferences.getBoolean(key, true); + updateFlags |= COMMENTS_UPDATE_FLAG; } } @@ -312,7 +343,8 @@ public class VideoDetailFragment } if (!isLoading.get() && currentInfo != null && isVisible()) { - outState.putSerializable(INFO_KEY, currentInfo); + //TODO fix this. it should not be commented + //outState.putSerializable(INFO_KEY, currentInfo); } outState.putSerializable(STACK_KEY, stack); @@ -392,6 +424,9 @@ public class VideoDetailFragment case R.id.detail_related_streams_expand: toggleExpandRelatedVideos(currentInfo); break; + case R.id.detail_comments_expand: + toggleExpandComments(currentInfo); + break; } } @@ -452,6 +487,46 @@ public class VideoDetailFragment ThemeHelper.resolveResourceIdFromAttr(activity, R.attr.collapse))); } + + private void toggleExpandComments(StreamInfo info) { + if (DEBUG) Log.d(TAG, "toggleExpandComments() called with: info = [" + info + "]"); + if (!showComments) return; + + int initialCount = INITIAL_COMMENTS; + + if (commentsView.getChildCount() > initialCount && commentsView.getChildCount() >= info.getComments().size() && !info.hasMoreComments()) { + commentsView.removeViews(initialCount, + commentsView.getChildCount() - (initialCount)); + commentsExpandButton.setImageDrawable(ContextCompat.getDrawable( + activity, ThemeHelper.resolveResourceIdFromAttr(activity, R.attr.expand))); + return; + } + + //Log.d(TAG, "toggleExpandRelatedVideos() called with: info = [" + info + "], from = [" + INITIAL_RELATED_VIDEOS + "]"); + int currentCount = commentsView.getChildCount(); + for (int i = currentCount; i < info.getComments().size(); i++) { + CommentsInfoItem item = info.getComments().get(i); + //Log.d(TAG, "i = " + i); + commentsView.addView(infoItemBuilder.buildView(commentsView, item)); + } + + if (info.hasMoreComments()) { + loadMoreComments(info); + } else { + commentsExpandButton.setImageDrawable( + ContextCompat.getDrawable(activity, + ThemeHelper.resolveResourceIdFromAttr(activity, R.attr.collapse))); + } + } + + private void loadMoreComments(StreamInfo info) { + if (commentsDisposable != null) commentsDisposable.dispose(); + + commentsDisposable = Single.fromCallable(() -> { + StreamInfo.loadMoreComments(info); + return info.getComments(); + }).subscribeOn(Schedulers.io()).doOnError(e -> info.addError(e)).subscribe(); + } /*////////////////////////////////////////////////////////////////////////// // Init //////////////////////////////////////////////////////////////////////////*/ @@ -498,14 +573,37 @@ public class VideoDetailFragment uploaderTextView = rootView.findViewById(R.id.detail_uploader_text_view); uploaderThumb = rootView.findViewById(R.id.detail_uploader_thumbnail_view); + tabHost = (TabHost) rootView.findViewById(R.id.tab_host); + tabHost.setup(); + + TabHost.TabSpec commentsTab = tabHost.newTabSpec(COMMENTS_TAB_TAG); + commentsTab.setContent(R.id.detail_comments_root_layout); + commentsTab.setIndicator(getString(R.string.comments)); + + + TabHost.TabSpec relatedVideosTab = tabHost.newTabSpec(RELATED_TAB_TAG); + relatedVideosTab.setContent(R.id.detail_related_streams_root_layout); + relatedVideosTab.setIndicator(getString(R.string.next_video_title)); + + tabHost.addTab(commentsTab); + tabHost.addTab(relatedVideosTab); + + //show comments tab by default + tabHost.setCurrentTabByTag(COMMENTS_TAB_TAG); + relatedStreamRootLayout = rootView.findViewById(R.id.detail_related_streams_root_layout); nextStreamTitle = rootView.findViewById(R.id.detail_next_stream_title); relatedStreamsView = rootView.findViewById(R.id.detail_related_streams_view); - relatedStreamExpandButton = rootView.findViewById(R.id.detail_related_streams_expand); + commentsRootLayout = rootView.findViewById(R.id.detail_comments_root_layout); + commentsView = rootView.findViewById(R.id.detail_comments_view); + commentsExpandButton = rootView.findViewById(R.id.detail_comments_expand); + infoItemBuilder = new InfoItemBuilder(activity); setHeightThumbnail(); + + } @Override @@ -532,6 +630,7 @@ public class VideoDetailFragment detailControlsDownload.setOnClickListener(this); detailControlsDownload.setOnLongClickListener(this); relatedStreamExpandButton.setOnClickListener(this); + commentsExpandButton.setOnClickListener(this); detailControlsBackground.setLongClickable(true); detailControlsPopup.setLongClickable(true); @@ -622,7 +721,7 @@ public class VideoDetailFragment relatedStreamsView.addView( infoItemBuilder.buildView(relatedStreamsView, info.getNextVideo())); relatedStreamsView.addView(getSeparatorView()); - relatedStreamRootLayout.setVisibility(View.VISIBLE); + showRelatedStreamsIfSelected(); } else nextStreamTitle.setVisibility(View.GONE); if (info.getRelatedStreams() != null @@ -639,7 +738,7 @@ public class VideoDetailFragment } //if (DEBUG) Log.d(TAG, "Total time " + ((System.nanoTime() - first) / 1000000L) + "ms"); - relatedStreamRootLayout.setVisibility(View.VISIBLE); + showRelatedStreamsIfSelected(); relatedStreamExpandButton.setVisibility(View.VISIBLE); relatedStreamExpandButton.setImageDrawable(ContextCompat.getDrawable( @@ -648,6 +747,47 @@ public class VideoDetailFragment if (info.getNextVideo() == null) relatedStreamRootLayout.setVisibility(View.GONE); relatedStreamExpandButton.setVisibility(View.GONE); } + + } + + private void showRelatedStreamsIfSelected() { + if (tabHost.getCurrentTabTag().contentEquals(RELATED_TAB_TAG)) { + relatedStreamRootLayout.setVisibility(View.VISIBLE); + } + } + + private void initComments(StreamInfo info) { + if (commentsView.getChildCount() > 0) commentsView.removeAllViews(); + + if (info.getComments() != null + && !info.getComments().isEmpty() && showComments) { + //long first = System.nanoTime(), each; + int to = info.getComments().size() >= INITIAL_RELATED_VIDEOS + ? INITIAL_RELATED_VIDEOS + : info.getComments().size(); + for (int i = 0; i < to; i++) { + InfoItem item = info.getComments().get(i); + //each = System.nanoTime(); + commentsView.addView(infoItemBuilder.buildView(commentsView, item)); + //if (DEBUG) Log.d(TAG, "each took " + ((System.nanoTime() - each) / 1000000L) + "ms"); + } + //if (DEBUG) Log.d(TAG, "Total time " + ((System.nanoTime() - first) / 1000000L) + "ms"); + + showCommentsIfSelected(); + commentsExpandButton.setVisibility(View.VISIBLE); + + commentsExpandButton.setImageDrawable(ContextCompat.getDrawable( + activity, ThemeHelper.resolveResourceIdFromAttr(activity, R.attr.expand))); + } else { + commentsRootLayout.setVisibility(View.GONE); + } + + } + + private void showCommentsIfSelected() { + if (tabHost.getCurrentTabTag().contentEquals(COMMENTS_TAB_TAG)) { + commentsRootLayout.setVisibility(View.VISIBLE); + } } /*////////////////////////////////////////////////////////////////////////// @@ -682,7 +822,7 @@ public class VideoDetailFragment @Override public boolean onOptionsItemSelected(MenuItem item) { - if(isLoading.get()) { + if (isLoading.get()) { // if is still loading block menu return true; } @@ -706,7 +846,7 @@ public class VideoDetailFragment NavigationHelper.playWithKore(activity, Uri.parse( url.replace("https", "http"))); } catch (Exception e) { - if(DEBUG) Log.i(TAG, "Failed to start kore", e); + if (DEBUG) Log.i(TAG, "Failed to start kore", e); showInstallKoreDialog(activity); } return true; @@ -720,7 +860,8 @@ public class VideoDetailFragment builder.setMessage(R.string.kore_not_found) .setPositiveButton(R.string.install, (DialogInterface dialog, int which) -> NavigationHelper.installKore(context)) - .setNegativeButton(R.string.cancel, (DialogInterface dialog, int which) -> {}); + .setNegativeButton(R.string.cancel, (DialogInterface dialog, int which) -> { + }); builder.create().show(); } @@ -823,7 +964,8 @@ public class VideoDetailFragment } public void prepareAndHandleInfo(final StreamInfo info, boolean scrollToTop) { - if (DEBUG) Log.d(TAG, "prepareAndHandleInfo() called with: info = [" + info + "], scrollToTop = [" + scrollToTop + "]"); + if (DEBUG) + Log.d(TAG, "prepareAndHandleInfo() called with: info = [" + info + "], scrollToTop = [" + scrollToTop + "]"); setInitialData(info.getServiceId(), info.getOriginalUrl(), info.getName()); pushToStack(serviceId, url, name); @@ -1057,7 +1199,7 @@ public class VideoDetailFragment .setInterpolator(new FastOutSlowInInterpolator()) .start(); - if (showRelatedStreams) { + if (showRelatedStreams && tabHost.getCurrentTabTag().contentEquals(RELATED_TAB_TAG)) { relatedStreamRootLayout.animate().setListener(null).cancel(); relatedStreamRootLayout.setAlpha(0f); relatedStreamRootLayout.setTranslationY(translationY); @@ -1070,6 +1212,20 @@ public class VideoDetailFragment .setInterpolator(new FastOutSlowInInterpolator()) .start(); } + + if (showComments && tabHost.getCurrentTabTag().contentEquals(COMMENTS_TAB_TAG)) { + commentsRootLayout.animate().setListener(null).cancel(); + commentsRootLayout.setAlpha(0f); + commentsRootLayout.setTranslationY(translationY); + commentsRootLayout.setVisibility(View.VISIBLE); + commentsRootLayout.animate() + .alpha(1f) + .translationY(0) + .setStartDelay((long) (duration * .8f) + delay) + .setDuration(duration) + .setInterpolator(new FastOutSlowInInterpolator()) + .start(); + } } protected void setInitialData(int serviceId, String url, String name) { @@ -1204,6 +1360,8 @@ public class VideoDetailFragment setupActionBar(info); initThumbnailViews(info); initRelatedVideos(info); + initComments(info); + if (wasRelatedStreamsExpanded) { toggleExpandRelatedVideos(currentInfo); wasRelatedStreamsExpanded = false; @@ -1246,19 +1404,19 @@ public class VideoDetailFragment public void openDownloadDialog() { - try { - DownloadDialog downloadDialog = DownloadDialog.newInstance(currentInfo); - downloadDialog.setVideoStreams(sortedVideoStreams); - downloadDialog.setAudioStreams(currentInfo.getAudioStreams()); - downloadDialog.setSelectedVideoStream(selectedVideoStreamIndex); + try { + DownloadDialog downloadDialog = DownloadDialog.newInstance(currentInfo); + downloadDialog.setVideoStreams(sortedVideoStreams); + downloadDialog.setAudioStreams(currentInfo.getAudioStreams()); + downloadDialog.setSelectedVideoStream(selectedVideoStreamIndex); - downloadDialog.show(activity.getSupportFragmentManager(), "downloadDialog"); - } catch (Exception e) { - Toast.makeText(activity, - R.string.could_not_setup_download_menu, - Toast.LENGTH_LONG).show(); - e.printStackTrace(); - } + downloadDialog.show(activity.getSupportFragmentManager(), "downloadDialog"); + } catch (Exception e) { + Toast.makeText(activity, + R.string.could_not_setup_download_menu, + Toast.LENGTH_LONG).show(); + e.printStackTrace(); + } } /*////////////////////////////////////////////////////////////////////////// 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..cd557c931 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 @@ -17,6 +17,7 @@ import android.view.View; import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.channel.ChannelInfoItem; +import org.schabi.newpipe.extractor.comments.CommentsInfoItem; import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem; import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.fragments.BaseStateFragment; @@ -181,6 +182,13 @@ public abstract class BaseListFragment extends BaseStateFragment implem } }); + infoListAdapter.setOnCommentsSelectedListener(new OnClickGesture() { + @Override + public void selected(CommentsInfoItem selectedItem) { + //Log.d("comments" , "this comment was clicked" + selectedItem.getCommentText()); + } + }); + itemsList.clearOnScrollListeners(); itemsList.addOnScrollListener(new OnScrollBelowItemsListener() { @Override diff --git a/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java b/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java index 78867c81f..dbb91bbb2 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java @@ -10,10 +10,13 @@ import com.nostra13.universalimageloader.core.ImageLoader; import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.channel.ChannelInfoItem; +import org.schabi.newpipe.extractor.comments.CommentsInfoItem; 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.CommentsInfoItemHolder; +import org.schabi.newpipe.info_list.holder.CommentsMiniInfoItemHolder; import org.schabi.newpipe.info_list.holder.InfoItemHolder; import org.schabi.newpipe.info_list.holder.PlaylistInfoItemHolder; import org.schabi.newpipe.info_list.holder.PlaylistMiniInfoItemHolder; @@ -50,6 +53,7 @@ public class InfoItemBuilder { private OnClickGesture onStreamSelectedListener; private OnClickGesture onChannelSelectedListener; private OnClickGesture onPlaylistSelectedListener; + private OnClickGesture onCommentsSelectedListener; public InfoItemBuilder(Context context) { this.context = context; @@ -73,6 +77,8 @@ public class InfoItemBuilder { return useMiniVariant ? new ChannelMiniInfoItemHolder(this, parent) : new ChannelInfoItemHolder(this, parent); case PLAYLIST: return useMiniVariant ? new PlaylistMiniInfoItemHolder(this, parent) : new PlaylistInfoItemHolder(this, parent); + case COMMENT: + return useMiniVariant ? new CommentsMiniInfoItemHolder(this, parent) : new CommentsInfoItemHolder(this, parent); default: Log.e(TAG, "Trollolo"); throw new RuntimeException("InfoType not expected = " + infoType.name()); @@ -111,4 +117,12 @@ public class InfoItemBuilder { this.onPlaylistSelectedListener = listener; } + public OnClickGesture getOnCommentsSelectedListener() { + return onCommentsSelectedListener; + } + + public void setOnCommentsSelectedListener(OnClickGesture onCommentsSelectedListener) { + this.onCommentsSelectedListener = onCommentsSelectedListener; + } + } 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..49bd2bdf1 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 @@ -8,6 +8,7 @@ import android.view.ViewGroup; import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.channel.ChannelInfoItem; +import org.schabi.newpipe.extractor.comments.CommentsInfoItem; import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem; import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.info_list.holder.ChannelInfoItemHolder; @@ -90,6 +91,10 @@ public class InfoListAdapter extends RecyclerView.Adapter listener) { + infoItemBuilder.setOnCommentsSelectedListener(listener); + } + public void useMiniItemVariants(boolean useMiniVariant) { this.useMiniVariant = useMiniVariant; } diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsInfoItemHolder.java new file mode 100644 index 000000000..46e4b4563 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsInfoItemHolder.java @@ -0,0 +1,53 @@ +package org.schabi.newpipe.info_list.holder; + +import android.view.ViewGroup; +import android.widget.TextView; + +import org.schabi.newpipe.R; +import org.schabi.newpipe.extractor.InfoItem; +import org.schabi.newpipe.extractor.channel.ChannelInfoItem; +import org.schabi.newpipe.extractor.comments.CommentsInfoItem; +import org.schabi.newpipe.info_list.InfoItemBuilder; +import org.schabi.newpipe.util.Localization; + +/* + * Created by Christian Schabesberger on 12.02.17. + * + * Copyright (C) Christian Schabesberger 2016 + * ChannelInfoItemHolder .java is part of NewPipe. + * + * NewPipe is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * NewPipe is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with NewPipe. If not, see . + */ + +public class CommentsInfoItemHolder extends CommentsMiniInfoItemHolder { + + public final TextView itemTitleView; + + public CommentsInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) { + super(infoItemBuilder, R.layout.list_comments_item, parent); + + itemTitleView = itemView.findViewById(R.id.itemTitleView); + } + + @Override + public void updateFromItem(final InfoItem infoItem) { + super.updateFromItem(infoItem); + + if (!(infoItem instanceof CommentsInfoItem)) return; + final CommentsInfoItem item = (CommentsInfoItem) infoItem; + + itemTitleView.setText(item.getAuthorName()); + } + +} diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java new file mode 100644 index 000000000..046cadc3f --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java @@ -0,0 +1,92 @@ +package org.schabi.newpipe.info_list.holder; + +import android.support.v7.app.AppCompatActivity; +import android.text.TextUtils; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import org.schabi.newpipe.R; +import org.schabi.newpipe.extractor.InfoItem; +import org.schabi.newpipe.extractor.comments.CommentsInfoItem; +import org.schabi.newpipe.info_list.InfoItemBuilder; +import org.schabi.newpipe.report.ErrorActivity; +import org.schabi.newpipe.util.ImageDisplayConstants; +import org.schabi.newpipe.util.Localization; +import org.schabi.newpipe.util.NavigationHelper; + +import de.hdodenhof.circleimageview.CircleImageView; + +public class CommentsMiniInfoItemHolder extends InfoItemHolder { + public final CircleImageView itemThumbnailView; + private final TextView itemContentView; + private final TextView itemLikesCountView; + private final TextView itemDislikesCountView; + + private static final int commentDefaultLines = 2; + private static final int commentExpandedLines = 1000; + + CommentsMiniInfoItemHolder(InfoItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) { + super(infoItemBuilder, layoutId, parent); + + itemThumbnailView = itemView.findViewById(R.id.itemThumbnailView); + itemContentView = itemView.findViewById(R.id.itemCommentContentView); + itemLikesCountView = itemView.findViewById(R.id.detail_thumbs_up_count_view); + itemDislikesCountView = itemView.findViewById(R.id.detail_thumbs_down_count_view); + } + + public CommentsMiniInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) { + this(infoItemBuilder, R.layout.list_comments_mini_item, parent); + } + + @Override + public void updateFromItem(final InfoItem infoItem) { + if (!(infoItem instanceof CommentsInfoItem)) return; + final CommentsInfoItem item = (CommentsInfoItem) infoItem; + + itemBuilder.getImageLoader() + .displayImage(item.getAuthorThumbnail(), + itemThumbnailView, + ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS); + + itemThumbnailView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + try { + final AppCompatActivity activity = (AppCompatActivity) itemBuilder.getContext(); + NavigationHelper.openChannelFragment( + activity.getSupportFragmentManager(), + item.getServiceId(), + item.getAuthorEndpoint(), + item.getAuthorName()); + } catch (Exception e) { + ErrorActivity.reportUiError((AppCompatActivity) itemBuilder.getContext(), e); + } + } + }); + + itemContentView.setText(item.getCommentText()); + if (null != item.getLikeCount()) { + itemLikesCountView.setText(String.valueOf(item.getLikeCount())); + } + + itemView.setOnClickListener(view -> { + toggleEllipsize(item.getCommentText()); + if (itemBuilder.getOnCommentsSelectedListener() != null) { + itemBuilder.getOnCommentsSelectedListener().selected(item); + } + }); + } + + private void toggleEllipsize(String text) { + // toggle ellipsize + if (null == itemContentView.getEllipsize()) { + itemContentView.setEllipsize(TextUtils.TruncateAt.END); + itemContentView.setMaxLines(commentDefaultLines); + } else { + itemContentView.setEllipsize(null); + itemContentView.setMaxLines(commentExpandedLines); + } + } +} diff --git a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java index e445233c3..42d746eae 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java @@ -59,7 +59,7 @@ public final class ExtractorHelper { } private static void checkServiceId(int serviceId) { - if(serviceId == Constants.NO_SERVICE_ID) { + if (serviceId == Constants.NO_SERVICE_ID) { throw new IllegalArgumentException("serviceId is NO_SERVICE_ID"); } } @@ -122,8 +122,8 @@ public final class ExtractorHelper { } public static Single getMoreChannelItems(final int serviceId, - final String url, - final String nextStreamsUrl) { + final String url, + final String nextStreamsUrl) { checkServiceId(serviceId); return Single.fromCallable(() -> ChannelInfo.getMoreItems(NewPipe.getService(serviceId), url, nextStreamsUrl)); @@ -138,8 +138,8 @@ public final class ExtractorHelper { } public static Single getMorePlaylistItems(final int serviceId, - final String url, - final String nextStreamsUrl) { + final String url, + final String nextStreamsUrl) { checkServiceId(serviceId); return Single.fromCallable(() -> PlaylistInfo.getMoreItems(NewPipe.getService(serviceId), url, nextStreamsUrl)); @@ -154,9 +154,9 @@ public final class ExtractorHelper { } public static Single getMoreKioskItems(final int serviceId, - final String url, - final String nextStreamsUrl, - final String contentCountry) { + final String url, + final String nextStreamsUrl, + final String contentCountry) { return Single.fromCallable(() -> KioskInfo.getMoreItems(NewPipe.getService(serviceId), url, nextStreamsUrl, contentCountry)); @@ -198,17 +198,17 @@ public final class ExtractorHelper { public static Maybe loadFromCache(final int serviceId, final String url) { checkServiceId(serviceId); return Maybe.defer(() -> { - //noinspection unchecked - I info = (I) cache.getFromKey(serviceId, url); - if (MainActivity.DEBUG) Log.d(TAG, "loadFromCache() called, info > " + info); + //noinspection unchecked + I info = (I) cache.getFromKey(serviceId, url); + if (MainActivity.DEBUG) Log.d(TAG, "loadFromCache() called, info > " + info); - // Only return info if it's not null (it is cached) - if (info != null) { - return Maybe.just(info); - } + // Only return info if it's not null (it is cached) + if (info != null) { + return Maybe.just(info); + } - return Maybe.empty(); - }); + return Maybe.empty(); + }); } /** diff --git a/app/src/main/res/layout/fragment_video_detail.xml b/app/src/main/res/layout/fragment_video_detail.xml index 7c6568b67..185f7d380 100644 --- a/app/src/main/res/layout/fragment_video_detail.xml +++ b/app/src/main/res/layout/fragment_video_detail.xml @@ -1,6 +1,5 @@ - + tools:src="@drawable/dummy_thumbnail" /> + tools:visibility="visible" /> + tools:visibility="visible" /> + tools:visibility="visible" /> @@ -127,7 +126,7 @@ android:textAppearance="?android:attr/textAppearanceLarge" android:textSize="@dimen/video_item_detail_title_text_size" tools:ignore="RtlHardcoded" - tools:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed a ultricies ex. Integer sit amet sodales risus. Duis non mi et urna pretium bibendum. Nunc eleifend est quis ipsum porttitor egestas. Sed facilisis, nisl quis eleifend pellentesque, orci metus egestas dolor, at accumsan eros metus quis libero."/> + tools:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed a ultricies ex. Integer sit amet sodales risus. Duis non mi et urna pretium bibendum. Nunc eleifend est quis ipsum porttitor egestas. Sed facilisis, nisl quis eleifend pellentesque, orci metus egestas dolor, at accumsan eros metus quis libero." /> + tools:ignore="ContentDescription,RtlHardcoded" /> @@ -150,7 +149,7 @@ android:layout_marginTop="@dimen/video_item_detail_error_panel_margin" android:indeterminate="true" android:visibility="gone" - tools:visibility="visible"/> + tools:visibility="visible" /> + tools:visibility="visible" /> + android:padding="6dp"> + tools:ignore="RtlHardcoded" /> + tools:text="Uploader" /> + tools:text="Published on Oct 2, 2009" /> + tools:text="Description Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed a ultricies ex. Integer sit amet sodales risus. Duis non mi et urna pretium bibendum." /> + android:background="?attr/separator_color" /> - - - - + - - - + android:orientation="vertical"> + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/list_comments_item.xml b/app/src/main/res/layout/list_comments_item.xml new file mode 100644 index 000000000..16b6107c5 --- /dev/null +++ b/app/src/main/res/layout/list_comments_item.xml @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/list_comments_mini_item.xml b/app/src/main/res/layout/list_comments_mini_item.xml new file mode 100644 index 000000000..7e8ce1a87 --- /dev/null +++ b/app/src/main/res/layout/list_comments_mini_item.xml @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml index 9b39fec26..b110a6aa3 100644 --- a/app/src/main/res/values/settings_keys.xml +++ b/app/src/main/res/values/settings_keys.xml @@ -135,6 +135,7 @@ show_search_suggestions show_play_with_kodi show_next_video + show_comments show_hold_to_append en GB diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0dc837ae8..64ff4ec74 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -129,6 +129,7 @@ Playlist Playlists Videos + Comments Tracks Users Yes