mirror of
				https://github.com/TeamNewPipe/NewPipe
				synced 2025-10-31 07:13:00 +00:00 
			
		
		
		
	added comments fragment
This commit is contained in:
		| @@ -55,7 +55,7 @@ dependencies { | |||||||
|         exclude module: 'support-annotations' |         exclude module: 'support-annotations' | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     implementation 'com.github.yausername:NewPipeExtractor:e04787f' |     implementation 'com.github.yausername:NewPipeExtractor:c1199c8' | ||||||
|  |  | ||||||
|     testImplementation 'junit:junit:4.12' |     testImplementation 'junit:junit:4.12' | ||||||
|     testImplementation 'org.mockito:mockito-core:2.8.9' |     testImplementation 'org.mockito:mockito-core:2.8.9' | ||||||
|   | |||||||
| @@ -54,9 +54,12 @@ import org.schabi.newpipe.ReCaptchaActivity; | |||||||
| import org.schabi.newpipe.download.DownloadDialog; | import org.schabi.newpipe.download.DownloadDialog; | ||||||
| import org.schabi.newpipe.extractor.InfoItem; | import org.schabi.newpipe.extractor.InfoItem; | ||||||
| import org.schabi.newpipe.extractor.NewPipe; | 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.CommentsInfo; | ||||||
| import org.schabi.newpipe.extractor.comments.CommentsInfoItem; | import org.schabi.newpipe.extractor.comments.CommentsInfoItem; | ||||||
| import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException; | 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.exceptions.ParsingException; | ||||||
| import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubeStreamExtractor; | import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubeStreamExtractor; | ||||||
| import org.schabi.newpipe.extractor.stream.AudioStream; | import org.schabi.newpipe.extractor.stream.AudioStream; | ||||||
| @@ -131,6 +134,7 @@ public class VideoDetailFragment | |||||||
|     private boolean autoPlayEnabled; |     private boolean autoPlayEnabled; | ||||||
|     private boolean showRelatedStreams; |     private boolean showRelatedStreams; | ||||||
|     private boolean showComments; |     private boolean showComments; | ||||||
|  |     private boolean isCommentsSupported; | ||||||
|     private boolean wasRelatedStreamsExpanded = false; |     private boolean wasRelatedStreamsExpanded = false; | ||||||
|  |  | ||||||
|     @State |     @State | ||||||
| @@ -141,6 +145,7 @@ public class VideoDetailFragment | |||||||
|     protected String url; |     protected String url; | ||||||
|  |  | ||||||
|     private StreamInfo currentInfo; |     private StreamInfo currentInfo; | ||||||
|  |     private CommentsInfo commentsInfo; | ||||||
|     private Disposable currentWorker; |     private Disposable currentWorker; | ||||||
|     @NonNull |     @NonNull | ||||||
|     private CompositeDisposable disposables = new CompositeDisposable(); |     private CompositeDisposable disposables = new CompositeDisposable(); | ||||||
| @@ -242,6 +247,7 @@ public class VideoDetailFragment | |||||||
|     public void onPause() { |     public void onPause() { | ||||||
|         super.onPause(); |         super.onPause(); | ||||||
|         if (currentWorker != null) currentWorker.dispose(); |         if (currentWorker != null) currentWorker.dispose(); | ||||||
|  |         if (commentsDisposable != null) commentsDisposable.dispose(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
| @@ -253,7 +259,7 @@ public class VideoDetailFragment | |||||||
|                 if ((updateFlags & RELATED_STREAMS_UPDATE_FLAG) != 0) |                 if ((updateFlags & RELATED_STREAMS_UPDATE_FLAG) != 0) | ||||||
|                     initRelatedVideos(currentInfo); |                     initRelatedVideos(currentInfo); | ||||||
|                 if ((updateFlags & RESOLUTIONS_MENU_UPDATE_FLAG) != 0) setupActionBar(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 |             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, |         // Check if it was loading when the fragment was stopped/paused, | ||||||
|         if (wasLoading.getAndSet(false)) { |         if (wasLoading.getAndSet(false)) { | ||||||
|             selectAndLoadVideo(serviceId, url, name); |             selectAndLoadVideo(serviceId, url, name); | ||||||
|  |         }else{ | ||||||
|  |             loadComments(false); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -277,8 +285,10 @@ public class VideoDetailFragment | |||||||
|                 .unregisterOnSharedPreferenceChangeListener(this); |                 .unregisterOnSharedPreferenceChangeListener(this); | ||||||
|  |  | ||||||
|         if (currentWorker != null) currentWorker.dispose(); |         if (currentWorker != null) currentWorker.dispose(); | ||||||
|  |         if (commentsDisposable != null) commentsDisposable.dispose(); | ||||||
|         if (disposables != null) disposables.clear(); |         if (disposables != null) disposables.clear(); | ||||||
|         currentWorker = null; |         currentWorker = null; | ||||||
|  |         commentsDisposable = null; | ||||||
|         disposables = null; |         disposables = null; | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -359,7 +369,7 @@ public class VideoDetailFragment | |||||||
|         if (serializable instanceof StreamInfo) { |         if (serializable instanceof StreamInfo) { | ||||||
|             //noinspection unchecked |             //noinspection unchecked | ||||||
|             currentInfo = (StreamInfo) serializable; |             currentInfo = (StreamInfo) serializable; | ||||||
|             InfoCache.getInstance().putInfo(serviceId, url, currentInfo); |             InfoCache.getInstance().putInfo(serviceId, url, currentInfo, InfoItem.InfoType.STREAM); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         serializable = savedState.getSerializable(STACK_KEY); |         serializable = savedState.getSerializable(STACK_KEY); | ||||||
| @@ -367,6 +377,7 @@ public class VideoDetailFragment | |||||||
|             //noinspection unchecked |             //noinspection unchecked | ||||||
|             stack.addAll((Collection<? extends StackItem>) serializable); |             stack.addAll((Collection<? extends StackItem>) serializable); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /*////////////////////////////////////////////////////////////////////////// |     /*////////////////////////////////////////////////////////////////////////// | ||||||
| @@ -425,7 +436,7 @@ public class VideoDetailFragment | |||||||
|                 toggleExpandRelatedVideos(currentInfo); |                 toggleExpandRelatedVideos(currentInfo); | ||||||
|                 break; |                 break; | ||||||
|             case R.id.detail_comments_expand: |             case R.id.detail_comments_expand: | ||||||
|                 toggleExpandComments(currentInfo.getCommentsInfo()); |                 toggleExpandComments(commentsInfo); | ||||||
|                 break; |                 break; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @@ -493,40 +504,33 @@ public class VideoDetailFragment | |||||||
|         if (!showComments || null == info) return; |         if (!showComments || null == info) return; | ||||||
|  |  | ||||||
|         int initialCount = INITIAL_COMMENTS; |         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.removeViews(initialCount, | ||||||
|                     commentsView.getChildCount() - (initialCount)); |                     currentCount - (initialCount)); | ||||||
|             commentsExpandButton.setImageDrawable(ContextCompat.getDrawable( |             commentsExpandButton.setImageDrawable(ContextCompat.getDrawable( | ||||||
|                     activity, ThemeHelper.resolveResourceIdFromAttr(activity, R.attr.expand))); |                     activity, ThemeHelper.resolveResourceIdFromAttr(activity, R.attr.expand))); | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         //Log.d(TAG, "toggleExpandRelatedVideos() called with: info = [" + info + "], from = [" + INITIAL_RELATED_VIDEOS + "]"); |         if(currentCount < info.getRelatedItems().size()){ | ||||||
|         int currentCount = commentsView.getChildCount(); |             //expand | ||||||
|         for (int i = currentCount; i < info.getComments().size(); i++) { |             for (int i = currentCount; i < info.getRelatedItems().size(); i++) { | ||||||
|             CommentsInfoItem item = info.getComments().get(i); |                 CommentsInfoItem item = info.getRelatedItems().get(i); | ||||||
|             //Log.d(TAG, "i = " + i); |                 commentsView.addView(infoItemBuilder.buildView(commentsView, item)); | ||||||
|             commentsView.addView(infoItemBuilder.buildView(commentsView, item)); |             } | ||||||
|         } |             if(!info.hasNextPage()){ | ||||||
|  |                 commentsExpandButton.setImageDrawable( | ||||||
|         if (info.hasMoreComments()) { |                         ContextCompat.getDrawable(activity, | ||||||
|             loadMoreComments(info); |                                 ThemeHelper.resolveResourceIdFromAttr(activity, R.attr.collapse))); | ||||||
|         } else { |             } | ||||||
|             commentsExpandButton.setImageDrawable( |         }else{ | ||||||
|                     ContextCompat.getDrawable(activity, |             NavigationHelper.openCommentsFragment(getFragmentManager(), serviceId, url, name); | ||||||
|                             ThemeHelper.resolveResourceIdFromAttr(activity, R.attr.collapse))); |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     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 |     // Init | ||||||
|     //////////////////////////////////////////////////////////////////////////*/ |     //////////////////////////////////////////////////////////////////////////*/ | ||||||
| @@ -573,23 +577,17 @@ public class VideoDetailFragment | |||||||
|         uploaderTextView = rootView.findViewById(R.id.detail_uploader_text_view); |         uploaderTextView = rootView.findViewById(R.id.detail_uploader_text_view); | ||||||
|         uploaderThumb = rootView.findViewById(R.id.detail_uploader_thumbnail_view); |         uploaderThumb = rootView.findViewById(R.id.detail_uploader_thumbnail_view); | ||||||
|  |  | ||||||
|         tabHost = (TabHost) rootView.findViewById(R.id.tab_host); |         StreamingService service = null; | ||||||
|         tabHost.setup(); |         try { | ||||||
|  |             service = NewPipe.getService(serviceId); | ||||||
|  |         } catch (ExtractionException e) { | ||||||
|  |             onError(e); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         TabHost.TabSpec commentsTab = tabHost.newTabSpec(COMMENTS_TAB_TAG); |         if (service.isCommentsSupported()) { | ||||||
|         commentsTab.setContent(R.id.detail_comments_root_layout); |             isCommentsSupported = true; | ||||||
|         commentsTab.setIndicator(getString(R.string.comments)); |             initTabs(rootView); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         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); |         relatedStreamRootLayout = rootView.findViewById(R.id.detail_related_streams_root_layout); | ||||||
|         nextStreamTitle = rootView.findViewById(R.id.detail_next_stream_title); |         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 |     @Override | ||||||
|     protected void initListeners() { |     protected void initListeners() { | ||||||
|         super.initListeners(); |         super.initListeners(); | ||||||
| @@ -751,22 +768,25 @@ public class VideoDetailFragment | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     private void showRelatedStreamsIfSelected() { |     private void showRelatedStreamsIfSelected() { | ||||||
|         if (tabHost.getCurrentTabTag().contentEquals(RELATED_TAB_TAG)) { |         if (null == tabHost || tabHost.getCurrentTabTag().contentEquals(RELATED_TAB_TAG)) { | ||||||
|             relatedStreamRootLayout.setVisibility(View.VISIBLE); |             relatedStreamRootLayout.setVisibility(View.VISIBLE); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private void initComments(CommentsInfo info) { |     private void initComments(CommentsInfo info) { | ||||||
|  |         if(null == info) return; | ||||||
|  |  | ||||||
|         if (commentsView.getChildCount() > 0) commentsView.removeAllViews(); |         if (commentsView.getChildCount() > 0) commentsView.removeAllViews(); | ||||||
|  |  | ||||||
|         if (null != info && info.getComments() != null |         List<CommentsInfoItem> initialComments = info.getRelatedItems(); | ||||||
|                 && !info.getComments().isEmpty() && showComments) { |         if (null != info && initialComments != null | ||||||
|  |                 && !initialComments.isEmpty() && showComments) { | ||||||
|             //long first = System.nanoTime(), each; |             //long first = System.nanoTime(), each; | ||||||
|             int to = info.getComments().size() >= INITIAL_RELATED_VIDEOS |             int to = initialComments.size() >= INITIAL_COMMENTS | ||||||
|                     ? INITIAL_RELATED_VIDEOS |                     ? INITIAL_COMMENTS | ||||||
|                     : info.getComments().size(); |                     : initialComments.size(); | ||||||
|             for (int i = 0; i < to; i++) { |             for (int i = 0; i < to; i++) { | ||||||
|                 InfoItem item = info.getComments().get(i); |                 InfoItem item = initialComments.get(i); | ||||||
|                 //each = System.nanoTime(); |                 //each = System.nanoTime(); | ||||||
|                 commentsView.addView(infoItemBuilder.buildView(commentsView, item)); |                 commentsView.addView(infoItemBuilder.buildView(commentsView, item)); | ||||||
|                 //if (DEBUG) Log.d(TAG, "each took " + ((System.nanoTime() - each) / 1000000L) + "ms"); |                 //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"); |             //if (DEBUG) Log.d(TAG, "Total time " + ((System.nanoTime() - first) / 1000000L) + "ms"); | ||||||
|  |  | ||||||
|             showCommentsIfSelected(); |             showCommentsIfSelected(); | ||||||
|             commentsExpandButton.setVisibility(View.VISIBLE); |             if(initialComments.size() > INITIAL_COMMENTS){ | ||||||
|  |                 commentsExpandButton.setVisibility(View.VISIBLE); | ||||||
|             commentsExpandButton.setImageDrawable(ContextCompat.getDrawable( |                 commentsExpandButton.setImageDrawable(ContextCompat.getDrawable( | ||||||
|                     activity, ThemeHelper.resolveResourceIdFromAttr(activity, R.attr.expand))); |                         activity, ThemeHelper.resolveResourceIdFromAttr(activity, R.attr.expand))); | ||||||
|  |             }else{ | ||||||
|  |                 commentsExpandButton.setVisibility(View.GONE); | ||||||
|  |             } | ||||||
|         } else { |         } else { | ||||||
|             commentsRootLayout.setVisibility(View.GONE); |             commentsRootLayout.setVisibility(View.GONE); | ||||||
|         } |         } | ||||||
| @@ -785,11 +808,16 @@ public class VideoDetailFragment | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     private void showCommentsIfSelected() { |     private void showCommentsIfSelected() { | ||||||
|         if (tabHost.getCurrentTabTag().contentEquals(COMMENTS_TAB_TAG)) { |         if (null == tabHost || tabHost.getCurrentTabTag().contentEquals(COMMENTS_TAB_TAG)) { | ||||||
|             commentsRootLayout.setVisibility(View.VISIBLE); |             commentsRootLayout.setVisibility(View.VISIBLE); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     private void clearComments(){ | ||||||
|  |         if (commentsView.getChildCount() > 0) commentsView.removeAllViews(); | ||||||
|  |         commentsExpandButton.setVisibility(View.GONE); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     /*////////////////////////////////////////////////////////////////////////// |     /*////////////////////////////////////////////////////////////////////////// | ||||||
|     // Menu |     // Menu | ||||||
|     //////////////////////////////////////////////////////////////////////////*/ |     //////////////////////////////////////////////////////////////////////////*/ | ||||||
| @@ -988,6 +1016,7 @@ public class VideoDetailFragment | |||||||
|     protected void prepareAndLoadInfo() { |     protected void prepareAndLoadInfo() { | ||||||
|         parallaxScrollRootView.smoothScrollTo(0, 0); |         parallaxScrollRootView.smoothScrollTo(0, 0); | ||||||
|         pushToStack(serviceId, url, name); |         pushToStack(serviceId, url, name); | ||||||
|  |         //clearComments(); | ||||||
|         startLoading(false); |         startLoading(false); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -1010,6 +1039,27 @@ public class VideoDetailFragment | |||||||
|                     isLoading.set(false); |                     isLoading.set(false); | ||||||
|                     onError(throwable); |                     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()) |                 .setInterpolator(new FastOutSlowInInterpolator()) | ||||||
|                 .start(); |                 .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.animate().setListener(null).cancel(); | ||||||
|             relatedStreamRootLayout.setAlpha(0f); |             relatedStreamRootLayout.setAlpha(0f); | ||||||
|             relatedStreamRootLayout.setTranslationY(translationY); |             relatedStreamRootLayout.setTranslationY(translationY); | ||||||
| @@ -1213,7 +1263,15 @@ public class VideoDetailFragment | |||||||
|                     .start(); |                     .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.animate().setListener(null).cancel(); | ||||||
|             commentsRootLayout.setAlpha(0f); |             commentsRootLayout.setAlpha(0f); | ||||||
|             commentsRootLayout.setTranslationY(translationY); |             commentsRootLayout.setTranslationY(translationY); | ||||||
| @@ -1360,7 +1418,6 @@ public class VideoDetailFragment | |||||||
|         setupActionBar(info); |         setupActionBar(info); | ||||||
|         initThumbnailViews(info); |         initThumbnailViews(info); | ||||||
|         initRelatedVideos(info); |         initRelatedVideos(info); | ||||||
|         initComments(info.getCommentsInfo()); |  | ||||||
|  |  | ||||||
|         if (wasRelatedStreamsExpanded) { |         if (wasRelatedStreamsExpanded) { | ||||||
|             toggleExpandRelatedVideos(currentInfo); |             toggleExpandRelatedVideos(currentInfo); | ||||||
|   | |||||||
| @@ -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<CommentsInfo> { | ||||||
|  |  | ||||||
|  |     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<ListExtractor.InfoItemsPage> loadMoreItemsLogic() { | ||||||
|  |         return ExtractorHelper.getMoreCommentItems(serviceId, currentInfo, currentNextPageUrl); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     protected Single<CommentsInfo> 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); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -13,6 +13,8 @@ import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem; | |||||||
| import org.schabi.newpipe.extractor.stream.StreamInfoItem; | import org.schabi.newpipe.extractor.stream.StreamInfoItem; | ||||||
| import org.schabi.newpipe.info_list.holder.ChannelInfoItemHolder; | import org.schabi.newpipe.info_list.holder.ChannelInfoItemHolder; | ||||||
| import org.schabi.newpipe.info_list.holder.ChannelMiniInfoItemHolder; | 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.InfoItemHolder; | ||||||
| import org.schabi.newpipe.info_list.holder.PlaylistInfoItemHolder; | import org.schabi.newpipe.info_list.holder.PlaylistInfoItemHolder; | ||||||
| import org.schabi.newpipe.info_list.holder.PlaylistMiniInfoItemHolder; | import org.schabi.newpipe.info_list.holder.PlaylistMiniInfoItemHolder; | ||||||
| @@ -57,6 +59,8 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde | |||||||
|     private static final int CHANNEL_HOLDER_TYPE = 0x201; |     private static final int CHANNEL_HOLDER_TYPE = 0x201; | ||||||
|     private static final int MINI_PLAYLIST_HOLDER_TYPE = 0x300; |     private static final int MINI_PLAYLIST_HOLDER_TYPE = 0x300; | ||||||
|     private static final int PLAYLIST_HOLDER_TYPE = 0x301; |     private static final int PLAYLIST_HOLDER_TYPE = 0x301; | ||||||
|  |     private static final int MINI_COMMENT_HOLDER_TYPE = 0x400; | ||||||
|  |     private static final int COMMENT_HOLDER_TYPE = 0x401; | ||||||
|  |  | ||||||
|     private final InfoItemBuilder infoItemBuilder; |     private final InfoItemBuilder infoItemBuilder; | ||||||
|     private final ArrayList<InfoItem> infoItemList; |     private final ArrayList<InfoItem> infoItemList; | ||||||
| @@ -216,6 +220,8 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde | |||||||
|                 return useMiniVariant ? MINI_CHANNEL_HOLDER_TYPE : CHANNEL_HOLDER_TYPE; |                 return useMiniVariant ? MINI_CHANNEL_HOLDER_TYPE : CHANNEL_HOLDER_TYPE; | ||||||
|             case PLAYLIST: |             case PLAYLIST: | ||||||
|                 return useMiniVariant ? MINI_PLAYLIST_HOLDER_TYPE : PLAYLIST_HOLDER_TYPE; |                 return useMiniVariant ? MINI_PLAYLIST_HOLDER_TYPE : PLAYLIST_HOLDER_TYPE; | ||||||
|  |             case COMMENT: | ||||||
|  |                 return useMiniVariant ? MINI_COMMENT_HOLDER_TYPE : COMMENT_HOLDER_TYPE; | ||||||
|             default: |             default: | ||||||
|                 Log.e(TAG, "Trollolo"); |                 Log.e(TAG, "Trollolo"); | ||||||
|                 return -1; |                 return -1; | ||||||
| @@ -224,7 +230,8 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde | |||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int type) { |     public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int type) { | ||||||
|         if (DEBUG) Log.d(TAG, "onCreateViewHolder() called with: parent = [" + parent + "], type = [" + type + "]"); |         if (DEBUG) | ||||||
|  |             Log.d(TAG, "onCreateViewHolder() called with: parent = [" + parent + "], type = [" + type + "]"); | ||||||
|         switch (type) { |         switch (type) { | ||||||
|             case HEADER_TYPE: |             case HEADER_TYPE: | ||||||
|                 return new HFHolder(header); |                 return new HFHolder(header); | ||||||
| @@ -242,6 +249,10 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde | |||||||
|                 return new PlaylistMiniInfoItemHolder(infoItemBuilder, parent); |                 return new PlaylistMiniInfoItemHolder(infoItemBuilder, parent); | ||||||
|             case PLAYLIST_HOLDER_TYPE: |             case PLAYLIST_HOLDER_TYPE: | ||||||
|                 return new PlaylistInfoItemHolder(infoItemBuilder, parent); |                 return new PlaylistInfoItemHolder(infoItemBuilder, parent); | ||||||
|  |             case MINI_COMMENT_HOLDER_TYPE: | ||||||
|  |                 return new CommentsMiniInfoItemHolder(infoItemBuilder, parent); | ||||||
|  |             case COMMENT_HOLDER_TYPE: | ||||||
|  |                 return new CommentsInfoItemHolder(infoItemBuilder, parent); | ||||||
|             default: |             default: | ||||||
|                 Log.e(TAG, "Trollolo"); |                 Log.e(TAG, "Trollolo"); | ||||||
|                 return new FallbackViewHolder(new View(parent.getContext())); |                 return new FallbackViewHolder(new View(parent.getContext())); | ||||||
|   | |||||||
| @@ -15,6 +15,7 @@ public enum UserAction { | |||||||
|     REQUESTED_CHANNEL("requested channel"), |     REQUESTED_CHANNEL("requested channel"), | ||||||
|     REQUESTED_PLAYLIST("requested playlist"), |     REQUESTED_PLAYLIST("requested playlist"), | ||||||
|     REQUESTED_KIOSK("requested kiosk"), |     REQUESTED_KIOSK("requested kiosk"), | ||||||
|  |     REQUESTED_COMMENTS("requested comments"), | ||||||
|     DELETE_FROM_HISTORY("delete from history"), |     DELETE_FROM_HISTORY("delete from history"), | ||||||
|     PLAY_STREAM("Play stream"); |     PLAY_STREAM("Play stream"); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -29,9 +29,11 @@ import org.schabi.newpipe.MainActivity; | |||||||
| import org.schabi.newpipe.R; | import org.schabi.newpipe.R; | ||||||
| import org.schabi.newpipe.ReCaptchaActivity; | import org.schabi.newpipe.ReCaptchaActivity; | ||||||
| import org.schabi.newpipe.extractor.Info; | import org.schabi.newpipe.extractor.Info; | ||||||
|  | import org.schabi.newpipe.extractor.InfoItem; | ||||||
| import org.schabi.newpipe.extractor.ListExtractor.InfoItemsPage; | import org.schabi.newpipe.extractor.ListExtractor.InfoItemsPage; | ||||||
| import org.schabi.newpipe.extractor.NewPipe; | import org.schabi.newpipe.extractor.NewPipe; | ||||||
| import org.schabi.newpipe.extractor.channel.ChannelInfo; | import org.schabi.newpipe.extractor.channel.ChannelInfo; | ||||||
|  | import org.schabi.newpipe.extractor.comments.CommentsInfo; | ||||||
| import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException; | import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException; | ||||||
| import org.schabi.newpipe.extractor.exceptions.ParsingException; | import org.schabi.newpipe.extractor.exceptions.ParsingException; | ||||||
| import org.schabi.newpipe.extractor.exceptions.ReCaptchaException; | import org.schabi.newpipe.extractor.exceptions.ReCaptchaException; | ||||||
| @@ -109,7 +111,7 @@ public final class ExtractorHelper { | |||||||
|                                                    final String url, |                                                    final String url, | ||||||
|                                                    boolean forceLoad) { |                                                    boolean forceLoad) { | ||||||
|         checkServiceId(serviceId); |         checkServiceId(serviceId); | ||||||
|         return checkCache(forceLoad, serviceId, url, Single.fromCallable(() -> |         return checkCache(forceLoad, serviceId, url, InfoItem.InfoType.STREAM, Single.fromCallable(() -> | ||||||
|                 StreamInfo.getInfo(NewPipe.getService(serviceId), url))); |                 StreamInfo.getInfo(NewPipe.getService(serviceId), url))); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -117,7 +119,7 @@ public final class ExtractorHelper { | |||||||
|                                                      final String url, |                                                      final String url, | ||||||
|                                                      boolean forceLoad) { |                                                      boolean forceLoad) { | ||||||
|         checkServiceId(serviceId); |         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))); |                 ChannelInfo.getInfo(NewPipe.getService(serviceId), url))); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -129,11 +131,27 @@ public final class ExtractorHelper { | |||||||
|                 ChannelInfo.getMoreItems(NewPipe.getService(serviceId), url, nextStreamsUrl)); |                 ChannelInfo.getMoreItems(NewPipe.getService(serviceId), url, nextStreamsUrl)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     public static Single<CommentsInfo> 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<InfoItemsPage> 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<PlaylistInfo> getPlaylistInfo(final int serviceId, |     public static Single<PlaylistInfo> getPlaylistInfo(final int serviceId, | ||||||
|                                                        final String url, |                                                        final String url, | ||||||
|                                                        boolean forceLoad) { |                                                        boolean forceLoad) { | ||||||
|         checkServiceId(serviceId); |         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))); |                 PlaylistInfo.getInfo(NewPipe.getService(serviceId), url))); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -149,7 +167,7 @@ public final class ExtractorHelper { | |||||||
|                                                  final String url, |                                                  final String url, | ||||||
|                                                  final String contentCountry, |                                                  final String contentCountry, | ||||||
|                                                  boolean forceLoad) { |                                                  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))); |                 KioskInfo.getInfo(NewPipe.getService(serviceId), url, contentCountry))); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -174,16 +192,17 @@ public final class ExtractorHelper { | |||||||
|     private static <I extends Info> Single<I> checkCache(boolean forceLoad, |     private static <I extends Info> Single<I> checkCache(boolean forceLoad, | ||||||
|                                                          int serviceId, |                                                          int serviceId, | ||||||
|                                                          String url, |                                                          String url, | ||||||
|  |                                                          InfoItem.InfoType infoType, | ||||||
|                                                          Single<I> loadFromNetwork) { |                                                          Single<I> loadFromNetwork) { | ||||||
|         checkServiceId(serviceId); |         checkServiceId(serviceId); | ||||||
|         loadFromNetwork = loadFromNetwork.doOnSuccess(info -> cache.putInfo(serviceId, url, info)); |         loadFromNetwork = loadFromNetwork.doOnSuccess(info -> cache.putInfo(serviceId, url, info, infoType)); | ||||||
|  |  | ||||||
|         Single<I> load; |         Single<I> load; | ||||||
|         if (forceLoad) { |         if (forceLoad) { | ||||||
|             cache.removeInfo(serviceId, url); |             cache.removeInfo(serviceId, url, infoType); | ||||||
|             load = loadFromNetwork; |             load = loadFromNetwork; | ||||||
|         } else { |         } else { | ||||||
|             load = Maybe.concat(ExtractorHelper.<I>loadFromCache(serviceId, url), |             load = Maybe.concat(ExtractorHelper.<I>loadFromCache(serviceId, url, infoType), | ||||||
|                     loadFromNetwork.toMaybe()) |                     loadFromNetwork.toMaybe()) | ||||||
|                     .firstElement() //Take the first valid |                     .firstElement() //Take the first valid | ||||||
|                     .toSingle(); |                     .toSingle(); | ||||||
| @@ -195,11 +214,11 @@ public final class ExtractorHelper { | |||||||
|     /** |     /** | ||||||
|      * Default implementation uses the {@link InfoCache} to get cached results |      * Default implementation uses the {@link InfoCache} to get cached results | ||||||
|      */ |      */ | ||||||
|     public static <I extends Info> Maybe<I> loadFromCache(final int serviceId, final String url) { |     public static <I extends Info> Maybe<I> loadFromCache(final int serviceId, final String url, InfoItem.InfoType infoType) { | ||||||
|         checkServiceId(serviceId); |         checkServiceId(serviceId); | ||||||
|         return Maybe.defer(() -> { |         return Maybe.defer(() -> { | ||||||
|             //noinspection unchecked |             //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); |             if (MainActivity.DEBUG) Log.d(TAG, "loadFromCache() called, info > " + info); | ||||||
|  |  | ||||||
|             // Only return info if it's not null (it is cached) |             // Only return info if it's not null (it is cached) | ||||||
|   | |||||||
| @@ -26,6 +26,7 @@ import android.util.Log; | |||||||
|  |  | ||||||
| import org.schabi.newpipe.MainActivity; | import org.schabi.newpipe.MainActivity; | ||||||
| import org.schabi.newpipe.extractor.Info; | import org.schabi.newpipe.extractor.Info; | ||||||
|  | import org.schabi.newpipe.extractor.InfoItem; | ||||||
|  |  | ||||||
| import java.util.Map; | import java.util.Map; | ||||||
| import java.util.concurrent.TimeUnit; | import java.util.concurrent.TimeUnit; | ||||||
| @@ -55,27 +56,27 @@ public final class InfoCache { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Nullable |     @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 + "]"); |         if (DEBUG) Log.d(TAG, "getFromKey() called with: serviceId = [" + serviceId + "], url = [" + url + "]"); | ||||||
|         synchronized (lruCache) { |         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 + "]"); |         if (DEBUG) Log.d(TAG, "putInfo() called with: info = [" + info + "]"); | ||||||
|  |  | ||||||
|         final long expirationMillis = ServiceHelper.getCacheExpirationMillis(info.getServiceId()); |         final long expirationMillis = ServiceHelper.getCacheExpirationMillis(info.getServiceId()); | ||||||
|         synchronized (lruCache) { |         synchronized (lruCache) { | ||||||
|             final CacheData data = new CacheData(info, expirationMillis); |             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 + "]"); |         if (DEBUG) Log.d(TAG, "removeInfo() called with: serviceId = [" + serviceId + "], url = [" + url + "]"); | ||||||
|         synchronized (lruCache) { |         synchronized (lruCache) { | ||||||
|             lruCache.remove(keyOf(serviceId, url)); |             lruCache.remove(keyOf(serviceId, url, infoType)); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -101,8 +102,8 @@ public final class InfoCache { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     @NonNull |     @NonNull | ||||||
|     private static String keyOf(final int serviceId, @NonNull final String url) { |     private static String keyOf(final int serviceId, @NonNull final String url, @NonNull InfoItem.InfoType infoType) { | ||||||
|         return serviceId + url; |         return serviceId + url + infoType.toString(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private static void removeStaleCache(@NonNull final LruCache<String, CacheData> cache) { |     private static void removeStaleCache(@NonNull final LruCache<String, CacheData> cache) { | ||||||
|   | |||||||
| @@ -34,6 +34,7 @@ import org.schabi.newpipe.extractor.stream.VideoStream; | |||||||
| import org.schabi.newpipe.fragments.MainFragment; | import org.schabi.newpipe.fragments.MainFragment; | ||||||
| import org.schabi.newpipe.fragments.detail.VideoDetailFragment; | import org.schabi.newpipe.fragments.detail.VideoDetailFragment; | ||||||
| import org.schabi.newpipe.fragments.list.channel.ChannelFragment; | 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.bookmark.BookmarkFragment; | ||||||
| import org.schabi.newpipe.local.feed.FeedFragment; | import org.schabi.newpipe.local.feed.FeedFragment; | ||||||
| import org.schabi.newpipe.fragments.list.kiosk.KioskFragment; | import org.schabi.newpipe.fragments.list.kiosk.KioskFragment; | ||||||
| @@ -331,6 +332,18 @@ public class NavigationHelper { | |||||||
|                 .commit(); |                 .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, |     public static void openPlaylistFragment(FragmentManager fragmentManager, | ||||||
|                                             int serviceId, |                                             int serviceId, | ||||||
|                                             String url, |                                             String url, | ||||||
|   | |||||||
							
								
								
									
										70
									
								
								app/src/main/res/layout/fragment_comments.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								app/src/main/res/layout/fragment_comments.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,70 @@ | |||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <RelativeLayout | ||||||
|  |     xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |     xmlns:tools="http://schemas.android.com/tools" | ||||||
|  |     android:layout_width="match_parent" | ||||||
|  |     android:layout_height="match_parent"> | ||||||
|  |  | ||||||
|  |     <android.support.v7.widget.RecyclerView | ||||||
|  |         android:id="@+id/items_list" | ||||||
|  |         android:layout_width="match_parent" | ||||||
|  |         android:layout_height="match_parent" | ||||||
|  |         android:scrollbars="vertical" | ||||||
|  |         tools:listitem="@layout/list_comments_item"/> | ||||||
|  |  | ||||||
|  |     <ProgressBar | ||||||
|  |         android:id="@+id/loading_progress_bar" | ||||||
|  |         android:layout_width="wrap_content" | ||||||
|  |         android:layout_height="wrap_content" | ||||||
|  |         android:layout_centerInParent="true" | ||||||
|  |         android:indeterminate="true" | ||||||
|  |         android:visibility="gone" | ||||||
|  |         tools:visibility="visible"/> | ||||||
|  |  | ||||||
|  |     <LinearLayout | ||||||
|  |         android:id="@+id/empty_state_view" | ||||||
|  |         android:layout_width="wrap_content" | ||||||
|  |         android:layout_height="wrap_content" | ||||||
|  |         android:layout_centerInParent="true" | ||||||
|  |         android:orientation="vertical" | ||||||
|  |         android:paddingTop="90dp" | ||||||
|  |         android:visibility="gone" | ||||||
|  |         tools:visibility="visible"> | ||||||
|  |  | ||||||
|  |         <TextView | ||||||
|  |             android:layout_width="wrap_content" | ||||||
|  |             android:layout_height="wrap_content" | ||||||
|  |             android:layout_gravity="center" | ||||||
|  |             android:layout_marginBottom="10dp" | ||||||
|  |             android:fontFamily="monospace" | ||||||
|  |             android:text="(╯°-°)╯" | ||||||
|  |             android:textSize="35sp" | ||||||
|  |             tools:ignore="HardcodedText,UnusedAttribute"/> | ||||||
|  |  | ||||||
|  |         <TextView | ||||||
|  |             android:layout_width="wrap_content" | ||||||
|  |             android:layout_height="wrap_content" | ||||||
|  |             android:layout_gravity="center" | ||||||
|  |             android:text="@string/empty_view_no_videos" | ||||||
|  |             android:textSize="24sp"/> | ||||||
|  |  | ||||||
|  |     </LinearLayout> | ||||||
|  |  | ||||||
|  |     <!--ERROR PANEL--> | ||||||
|  |     <include | ||||||
|  |         android:id="@+id/error_panel" | ||||||
|  |         layout="@layout/error_retry" | ||||||
|  |         android:layout_width="wrap_content" | ||||||
|  |         android:layout_height="wrap_content" | ||||||
|  |         android:layout_centerInParent="true" | ||||||
|  |         android:layout_marginTop="50dp" | ||||||
|  |         android:visibility="gone" | ||||||
|  |         tools:visibility="visible"/> | ||||||
|  |  | ||||||
|  |     <View | ||||||
|  |         android:layout_width="match_parent" | ||||||
|  |         android:layout_height="4dp" | ||||||
|  |         android:background="?attr/toolbar_shadow_drawable" | ||||||
|  |         android:layout_alignParentTop="true"/> | ||||||
|  |  | ||||||
|  | </RelativeLayout> | ||||||
| @@ -495,6 +495,7 @@ | |||||||
|                                         android:src="?attr/expand" |                                         android:src="?attr/expand" | ||||||
|                                         android:textAlignment="center" |                                         android:textAlignment="center" | ||||||
|                                         android:textAllCaps="true" |                                         android:textAllCaps="true" | ||||||
|  |                                         android:visibility="gone" | ||||||
|                                         tools:ignore="ContentDescription" /> |                                         tools:ignore="ContentDescription" /> | ||||||
|  |  | ||||||
|                                 </LinearLayout> |                                 </LinearLayout> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Ritvik Saraf
					Ritvik Saraf