From 7047b62442320a64a164fd3392e36c5e828e8b85 Mon Sep 17 00:00:00 2001 From: Ritvik Saraf <13ritvik@gmail.com> Date: Sun, 23 Sep 2018 07:02:19 +0530 Subject: [PATCH] added comments fragment --- app/build.gradle | 2 +- .../fragments/detail/VideoDetailFragment.java | 173 ++++++++++----- .../list/comments/CommentsFragment.java | 201 ++++++++++++++++++ .../newpipe/info_list/InfoListAdapter.java | 13 +- .../org/schabi/newpipe/report/UserAction.java | 1 + .../schabi/newpipe/util/ExtractorHelper.java | 37 +++- .../org/schabi/newpipe/util/InfoCache.java | 17 +- .../schabi/newpipe/util/NavigationHelper.java | 13 ++ app/src/main/res/layout/fragment_comments.xml | 70 ++++++ .../main/res/layout/fragment_video_detail.xml | 1 + 10 files changed, 451 insertions(+), 77 deletions(-) create mode 100644 app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentsFragment.java create mode 100644 app/src/main/res/layout/fragment_comments.xml diff --git a/app/build.gradle b/app/build.gradle index c4a54f386..e4c1f24c1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -55,7 +55,7 @@ dependencies { exclude module: 'support-annotations' } - implementation 'com.github.yausername:NewPipeExtractor:e04787f' + implementation 'com.github.yausername:NewPipeExtractor:c1199c8' testImplementation 'junit:junit:4.12' testImplementation 'org.mockito:mockito-core:2.8.9' 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 90056c724..4357753b4 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 @@ -54,9 +54,12 @@ 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.ServiceList; +import org.schabi.newpipe.extractor.StreamingService; import org.schabi.newpipe.extractor.comments.CommentsInfo; import org.schabi.newpipe.extractor.comments.CommentsInfoItem; import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException; +import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.extractor.exceptions.ParsingException; import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubeStreamExtractor; import org.schabi.newpipe.extractor.stream.AudioStream; @@ -131,6 +134,7 @@ public class VideoDetailFragment private boolean autoPlayEnabled; private boolean showRelatedStreams; private boolean showComments; + private boolean isCommentsSupported; private boolean wasRelatedStreamsExpanded = false; @State @@ -141,6 +145,7 @@ public class VideoDetailFragment protected String url; private StreamInfo currentInfo; + private CommentsInfo commentsInfo; private Disposable currentWorker; @NonNull private CompositeDisposable disposables = new CompositeDisposable(); @@ -242,6 +247,7 @@ public class VideoDetailFragment public void onPause() { super.onPause(); if (currentWorker != null) currentWorker.dispose(); + if (commentsDisposable != null) commentsDisposable.dispose(); } @Override @@ -253,7 +259,7 @@ public class VideoDetailFragment 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.getCommentsInfo()); + if ((updateFlags & COMMENTS_UPDATE_FLAG) != 0) initComments(commentsInfo); } if ((updateFlags & TOOLBAR_ITEMS_UPDATE_FLAG) != 0 @@ -267,6 +273,8 @@ public class VideoDetailFragment // Check if it was loading when the fragment was stopped/paused, if (wasLoading.getAndSet(false)) { selectAndLoadVideo(serviceId, url, name); + }else{ + loadComments(false); } } @@ -277,8 +285,10 @@ public class VideoDetailFragment .unregisterOnSharedPreferenceChangeListener(this); if (currentWorker != null) currentWorker.dispose(); + if (commentsDisposable != null) commentsDisposable.dispose(); if (disposables != null) disposables.clear(); currentWorker = null; + commentsDisposable = null; disposables = null; } @@ -359,7 +369,7 @@ public class VideoDetailFragment if (serializable instanceof StreamInfo) { //noinspection unchecked currentInfo = (StreamInfo) serializable; - InfoCache.getInstance().putInfo(serviceId, url, currentInfo); + InfoCache.getInstance().putInfo(serviceId, url, currentInfo, InfoItem.InfoType.STREAM); } serializable = savedState.getSerializable(STACK_KEY); @@ -367,6 +377,7 @@ public class VideoDetailFragment //noinspection unchecked stack.addAll((Collection) serializable); } + } /*////////////////////////////////////////////////////////////////////////// @@ -425,7 +436,7 @@ public class VideoDetailFragment toggleExpandRelatedVideos(currentInfo); break; case R.id.detail_comments_expand: - toggleExpandComments(currentInfo.getCommentsInfo()); + toggleExpandComments(commentsInfo); break; } } @@ -493,40 +504,33 @@ public class VideoDetailFragment if (!showComments || null == info) return; int initialCount = INITIAL_COMMENTS; + int currentCount = commentsView.getChildCount(); - if (commentsView.getChildCount() > initialCount && commentsView.getChildCount() >= info.getComments().size() && !info.hasMoreComments()) { + //collapse + if (currentCount > initialCount && !info.hasNextPage()) { commentsView.removeViews(initialCount, - commentsView.getChildCount() - (initialCount)); + currentCount - (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))); + if(currentCount < info.getRelatedItems().size()){ + //expand + for (int i = currentCount; i < info.getRelatedItems().size(); i++) { + CommentsInfoItem item = info.getRelatedItems().get(i); + commentsView.addView(infoItemBuilder.buildView(commentsView, item)); + } + if(!info.hasNextPage()){ + commentsExpandButton.setImageDrawable( + ContextCompat.getDrawable(activity, + ThemeHelper.resolveResourceIdFromAttr(activity, R.attr.collapse))); + } + }else{ + NavigationHelper.openCommentsFragment(getFragmentManager(), serviceId, url, name); } } - private void loadMoreComments(CommentsInfo info) { - if (commentsDisposable != null) commentsDisposable.dispose(); - - commentsDisposable = Single.fromCallable(() -> { - CommentsInfo.loadMoreComments(info); - return info.getComments(); - }).subscribeOn(Schedulers.io()).doOnError(e -> info.addError(e)).subscribe(); - } /*////////////////////////////////////////////////////////////////////////// // Init //////////////////////////////////////////////////////////////////////////*/ @@ -573,23 +577,17 @@ 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(); + StreamingService service = null; + try { + service = NewPipe.getService(serviceId); + } catch (ExtractionException e) { + onError(e); + } - 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); + if (service.isCommentsSupported()) { + isCommentsSupported = true; + initTabs(rootView); + } relatedStreamRootLayout = rootView.findViewById(R.id.detail_related_streams_root_layout); nextStreamTitle = rootView.findViewById(R.id.detail_next_stream_title); @@ -606,6 +604,25 @@ public class VideoDetailFragment } + private void initTabs(View rootView) { + 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); + } + @Override protected void initListeners() { super.initListeners(); @@ -751,22 +768,25 @@ public class VideoDetailFragment } private void showRelatedStreamsIfSelected() { - if (tabHost.getCurrentTabTag().contentEquals(RELATED_TAB_TAG)) { + if (null == tabHost || tabHost.getCurrentTabTag().contentEquals(RELATED_TAB_TAG)) { relatedStreamRootLayout.setVisibility(View.VISIBLE); } } private void initComments(CommentsInfo info) { + if(null == info) return; + if (commentsView.getChildCount() > 0) commentsView.removeAllViews(); - if (null != info && info.getComments() != null - && !info.getComments().isEmpty() && showComments) { + List initialComments = info.getRelatedItems(); + if (null != info && initialComments != null + && !initialComments.isEmpty() && showComments) { //long first = System.nanoTime(), each; - int to = info.getComments().size() >= INITIAL_RELATED_VIDEOS - ? INITIAL_RELATED_VIDEOS - : info.getComments().size(); + int to = initialComments.size() >= INITIAL_COMMENTS + ? INITIAL_COMMENTS + : initialComments.size(); for (int i = 0; i < to; i++) { - InfoItem item = info.getComments().get(i); + InfoItem item = initialComments.get(i); //each = System.nanoTime(); commentsView.addView(infoItemBuilder.buildView(commentsView, item)); //if (DEBUG) Log.d(TAG, "each took " + ((System.nanoTime() - each) / 1000000L) + "ms"); @@ -774,10 +794,13 @@ public class VideoDetailFragment //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))); + if(initialComments.size() > INITIAL_COMMENTS){ + commentsExpandButton.setVisibility(View.VISIBLE); + commentsExpandButton.setImageDrawable(ContextCompat.getDrawable( + activity, ThemeHelper.resolveResourceIdFromAttr(activity, R.attr.expand))); + }else{ + commentsExpandButton.setVisibility(View.GONE); + } } else { commentsRootLayout.setVisibility(View.GONE); } @@ -785,11 +808,16 @@ public class VideoDetailFragment } private void showCommentsIfSelected() { - if (tabHost.getCurrentTabTag().contentEquals(COMMENTS_TAB_TAG)) { + if (null == tabHost || tabHost.getCurrentTabTag().contentEquals(COMMENTS_TAB_TAG)) { commentsRootLayout.setVisibility(View.VISIBLE); } } + private void clearComments(){ + if (commentsView.getChildCount() > 0) commentsView.removeAllViews(); + commentsExpandButton.setVisibility(View.GONE); + } + /*////////////////////////////////////////////////////////////////////////// // Menu //////////////////////////////////////////////////////////////////////////*/ @@ -988,6 +1016,7 @@ public class VideoDetailFragment protected void prepareAndLoadInfo() { parallaxScrollRootView.smoothScrollTo(0, 0); pushToStack(serviceId, url, name); + //clearComments(); startLoading(false); } @@ -1010,6 +1039,27 @@ public class VideoDetailFragment isLoading.set(false); onError(throwable); }); + + loadComments(forceLoad); + + } + + private void loadComments(boolean forceLoad) { + if(isCommentsSupported && showComments){ + commentsInfo = null; + if (commentsDisposable != null) commentsDisposable.dispose(); + + commentsDisposable = ExtractorHelper.getCommentsInfo(serviceId, url, forceLoad) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe((@NonNull CommentsInfo result) -> { + commentsInfo = result; + showCommentsWithAnimation(120, 0,0); + initComments(commentsInfo); + }, (@NonNull Throwable throwable) -> { + onError(throwable); + }); + } } /*////////////////////////////////////////////////////////////////////////// @@ -1199,7 +1249,7 @@ public class VideoDetailFragment .setInterpolator(new FastOutSlowInInterpolator()) .start(); - if (showRelatedStreams && tabHost.getCurrentTabTag().contentEquals(RELATED_TAB_TAG)) { + if (showRelatedStreams && (null == tabHost || tabHost.getCurrentTabTag().contentEquals(RELATED_TAB_TAG))) { relatedStreamRootLayout.animate().setListener(null).cancel(); relatedStreamRootLayout.setAlpha(0f); relatedStreamRootLayout.setTranslationY(translationY); @@ -1213,7 +1263,15 @@ public class VideoDetailFragment .start(); } - if (showComments && tabHost.getCurrentTabTag().contentEquals(COMMENTS_TAB_TAG)) { + } + + private void showCommentsWithAnimation(long duration, + long delay, + @FloatRange(from = 0.0f, to = 1.0f) float translationPercent) { + int translationY = (int) (getResources().getDisplayMetrics().heightPixels * + (translationPercent > 0.0f ? translationPercent : .06f)); + + if (showComments && (null == tabHost || tabHost.getCurrentTabTag().contentEquals(COMMENTS_TAB_TAG))) { commentsRootLayout.animate().setListener(null).cancel(); commentsRootLayout.setAlpha(0f); commentsRootLayout.setTranslationY(translationY); @@ -1360,7 +1418,6 @@ public class VideoDetailFragment setupActionBar(info); initThumbnailViews(info); initRelatedVideos(info); - initComments(info.getCommentsInfo()); if (wasRelatedStreamsExpanded) { toggleExpandRelatedVideos(currentInfo); diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentsFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentsFragment.java new file mode 100644 index 000000000..78787107a --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentsFragment.java @@ -0,0 +1,201 @@ +package org.schabi.newpipe.fragments.list.comments; + +import android.app.Activity; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.content.ContextCompat; +import android.support.v7.app.ActionBar; +import android.text.TextUtils; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.jakewharton.rxbinding2.view.RxView; + +import org.schabi.newpipe.R; +import org.schabi.newpipe.database.subscription.SubscriptionEntity; +import org.schabi.newpipe.extractor.InfoItem; +import org.schabi.newpipe.extractor.ListExtractor; +import org.schabi.newpipe.extractor.NewPipe; +import org.schabi.newpipe.extractor.channel.ChannelInfo; +import org.schabi.newpipe.extractor.comments.CommentsInfo; +import org.schabi.newpipe.extractor.exceptions.ExtractionException; +import org.schabi.newpipe.extractor.stream.StreamInfoItem; +import org.schabi.newpipe.fragments.list.BaseListInfoFragment; +import org.schabi.newpipe.info_list.InfoItemDialog; +import org.schabi.newpipe.local.dialog.PlaylistAppendDialog; +import org.schabi.newpipe.local.subscription.SubscriptionService; +import org.schabi.newpipe.player.playqueue.ChannelPlayQueue; +import org.schabi.newpipe.player.playqueue.PlayQueue; +import org.schabi.newpipe.player.playqueue.SinglePlayQueue; +import org.schabi.newpipe.report.UserAction; +import org.schabi.newpipe.util.AnimationUtils; +import org.schabi.newpipe.util.ExtractorHelper; +import org.schabi.newpipe.util.ImageDisplayConstants; +import org.schabi.newpipe.util.Localization; +import org.schabi.newpipe.util.NavigationHelper; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import io.reactivex.Observable; +import io.reactivex.Single; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.CompositeDisposable; +import io.reactivex.disposables.Disposable; +import io.reactivex.functions.Action; +import io.reactivex.functions.Consumer; +import io.reactivex.functions.Function; +import io.reactivex.schedulers.Schedulers; + +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 CommentsFragment extends BaseListInfoFragment { + + private CompositeDisposable disposables = new CompositeDisposable(); + + /*////////////////////////////////////////////////////////////////////////// + // Views + //////////////////////////////////////////////////////////////////////////*/ + + + + private boolean mIsVisibleToUser = false; + + public static CommentsFragment getInstance(int serviceId, String url, String name) { + CommentsFragment instance = new CommentsFragment(); + instance.setInitialData(serviceId, url, name); + return instance; + } + + /*////////////////////////////////////////////////////////////////////////// + // LifeCycle + //////////////////////////////////////////////////////////////////////////*/ + + @Override + public void setUserVisibleHint(boolean isVisibleToUser) { + super.setUserVisibleHint(isVisibleToUser); + mIsVisibleToUser = isVisibleToUser; + if(activity != null + && useAsFrontPage + && isVisibleToUser) { + setTitle(currentInfo != null ? currentInfo.getName() : name); + } + } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + } + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_comments, container, false); + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (disposables != null) disposables.clear(); + } + + /*////////////////////////////////////////////////////////////////////////// + // Init + //////////////////////////////////////////////////////////////////////////*/ + + + /*////////////////////////////////////////////////////////////////////////// + // Menu + //////////////////////////////////////////////////////////////////////////*/ + + + /*////////////////////////////////////////////////////////////////////////// + // Load and handle + //////////////////////////////////////////////////////////////////////////*/ + + @Override + protected Single loadMoreItemsLogic() { + return ExtractorHelper.getMoreCommentItems(serviceId, currentInfo, currentNextPageUrl); + } + + @Override + protected Single loadResult(boolean forceLoad) { + return ExtractorHelper.getCommentsInfo(serviceId, url, forceLoad); + } + + /*////////////////////////////////////////////////////////////////////////// + // Contract + //////////////////////////////////////////////////////////////////////////*/ + + @Override + public void showLoading() { + super.showLoading(); + } + + @Override + public void handleResult(@NonNull CommentsInfo result) { + super.handleResult(result); + + if (!result.getErrors().isEmpty()) { + showSnackBarError(result.getErrors(), UserAction.REQUESTED_COMMENTS, NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0); + } + + if (disposables != null) disposables.clear(); + } + + @Override + public void handleNextItems(ListExtractor.InfoItemsPage result) { + super.handleNextItems(result); + + if (!result.getErrors().isEmpty()) { + showSnackBarError(result.getErrors(), + UserAction.REQUESTED_COMMENTS, + NewPipe.getNameOfService(serviceId), + "Get next page of: " + url, + R.string.general_error); + } + } + + /*////////////////////////////////////////////////////////////////////////// + // OnError + //////////////////////////////////////////////////////////////////////////*/ + + @Override + protected boolean onError(Throwable exception) { + if (super.onError(exception)) return true; + + int errorId = exception instanceof ExtractionException ? R.string.parsing_error : R.string.general_error; + onUnrecoverableError(exception, + UserAction.REQUESTED_COMMENTS, + NewPipe.getNameOfService(serviceId), + url, + errorId); + return true; + } + + /*////////////////////////////////////////////////////////////////////////// + // Utils + //////////////////////////////////////////////////////////////////////////*/ + + @Override + public void setTitle(String title) { + super.setTitle(title); + } +} 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 49bd2bdf1..64abf67f8 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 @@ -13,6 +13,8 @@ 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; @@ -57,6 +59,8 @@ public class InfoListAdapter extends RecyclerView.Adapter infoItemList; @@ -216,6 +220,8 @@ public class InfoListAdapter extends RecyclerView.Adapter + return checkCache(forceLoad, serviceId, url, InfoItem.InfoType.STREAM, Single.fromCallable(() -> StreamInfo.getInfo(NewPipe.getService(serviceId), url))); } @@ -117,7 +119,7 @@ public final class ExtractorHelper { final String url, boolean forceLoad) { checkServiceId(serviceId); - return checkCache(forceLoad, serviceId, url, Single.fromCallable(() -> + return checkCache(forceLoad, serviceId, url, InfoItem.InfoType.CHANNEL, Single.fromCallable(() -> ChannelInfo.getInfo(NewPipe.getService(serviceId), url))); } @@ -129,11 +131,27 @@ public final class ExtractorHelper { ChannelInfo.getMoreItems(NewPipe.getService(serviceId), url, nextStreamsUrl)); } + public static Single getCommentsInfo(final int serviceId, + final String url, + boolean forceLoad) { + checkServiceId(serviceId); + return checkCache(forceLoad, serviceId, url, InfoItem.InfoType.COMMENT, Single.fromCallable(() -> + CommentsInfo.getInfo(NewPipe.getService(serviceId), url))); + } + + public static Single getMoreCommentItems(final int serviceId, + final CommentsInfo info, + final String nextPageUrl) { + checkServiceId(serviceId); + return Single.fromCallable(() -> + CommentsInfo.getMoreItems(NewPipe.getService(serviceId), info, nextPageUrl)); + } + public static Single getPlaylistInfo(final int serviceId, final String url, boolean forceLoad) { checkServiceId(serviceId); - return checkCache(forceLoad, serviceId, url, Single.fromCallable(() -> + return checkCache(forceLoad, serviceId, url, InfoItem.InfoType.PLAYLIST, Single.fromCallable(() -> PlaylistInfo.getInfo(NewPipe.getService(serviceId), url))); } @@ -149,7 +167,7 @@ public final class ExtractorHelper { final String url, final String contentCountry, boolean forceLoad) { - return checkCache(forceLoad, serviceId, url, Single.fromCallable(() -> + return checkCache(forceLoad, serviceId, url, InfoItem.InfoType.PLAYLIST, Single.fromCallable(() -> KioskInfo.getInfo(NewPipe.getService(serviceId), url, contentCountry))); } @@ -174,16 +192,17 @@ public final class ExtractorHelper { private static Single checkCache(boolean forceLoad, int serviceId, String url, + InfoItem.InfoType infoType, Single loadFromNetwork) { checkServiceId(serviceId); - loadFromNetwork = loadFromNetwork.doOnSuccess(info -> cache.putInfo(serviceId, url, info)); + loadFromNetwork = loadFromNetwork.doOnSuccess(info -> cache.putInfo(serviceId, url, info, infoType)); Single load; if (forceLoad) { - cache.removeInfo(serviceId, url); + cache.removeInfo(serviceId, url, infoType); load = loadFromNetwork; } else { - load = Maybe.concat(ExtractorHelper.loadFromCache(serviceId, url), + load = Maybe.concat(ExtractorHelper.loadFromCache(serviceId, url, infoType), loadFromNetwork.toMaybe()) .firstElement() //Take the first valid .toSingle(); @@ -195,11 +214,11 @@ public final class ExtractorHelper { /** * Default implementation uses the {@link InfoCache} to get cached results */ - public static Maybe loadFromCache(final int serviceId, final String url) { + public static Maybe loadFromCache(final int serviceId, final String url, InfoItem.InfoType infoType) { checkServiceId(serviceId); return Maybe.defer(() -> { //noinspection unchecked - I info = (I) cache.getFromKey(serviceId, url); + I info = (I) cache.getFromKey(serviceId, url, infoType); if (MainActivity.DEBUG) Log.d(TAG, "loadFromCache() called, info > " + info); // Only return info if it's not null (it is cached) diff --git a/app/src/main/java/org/schabi/newpipe/util/InfoCache.java b/app/src/main/java/org/schabi/newpipe/util/InfoCache.java index ecc66bb40..82f64a5ff 100644 --- a/app/src/main/java/org/schabi/newpipe/util/InfoCache.java +++ b/app/src/main/java/org/schabi/newpipe/util/InfoCache.java @@ -26,6 +26,7 @@ import android.util.Log; import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.extractor.Info; +import org.schabi.newpipe.extractor.InfoItem; import java.util.Map; import java.util.concurrent.TimeUnit; @@ -55,27 +56,27 @@ public final class InfoCache { } @Nullable - public Info getFromKey(int serviceId, @NonNull String url) { + public Info getFromKey(int serviceId, @NonNull String url, @NonNull InfoItem.InfoType infoType) { if (DEBUG) Log.d(TAG, "getFromKey() called with: serviceId = [" + serviceId + "], url = [" + url + "]"); synchronized (lruCache) { - return getInfo(lruCache, keyOf(serviceId, url)); + return getInfo(lruCache, keyOf(serviceId, url, infoType)); } } - public void putInfo(int serviceId, @NonNull String url, @NonNull Info info) { + public void putInfo(int serviceId, @NonNull String url, @NonNull Info info, @NonNull InfoItem.InfoType infoType) { if (DEBUG) Log.d(TAG, "putInfo() called with: info = [" + info + "]"); final long expirationMillis = ServiceHelper.getCacheExpirationMillis(info.getServiceId()); synchronized (lruCache) { final CacheData data = new CacheData(info, expirationMillis); - lruCache.put(keyOf(serviceId, url), data); + lruCache.put(keyOf(serviceId, url, infoType), data); } } - public void removeInfo(int serviceId, @NonNull String url) { + public void removeInfo(int serviceId, @NonNull String url, @NonNull InfoItem.InfoType infoType) { if (DEBUG) Log.d(TAG, "removeInfo() called with: serviceId = [" + serviceId + "], url = [" + url + "]"); synchronized (lruCache) { - lruCache.remove(keyOf(serviceId, url)); + lruCache.remove(keyOf(serviceId, url, infoType)); } } @@ -101,8 +102,8 @@ public final class InfoCache { } @NonNull - private static String keyOf(final int serviceId, @NonNull final String url) { - return serviceId + url; + private static String keyOf(final int serviceId, @NonNull final String url, @NonNull InfoItem.InfoType infoType) { + return serviceId + url + infoType.toString(); } private static void removeStaleCache(@NonNull final LruCache cache) { diff --git a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java index 13767125d..27cf7bf71 100644 --- a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java @@ -34,6 +34,7 @@ import org.schabi.newpipe.extractor.stream.VideoStream; import org.schabi.newpipe.fragments.MainFragment; import org.schabi.newpipe.fragments.detail.VideoDetailFragment; import org.schabi.newpipe.fragments.list.channel.ChannelFragment; +import org.schabi.newpipe.fragments.list.comments.CommentsFragment; import org.schabi.newpipe.local.bookmark.BookmarkFragment; import org.schabi.newpipe.local.feed.FeedFragment; import org.schabi.newpipe.fragments.list.kiosk.KioskFragment; @@ -331,6 +332,18 @@ public class NavigationHelper { .commit(); } + public static void openCommentsFragment( + FragmentManager fragmentManager, + int serviceId, + String url, + String name) { + if (name == null) name = ""; + defaultTransaction(fragmentManager) + .replace(R.id.fragment_holder, CommentsFragment.getInstance(serviceId, url, name)) + .addToBackStack(null) + .commit(); + } + public static void openPlaylistFragment(FragmentManager fragmentManager, int serviceId, String url, diff --git a/app/src/main/res/layout/fragment_comments.xml b/app/src/main/res/layout/fragment_comments.xml new file mode 100644 index 000000000..97ad5c247 --- /dev/null +++ b/app/src/main/res/layout/fragment_comments.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_video_detail.xml b/app/src/main/res/layout/fragment_video_detail.xml index 185f7d380..c59c4ac11 100644 --- a/app/src/main/res/layout/fragment_video_detail.xml +++ b/app/src/main/res/layout/fragment_video_detail.xml @@ -495,6 +495,7 @@ android:src="?attr/expand" android:textAlignment="center" android:textAllCaps="true" + android:visibility="gone" tools:ignore="ContentDescription" />