mirror of
				https://github.com/TeamNewPipe/NewPipe
				synced 2025-10-30 23:03:00 +00:00 
			
		
		
		
	merged upstream/dev
This commit is contained in:
		
							
								
								
									
										1
									
								
								.github/FUNDING.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.github/FUNDING.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | liberapay: TeamNewPipe | ||||||
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -7,7 +7,6 @@ | |||||||
| /app/app.iml | /app/app.iml | ||||||
| /.idea | /.idea | ||||||
| /*.iml | /*.iml | ||||||
| gradle.properties |  | ||||||
| *~ | *~ | ||||||
| .weblate | .weblate | ||||||
| *.class | *.class | ||||||
|   | |||||||
							
								
								
									
										11
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								README.md
									
									
									
									
									
								
							| @@ -66,15 +66,22 @@ NewPipe does not use any Google framework libraries, nor the YouTube API. Websit | |||||||
| * Enqueue videos | * Enqueue videos | ||||||
| * Local playlists | * Local playlists | ||||||
| * Subtitles | * Subtitles | ||||||
| * Multi-service support (e.g. SoundCloud \[beta\]) |  | ||||||
| * Livestream support | * Livestream support | ||||||
|  | * Show comments | ||||||
|  |  | ||||||
| ### Coming Features | ### Coming Features | ||||||
|  |  | ||||||
| * Cast to UPnP and Cast | * Cast to UPnP and Cast | ||||||
| * Show comments |  | ||||||
| * … and many more | * … and many more | ||||||
|  |  | ||||||
|  | ### Supported Services | ||||||
|  |  | ||||||
|  | NewPipe supports multiple services. Our [docs](https://teamnewpipe.github.io/documentation/) provide more info on how a new service can be added to the app and the extractor. Please get in touch with us if you intend to add a new one. Currently supported services are: | ||||||
|  |  | ||||||
|  | * YouTube | ||||||
|  | * SoundCloud \[beta\] | ||||||
|  | * media.ccc.de \[beta\] | ||||||
|  |  | ||||||
| ## Updates | ## Updates | ||||||
| When a change to the NewPipe code occurs (due to either adding features or bug fixing), eventually a release will occur. These are in the format x.xx.x . In order to get this new version, you can: | When a change to the NewPipe code occurs (due to either adding features or bug fixing), eventually a release will occur. These are in the format x.xx.x . In order to get this new version, you can: | ||||||
|  * Build a debug APK yourself. This is the fastest way to get new features on your device, but is much more complicated, so we recommend using one of the other methods. |  * Build a debug APK yourself. This is the fastest way to get new features on your device, but is much more complicated, so we recommend using one of the other methods. | ||||||
|   | |||||||
| @@ -1,4 +1,7 @@ | |||||||
| apply plugin: 'com.android.application' | apply plugin: 'com.android.application' | ||||||
|  | apply plugin: 'kotlin-android' | ||||||
|  | apply plugin: 'kotlin-android-extensions' | ||||||
|  | apply plugin: 'kotlin-kapt' | ||||||
|  |  | ||||||
| android { | android { | ||||||
|     compileSdkVersion 28 |     compileSdkVersion 28 | ||||||
| @@ -8,10 +11,10 @@ android { | |||||||
|         applicationId "org.schabi.newpipe" |         applicationId "org.schabi.newpipe" | ||||||
|         minSdkVersion 19 |         minSdkVersion 19 | ||||||
|         targetSdkVersion 28 |         targetSdkVersion 28 | ||||||
|         versionCode 720 |         versionCode 790 | ||||||
|         versionName "0.16.0" |         versionName "0.17.4" | ||||||
|  |  | ||||||
|         testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" |         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" | ||||||
|         vectorDrawables.useSupportLibrary = true |         vectorDrawables.useSupportLibrary = true | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -43,38 +46,43 @@ android { | |||||||
| } | } | ||||||
|  |  | ||||||
| ext { | ext { | ||||||
|     supportLibVersion = '28.0.0' |     androidxLibVersion = '1.0.0' | ||||||
|     exoPlayerLibVersion = '2.8.4' //2.9.0 |     exoPlayerLibVersion = '2.10.7' | ||||||
|     roomDbLibVersion = '1.1.1' |     roomDbLibVersion = '2.1.0' | ||||||
|     leakCanaryLibVersion = '1.5.4' //1.6.1 |     leakCanaryLibVersion = '1.5.4' //1.6.1 | ||||||
|     okHttpLibVersion = '3.11.0' |     okHttpLibVersion = '3.12.6' | ||||||
|     icepickLibVersion = '3.2.0' |     icepickLibVersion = '3.2.0' | ||||||
|     stethoLibVersion = '1.5.0' |     stethoLibVersion = '1.5.0' | ||||||
| } | } | ||||||
|  |  | ||||||
| dependencies { | dependencies { | ||||||
|     androidTestImplementation('com.android.support.test.espresso:espresso-core:3.0.2', { |     implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" | ||||||
|  |  | ||||||
|  |     androidTestImplementation('androidx.test.espresso:espresso-core:3.1.0', { | ||||||
|         exclude module: 'support-annotations' |         exclude module: 'support-annotations' | ||||||
|     }) |     }) | ||||||
|  |  | ||||||
|     implementation 'com.github.yausername:NewPipeExtractor:f60c973' |     implementation 'com.github.yausername:NewPipeExtractor:318f600' | ||||||
|  |  | ||||||
|     testImplementation 'junit:junit:4.12' |     testImplementation 'junit:junit:4.12' | ||||||
|     testImplementation 'org.mockito:mockito-core:2.23.0' |     testImplementation 'org.mockito:mockito-core:2.23.0' | ||||||
|  |  | ||||||
|     implementation "com.android.support:appcompat-v7:${supportLibVersion}" |     implementation 'androidx.appcompat:appcompat:1.1.0' | ||||||
|     implementation "com.android.support:support-v4:${supportLibVersion}" |     implementation "androidx.legacy:legacy-support-v4:${androidxLibVersion}" | ||||||
|     implementation "com.android.support:design:${supportLibVersion}" |     implementation "com.google.android.material:material:${androidxLibVersion}" | ||||||
|     implementation "com.android.support:recyclerview-v7:${supportLibVersion}" |     implementation "androidx.recyclerview:recyclerview:${androidxLibVersion}" | ||||||
|     implementation "com.android.support:preference-v14:${supportLibVersion}" |     implementation "androidx.legacy:legacy-preference-v14:${androidxLibVersion}" | ||||||
|     implementation "com.android.support:cardview-v7:${supportLibVersion}" |     implementation "androidx.cardview:cardview:${androidxLibVersion}" | ||||||
|     implementation 'com.android.support.constraint:constraint-layout:1.1.3' |     implementation 'androidx.constraintlayout:constraintlayout:1.1.3' | ||||||
|  |  | ||||||
|  |     // Originally in NewPipeExtractor | ||||||
|  |     implementation 'com.grack:nanojson:1.1' | ||||||
|  |     implementation 'org.jsoup:jsoup:1.9.2' | ||||||
|  |  | ||||||
|     implementation 'ch.acra:acra:4.9.2' //4.11 |     implementation 'ch.acra:acra:4.9.2' //4.11 | ||||||
|  |  | ||||||
|     implementation 'com.nostra13.universalimageloader:universal-image-loader:1.9.5' |     implementation 'com.nostra13.universalimageloader:universal-image-loader:1.9.5' | ||||||
|     implementation 'de.hdodenhof:circleimageview:2.2.0' |     implementation 'de.hdodenhof:circleimageview:2.2.0' | ||||||
|     implementation 'com.github.nirhart:ParallaxScroll:dd53d1f9d1' |  | ||||||
|     implementation 'com.nononsenseapps:filepicker:4.2.1' |     implementation 'com.nononsenseapps:filepicker:4.2.1' | ||||||
|  |  | ||||||
|     implementation "com.google.android.exoplayer:exoplayer:${exoPlayerLibVersion}" |     implementation "com.google.android.exoplayer:exoplayer:${exoPlayerLibVersion}" | ||||||
| @@ -82,18 +90,18 @@ dependencies { | |||||||
|  |  | ||||||
|     debugImplementation "com.facebook.stetho:stetho:${stethoLibVersion}" |     debugImplementation "com.facebook.stetho:stetho:${stethoLibVersion}" | ||||||
|     debugImplementation "com.facebook.stetho:stetho-urlconnection:${stethoLibVersion}" |     debugImplementation "com.facebook.stetho:stetho-urlconnection:${stethoLibVersion}" | ||||||
|     debugImplementation 'com.android.support:multidex:1.0.3' |     debugImplementation 'androidx.multidex:multidex:2.0.1' | ||||||
|  |  | ||||||
|     implementation 'io.reactivex.rxjava2:rxjava:2.2.2' |     implementation 'io.reactivex.rxjava2:rxjava:2.2.2' | ||||||
|     implementation 'io.reactivex.rxjava2:rxandroid:2.1.0' |     implementation 'io.reactivex.rxjava2:rxandroid:2.1.0' | ||||||
|     implementation 'com.jakewharton.rxbinding2:rxbinding:2.1.1' |     implementation 'com.jakewharton.rxbinding2:rxbinding:2.1.1' | ||||||
|  |  | ||||||
|     implementation "android.arch.persistence.room:runtime:${roomDbLibVersion}" |     implementation "androidx.room:room-runtime:${roomDbLibVersion}" | ||||||
|     implementation "android.arch.persistence.room:rxjava2:${roomDbLibVersion}" |     implementation "androidx.room:room-rxjava2:${roomDbLibVersion}" | ||||||
|     annotationProcessor "android.arch.persistence.room:compiler:${roomDbLibVersion}" |     kapt "androidx.room:room-compiler:${roomDbLibVersion}" | ||||||
|  |  | ||||||
|     implementation "frankiesardo:icepick:${icepickLibVersion}" |     implementation "frankiesardo:icepick:${icepickLibVersion}" | ||||||
|     annotationProcessor "frankiesardo:icepick-processor:${icepickLibVersion}" |     kapt "frankiesardo:icepick-processor:${icepickLibVersion}" | ||||||
|  |  | ||||||
|     debugImplementation "com.squareup.leakcanary:leakcanary-android:${leakCanaryLibVersion}" |     debugImplementation "com.squareup.leakcanary:leakcanary-android:${leakCanaryLibVersion}" | ||||||
|     releaseImplementation "com.squareup.leakcanary:leakcanary-android-no-op:${leakCanaryLibVersion}" |     releaseImplementation "com.squareup.leakcanary:leakcanary-android-no-op:${leakCanaryLibVersion}" | ||||||
|   | |||||||
| @@ -1,8 +1,8 @@ | |||||||
| package org.schabi.newpipe.report; | package org.schabi.newpipe.report; | ||||||
|  |  | ||||||
| import android.os.Parcel; | import android.os.Parcel; | ||||||
| import android.support.test.filters.LargeTest; | import androidx.test.filters.LargeTest; | ||||||
| import android.support.test.runner.AndroidJUnit4; | import androidx.test.ext.junit.runners.AndroidJUnit4; | ||||||
|  |  | ||||||
| import org.junit.Test; | import org.junit.Test; | ||||||
| import org.junit.runner.RunWith; | import org.junit.runner.RunWith; | ||||||
|   | |||||||
| @@ -3,8 +3,8 @@ package org.schabi.newpipe; | |||||||
| import android.content.Context; | import android.content.Context; | ||||||
| import android.content.SharedPreferences; | import android.content.SharedPreferences; | ||||||
| import android.preference.PreferenceManager; | import android.preference.PreferenceManager; | ||||||
| import android.support.annotation.NonNull; | import androidx.annotation.NonNull; | ||||||
| import android.support.multidex.MultiDex; | import androidx.multidex.MultiDex; | ||||||
|  |  | ||||||
| import com.facebook.stetho.Stetho; | import com.facebook.stetho.Stetho; | ||||||
| import com.facebook.stetho.okhttp3.StethoInterceptor; | import com.facebook.stetho.okhttp3.StethoInterceptor; | ||||||
|   | |||||||
| @@ -29,7 +29,7 @@ | |||||||
|             </intent-filter> |             </intent-filter> | ||||||
|         </activity> |         </activity> | ||||||
|  |  | ||||||
|         <receiver android:name="android.support.v4.media.session.MediaButtonReceiver" > |         <receiver android:name="androidx.media.session.MediaButtonReceiver" > | ||||||
|             <intent-filter> |             <intent-filter> | ||||||
|                 <action android:name="android.intent.action.MEDIA_BUTTON" /> |                 <action android:name="android.intent.action.MEDIA_BUTTON" /> | ||||||
|             </intent-filter> |             </intent-filter> | ||||||
| @@ -115,7 +115,7 @@ | |||||||
|             android:label="@string/reCaptchaActivity"/> |             android:label="@string/reCaptchaActivity"/> | ||||||
|  |  | ||||||
|         <provider |         <provider | ||||||
|             android:name="android.support.v4.content.FileProvider" |             android:name="androidx.core.content.FileProvider" | ||||||
|             android:authorities="${applicationId}.provider" |             android:authorities="${applicationId}.provider" | ||||||
|             android:exported="false" |             android:exported="false" | ||||||
|             android:grantUriPermissions="true"> |             android:grantUriPermissions="true"> | ||||||
| @@ -228,7 +228,20 @@ | |||||||
|                 <data android:scheme="http"/> |                 <data android:scheme="http"/> | ||||||
|                 <data android:scheme="https"/> |                 <data android:scheme="https"/> | ||||||
|                 <data android:host="invidio.us"/> |                 <data android:host="invidio.us"/> | ||||||
|  |                 <data android:host="dev.invidio.us"/> | ||||||
|                 <data android:host="www.invidio.us"/> |                 <data android:host="www.invidio.us"/> | ||||||
|  |                 <data android:host="invidious.snopyta.org"/> | ||||||
|  |                 <data android:host="de.invidious.snopyta.org"/> | ||||||
|  |                 <data android:host="fi.invidious.snopyta.org"/> | ||||||
|  |                 <data android:host="vid.wxzm.sx"/> | ||||||
|  |                 <data android:host="invidious.kabi.tk"/> | ||||||
|  |                 <data android:host="invidiou.sh"/> | ||||||
|  |                 <data android:host="www.invidiou.sh"/> | ||||||
|  |                 <data android:host="no.invidiou.sh"/> | ||||||
|  |                 <data android:host="invidious.enkirton.net"/> | ||||||
|  |                 <data android:host="tube.poal.co"/> | ||||||
|  |                 <data android:host="invidious.13ad.de"/> | ||||||
|  |                 <data android:host="yt.elukerio.org"/> | ||||||
|                 <!-- video prefix --> |                 <!-- video prefix --> | ||||||
|                 <data android:pathPrefix="/embed/"/> |                 <data android:pathPrefix="/embed/"/> | ||||||
|                 <data android:pathPrefix="/watch"/> |                 <data android:pathPrefix="/watch"/> | ||||||
|   | |||||||
| @@ -1,116 +0,0 @@ | |||||||
| package android.support.design.widget; |  | ||||||
|  |  | ||||||
| import android.animation.ValueAnimator; |  | ||||||
| import android.content.Context; |  | ||||||
| import android.support.annotation.NonNull; |  | ||||||
| import android.support.design.animation.AnimationUtils; |  | ||||||
| import android.util.AttributeSet; |  | ||||||
| import android.view.View; |  | ||||||
|  |  | ||||||
| // check this https://github.com/ToDou/appbarlayout-spring-behavior/blob/master/appbarspring/src/main/java/android/support/design/widget/AppBarFlingFixBehavior.java |  | ||||||
| public final class FlingBehavior extends AppBarLayout.Behavior { |  | ||||||
|  |  | ||||||
|     private ValueAnimator mOffsetAnimator; |  | ||||||
|     private static final int MAX_OFFSET_ANIMATION_DURATION = 600; // ms |  | ||||||
|  |  | ||||||
|     public FlingBehavior() { |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public FlingBehavior(Context context, AttributeSet attrs) { |  | ||||||
|         super(context, attrs); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, int dx, int dy, int[] consumed, int type) { |  | ||||||
|         if (dy != 0) { |  | ||||||
|             int val = child.getBottom(); |  | ||||||
|             if (val != 0) { |  | ||||||
|                 int min, max; |  | ||||||
|                 if (dy < 0) { |  | ||||||
|                     // We're scrolling down |  | ||||||
|                 } else { |  | ||||||
|                     // We're scrolling up |  | ||||||
|                     if (mOffsetAnimator != null && mOffsetAnimator.isRunning()) { |  | ||||||
|                         mOffsetAnimator.cancel(); |  | ||||||
|                     } |  | ||||||
|                     min = -child.getUpNestedPreScrollRange(); |  | ||||||
|                     max = 0; |  | ||||||
|                     consumed[1] = scroll(coordinatorLayout, child, dy, min, max); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public boolean onNestedPreFling(@NonNull CoordinatorLayout coordinatorLayout, @NonNull AppBarLayout child, @NonNull View target, float velocityX, float velocityY) { |  | ||||||
|  |  | ||||||
|         if (velocityY != 0) { |  | ||||||
|             if (velocityY < 0) { |  | ||||||
|                 // We're flinging down |  | ||||||
|                 int val = child.getBottom(); |  | ||||||
|                 if (val != 0) { |  | ||||||
|                     final int targetScroll = |  | ||||||
|                             +child.getDownNestedPreScrollRange(); |  | ||||||
|                     animateOffsetTo(coordinatorLayout, child, targetScroll, velocityY); |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|             } else { |  | ||||||
|                 // We're flinging up |  | ||||||
|                 int val = child.getBottom(); |  | ||||||
|                 if (val != 0) { |  | ||||||
|                     final int targetScroll = -child.getUpNestedPreScrollRange(); |  | ||||||
|                     if (getTopBottomOffsetForScrollingSibling() > targetScroll) { |  | ||||||
|                         animateOffsetTo(coordinatorLayout, child, targetScroll, velocityY); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private void animateOffsetTo(final CoordinatorLayout coordinatorLayout, |  | ||||||
|                                  final AppBarLayout child, final int offset, float velocity) { |  | ||||||
|         final int distance = Math.abs(getTopBottomOffsetForScrollingSibling() - offset); |  | ||||||
|  |  | ||||||
|         final int duration; |  | ||||||
|         velocity = Math.abs(velocity); |  | ||||||
|         if (velocity > 0) { |  | ||||||
|             duration = 3 * Math.round(1000 * (distance / velocity)); |  | ||||||
|         } else { |  | ||||||
|             final float distanceRatio = (float) distance / child.getHeight(); |  | ||||||
|             duration = (int) ((distanceRatio + 1) * 150); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         animateOffsetWithDuration(coordinatorLayout, child, offset, duration); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private void animateOffsetWithDuration(final CoordinatorLayout coordinatorLayout, |  | ||||||
|                                            final AppBarLayout child, final int offset, final int duration) { |  | ||||||
|         final int currentOffset = getTopBottomOffsetForScrollingSibling(); |  | ||||||
|         if (currentOffset == offset) { |  | ||||||
|             if (mOffsetAnimator != null && mOffsetAnimator.isRunning()) { |  | ||||||
|                 mOffsetAnimator.cancel(); |  | ||||||
|             } |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if (mOffsetAnimator == null) { |  | ||||||
|             mOffsetAnimator = new ValueAnimator(); |  | ||||||
|             mOffsetAnimator.setInterpolator(AnimationUtils.DECELERATE_INTERPOLATOR); |  | ||||||
|             mOffsetAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { |  | ||||||
|                 @Override |  | ||||||
|                 public void onAnimationUpdate(ValueAnimator animator) { |  | ||||||
|                     setHeaderTopBottomOffset(coordinatorLayout, child, |  | ||||||
|                             (Integer) animator.getAnimatedValue()); |  | ||||||
|                 } |  | ||||||
|             }); |  | ||||||
|         } else { |  | ||||||
|             mOffsetAnimator.cancel(); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         mOffsetAnimator.setDuration(Math.min(duration, MAX_OFFSET_ANIMATION_DURATION)); |  | ||||||
|         mOffsetAnimator.setIntValues(currentOffset, offset); |  | ||||||
|         mOffsetAnimator.start(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -0,0 +1,82 @@ | |||||||
|  | package com.google.android.material.appbar; | ||||||
|  |  | ||||||
|  | import android.content.Context; | ||||||
|  | import android.util.AttributeSet; | ||||||
|  | import android.view.MotionEvent; | ||||||
|  | import android.widget.OverScroller; | ||||||
|  |  | ||||||
|  | import androidx.annotation.Nullable; | ||||||
|  | import androidx.coordinatorlayout.widget.CoordinatorLayout; | ||||||
|  |  | ||||||
|  | import java.lang.reflect.Field; | ||||||
|  |  | ||||||
|  | // check this https://stackoverflow.com/questions/56849221/recyclerview-fling-causes-laggy-while-appbarlayout-is-scrolling/57997489#57997489 | ||||||
|  | public final class FlingBehavior extends AppBarLayout.Behavior { | ||||||
|  |  | ||||||
|  |     public FlingBehavior(Context context, AttributeSet attrs) { | ||||||
|  |         super(context, attrs); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public boolean onInterceptTouchEvent(CoordinatorLayout parent, AppBarLayout child, MotionEvent ev) { | ||||||
|  |         switch (ev.getActionMasked()) { | ||||||
|  |             case MotionEvent.ACTION_DOWN: | ||||||
|  |                 // remove reference to old nested scrolling child | ||||||
|  |                 resetNestedScrollingChild(); | ||||||
|  |                 // Stop fling when your finger touches the screen | ||||||
|  |                 stopAppBarLayoutFling(); | ||||||
|  |                 break; | ||||||
|  |             default: | ||||||
|  |                 break; | ||||||
|  |         } | ||||||
|  |         return super.onInterceptTouchEvent(parent, child, ev); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Nullable | ||||||
|  |     private OverScroller getScrollerField() { | ||||||
|  |         try { | ||||||
|  |             Class<?> headerBehaviorType = this.getClass().getSuperclass().getSuperclass().getSuperclass(); | ||||||
|  |             if (headerBehaviorType != null) { | ||||||
|  |                 Field field = headerBehaviorType.getDeclaredField("scroller"); | ||||||
|  |                 field.setAccessible(true); | ||||||
|  |                 return ((OverScroller) field.get(this)); | ||||||
|  |             } | ||||||
|  |         } catch (NoSuchFieldException | IllegalAccessException e) { | ||||||
|  |             // ? | ||||||
|  |         } | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Nullable | ||||||
|  |     private Field getLastNestedScrollingChildRefField() { | ||||||
|  |         try { | ||||||
|  |             Class<?> headerBehaviorType = this.getClass().getSuperclass().getSuperclass(); | ||||||
|  |             if (headerBehaviorType != null) { | ||||||
|  |                 Field field = headerBehaviorType.getDeclaredField("lastNestedScrollingChildRef"); | ||||||
|  |                 field.setAccessible(true); | ||||||
|  |                 return field; | ||||||
|  |             } | ||||||
|  |         } catch (NoSuchFieldException e) { | ||||||
|  |             // ? | ||||||
|  |         } | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private void resetNestedScrollingChild(){ | ||||||
|  |         Field field = getLastNestedScrollingChildRefField(); | ||||||
|  |         if(field != null){ | ||||||
|  |             try { | ||||||
|  |                 Object value = field.get(this); | ||||||
|  |                 if(value != null) field.set(this, null); | ||||||
|  |             } catch (IllegalAccessException e) { | ||||||
|  |                 // ? | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private void stopAppBarLayoutFling() { | ||||||
|  |         OverScroller scroller = getScrollerField(); | ||||||
|  |         if (scroller != null) scroller.forceFinished(true); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -6,7 +6,7 @@ import android.app.NotificationChannel; | |||||||
| import android.app.NotificationManager; | import android.app.NotificationManager; | ||||||
| import android.content.Context; | import android.content.Context; | ||||||
| import android.os.Build; | import android.os.Build; | ||||||
| import android.support.annotation.Nullable; | import androidx.annotation.Nullable; | ||||||
| import android.util.Log; | import android.util.Log; | ||||||
|  |  | ||||||
| import com.nostra13.universalimageloader.cache.memory.impl.LRULimitedMemoryCache; | import com.nostra13.universalimageloader.cache.memory.impl.LRULimitedMemoryCache; | ||||||
|   | |||||||
| @@ -2,10 +2,10 @@ package org.schabi.newpipe; | |||||||
|  |  | ||||||
| import android.content.Context; | import android.content.Context; | ||||||
| import android.os.Bundle; | import android.os.Bundle; | ||||||
| import android.support.annotation.NonNull; | import androidx.annotation.NonNull; | ||||||
| import android.support.v4.app.Fragment; | import androidx.fragment.app.Fragment; | ||||||
| import android.support.v4.app.FragmentManager; | import androidx.fragment.app.FragmentManager; | ||||||
| import android.support.v7.app.AppCompatActivity; | import androidx.appcompat.app.AppCompatActivity; | ||||||
| import android.util.Log; | import android.util.Log; | ||||||
| import android.view.View; | import android.view.View; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -12,21 +12,18 @@ import android.net.ConnectivityManager; | |||||||
| import android.net.Uri; | import android.net.Uri; | ||||||
| import android.os.AsyncTask; | import android.os.AsyncTask; | ||||||
| import android.preference.PreferenceManager; | import android.preference.PreferenceManager; | ||||||
| import android.support.v4.app.NotificationCompat; | import androidx.core.app.NotificationCompat; | ||||||
| import android.support.v4.app.NotificationManagerCompat; | import androidx.core.app.NotificationManagerCompat; | ||||||
|  | import android.util.Log; | ||||||
|  |  | ||||||
| import org.json.JSONException; | import org.json.JSONException; | ||||||
| import org.json.JSONObject; | import org.json.JSONObject; | ||||||
| import org.schabi.newpipe.report.ErrorActivity; | import org.schabi.newpipe.report.ErrorActivity; | ||||||
| import org.schabi.newpipe.report.UserAction; | import org.schabi.newpipe.report.UserAction; | ||||||
|  |  | ||||||
| import java.io.BufferedReader; |  | ||||||
| import java.io.ByteArrayInputStream; | import java.io.ByteArrayInputStream; | ||||||
| import java.io.IOException; | import java.io.IOException; | ||||||
| import java.io.InputStream; | import java.io.InputStream; | ||||||
| import java.io.InputStreamReader; |  | ||||||
| import java.net.HttpURLConnection; |  | ||||||
| import java.net.URL; |  | ||||||
| import java.security.MessageDigest; | import java.security.MessageDigest; | ||||||
| import java.security.NoSuchAlgorithmException; | import java.security.NoSuchAlgorithmException; | ||||||
| import java.security.cert.CertificateEncodingException; | import java.security.cert.CertificateEncodingException; | ||||||
| @@ -37,7 +34,6 @@ import java.util.concurrent.TimeUnit; | |||||||
|  |  | ||||||
| import okhttp3.OkHttpClient; | import okhttp3.OkHttpClient; | ||||||
| import okhttp3.Request; | import okhttp3.Request; | ||||||
| import okhttp3.RequestBody; |  | ||||||
| import okhttp3.Response; | import okhttp3.Response; | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -47,6 +43,8 @@ import okhttp3.Response; | |||||||
|  */ |  */ | ||||||
| public class CheckForNewAppVersionTask extends AsyncTask<Void, Void, String> { | public class CheckForNewAppVersionTask extends AsyncTask<Void, Void, String> { | ||||||
|  |  | ||||||
|  |     private static final boolean DEBUG = MainActivity.DEBUG; | ||||||
|  |     private static final String TAG = CheckForNewAppVersionTask.class.getSimpleName(); | ||||||
|     private static final Application app = App.getApp(); |     private static final Application app = App.getApp(); | ||||||
|     private static final String GITHUB_APK_SHA1 = "B0:2E:90:7C:1C:D6:FC:57:C3:35:F0:88:D0:8F:50:5F:94:E4:D2:15"; |     private static final String GITHUB_APK_SHA1 = "B0:2E:90:7C:1C:D6:FC:57:C3:35:F0:88:D0:8F:50:5F:94:E4:D2:15"; | ||||||
|     private static final String newPipeApiUrl = "https://newpipe.schabi.org/api/data.json"; |     private static final String newPipeApiUrl = "https://newpipe.schabi.org/api/data.json"; | ||||||
| @@ -90,9 +88,8 @@ public class CheckForNewAppVersionTask extends AsyncTask<Void, Void, String> { | |||||||
|             Response response = client.newCall(request).execute(); |             Response response = client.newCall(request).execute(); | ||||||
|             return response.body().string(); |             return response.body().string(); | ||||||
|         } catch (IOException ex) { |         } catch (IOException ex) { | ||||||
|             ErrorActivity.reportError(app, ex, null, null, |             // connectivity problems, do not alarm user and fail silently | ||||||
|                     ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none", |             if (DEBUG) Log.w(TAG, Log.getStackTraceString(ex)); | ||||||
|                             "app update API fail", R.string.app_ui_crash)); |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         return null; |         return null; | ||||||
| @@ -117,9 +114,8 @@ public class CheckForNewAppVersionTask extends AsyncTask<Void, Void, String> { | |||||||
|                 compareAppVersionAndShowNotification(versionName, apkLocationUrl, versionCode); |                 compareAppVersionAndShowNotification(versionName, apkLocationUrl, versionCode); | ||||||
|  |  | ||||||
|             } catch (JSONException ex) { |             } catch (JSONException ex) { | ||||||
|                 ErrorActivity.reportError(app, ex, null, null, |                 // connectivity problems, do not alarm user and fail silently | ||||||
|                         ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "none", |                 if (DEBUG) Log.w(TAG, Log.getStackTraceString(ex)); | ||||||
|                         "could not parse app update JSON data", R.string.app_ui_crash)); |  | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -1,8 +1,10 @@ | |||||||
| package org.schabi.newpipe; | package org.schabi.newpipe; | ||||||
|  |  | ||||||
| import android.support.annotation.Nullable; |  | ||||||
| import android.text.TextUtils; | import android.text.TextUtils; | ||||||
|  |  | ||||||
|  | import androidx.annotation.NonNull; | ||||||
|  | import androidx.annotation.Nullable; | ||||||
|  |  | ||||||
| import org.schabi.newpipe.extractor.DownloadRequest; | import org.schabi.newpipe.extractor.DownloadRequest; | ||||||
| import org.schabi.newpipe.extractor.DownloadResponse; | import org.schabi.newpipe.extractor.DownloadResponse; | ||||||
| import org.schabi.newpipe.extractor.exceptions.ReCaptchaException; | import org.schabi.newpipe.extractor.exceptions.ReCaptchaException; | ||||||
| @@ -10,6 +12,7 @@ import org.schabi.newpipe.extractor.utils.Localization; | |||||||
|  |  | ||||||
| import java.io.IOException; | import java.io.IOException; | ||||||
| import java.io.InputStream; | import java.io.InputStream; | ||||||
|  | import java.util.Arrays; | ||||||
| import java.util.Collections; | import java.util.Collections; | ||||||
| import java.util.HashMap; | import java.util.HashMap; | ||||||
| import java.util.List; | import java.util.List; | ||||||
| @@ -23,6 +26,8 @@ import okhttp3.RequestBody; | |||||||
| import okhttp3.Response; | import okhttp3.Response; | ||||||
| import okhttp3.ResponseBody; | import okhttp3.ResponseBody; | ||||||
|  |  | ||||||
|  | import static java.util.Collections.singletonList; | ||||||
|  |  | ||||||
|  |  | ||||||
| /* | /* | ||||||
|  * Created by Christian Schabesberger on 28.01.16. |  * Created by Christian Schabesberger on 28.01.16. | ||||||
| @@ -163,7 +168,7 @@ public class Downloader implements org.schabi.newpipe.extractor.Downloader { | |||||||
|         final ResponseBody body = response.body(); |         final ResponseBody body = response.body(); | ||||||
|  |  | ||||||
|         if (response.code() == 429) { |         if (response.code() == 429) { | ||||||
|             throw new ReCaptchaException("reCaptcha Challenge requested"); |             throw new ReCaptchaException("reCaptcha Challenge requested", siteUrl); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (body == null) { |         if (body == null) { | ||||||
| @@ -213,7 +218,7 @@ public class Downloader implements org.schabi.newpipe.extractor.Downloader { | |||||||
|         final ResponseBody body = response.body(); |         final ResponseBody body = response.body(); | ||||||
|  |  | ||||||
|         if (response.code() == 429) { |         if (response.code() == 429) { | ||||||
|             throw new ReCaptchaException("reCaptcha Challenge requested"); |             throw new ReCaptchaException("reCaptcha Challenge requested", siteUrl); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (body == null) { |         if (body == null) { | ||||||
| @@ -221,7 +226,7 @@ public class Downloader implements org.schabi.newpipe.extractor.Downloader { | |||||||
|             return null; |             return null; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         return new DownloadResponse(body.string(), response.headers().toMultimap()); |         return new DownloadResponse(response.code(), body.string(), response.headers().toMultimap()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
| @@ -267,7 +272,7 @@ public class Downloader implements org.schabi.newpipe.extractor.Downloader { | |||||||
|         final ResponseBody body = response.body(); |         final ResponseBody body = response.body(); | ||||||
|  |  | ||||||
|         if (response.code() == 429) { |         if (response.code() == 429) { | ||||||
|             throw new ReCaptchaException("reCaptcha Challenge requested"); |             throw new ReCaptchaException("reCaptcha Challenge requested", siteUrl); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (body == null) { |         if (body == null) { | ||||||
| @@ -275,6 +280,30 @@ public class Downloader implements org.schabi.newpipe.extractor.Downloader { | |||||||
|             return null; |             return null; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         return new DownloadResponse(body.string(), response.headers().toMultimap()); |         return new DownloadResponse(response.code(), body.string(), response.headers().toMultimap()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public DownloadResponse head(String siteUrl) throws IOException, ReCaptchaException { | ||||||
|  |         final Request request = new Request.Builder() | ||||||
|  |                 .head().url(siteUrl) | ||||||
|  |                 .addHeader("User-Agent", USER_AGENT) | ||||||
|  |                 .build(); | ||||||
|  |         final Response response = client.newCall(request).execute(); | ||||||
|  |  | ||||||
|  |         if (response.code() == 429) { | ||||||
|  |             throw new ReCaptchaException("reCaptcha Challenge requested", siteUrl); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return new DownloadResponse(response.code(), null, response.headers().toMultimap()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public DownloadResponse get(String siteUrl, @NonNull Localization localization) throws IOException, ReCaptchaException { | ||||||
|  |         final Map<String, List<String>> requestHeaders = new HashMap<>(); | ||||||
|  |         requestHeaders.put("Accept-Language", singletonList(localization.getLanguage())); | ||||||
|  |  | ||||||
|  |         return get(siteUrl, new DownloadRequest(null, requestHeaders)); | ||||||
|  |     } | ||||||
|  |  | ||||||
| } | } | ||||||
| @@ -28,17 +28,7 @@ import android.os.Bundle; | |||||||
| import android.os.Handler; | import android.os.Handler; | ||||||
| import android.os.Looper; | import android.os.Looper; | ||||||
| import android.preference.PreferenceManager; | import android.preference.PreferenceManager; | ||||||
| import android.support.annotation.NonNull; |  | ||||||
| import android.support.design.widget.NavigationView; |  | ||||||
| import android.support.v4.app.Fragment; |  | ||||||
| import android.support.v4.view.GravityCompat; |  | ||||||
| import android.support.v4.widget.DrawerLayout; |  | ||||||
| import android.support.v7.app.ActionBar; |  | ||||||
| import android.support.v7.app.ActionBarDrawerToggle; |  | ||||||
| import android.support.v7.app.AppCompatActivity; |  | ||||||
| import android.support.v7.widget.Toolbar; |  | ||||||
| import android.util.Log; | import android.util.Log; | ||||||
| import android.view.Gravity; |  | ||||||
| import android.view.Menu; | import android.view.Menu; | ||||||
| import android.view.MenuInflater; | import android.view.MenuInflater; | ||||||
| import android.view.MenuItem; | import android.view.MenuItem; | ||||||
| @@ -49,6 +39,17 @@ import android.widget.Button; | |||||||
| import android.widget.ImageView; | import android.widget.ImageView; | ||||||
| import android.widget.TextView; | import android.widget.TextView; | ||||||
|  |  | ||||||
|  | import androidx.annotation.NonNull; | ||||||
|  | import androidx.appcompat.app.ActionBar; | ||||||
|  | import androidx.appcompat.app.ActionBarDrawerToggle; | ||||||
|  | import androidx.appcompat.app.AppCompatActivity; | ||||||
|  | import androidx.appcompat.widget.Toolbar; | ||||||
|  | import androidx.core.view.GravityCompat; | ||||||
|  | import androidx.drawerlayout.widget.DrawerLayout; | ||||||
|  | import androidx.fragment.app.Fragment; | ||||||
|  |  | ||||||
|  | import com.google.android.material.navigation.NavigationView; | ||||||
|  |  | ||||||
| import org.schabi.newpipe.extractor.NewPipe; | import org.schabi.newpipe.extractor.NewPipe; | ||||||
| import org.schabi.newpipe.extractor.StreamingService; | import org.schabi.newpipe.extractor.StreamingService; | ||||||
| import org.schabi.newpipe.extractor.exceptions.ExtractionException; | import org.schabi.newpipe.extractor.exceptions.ExtractionException; | ||||||
| @@ -73,6 +74,7 @@ public class MainActivity extends AppCompatActivity { | |||||||
|     private DrawerLayout drawer = null; |     private DrawerLayout drawer = null; | ||||||
|     private NavigationView drawerItems = null; |     private NavigationView drawerItems = null; | ||||||
|     private TextView headerServiceView = null; |     private TextView headerServiceView = null; | ||||||
|  |     private Button toggleServiceButton = null; | ||||||
|  |  | ||||||
|     private boolean servicesShown = false; |     private boolean servicesShown = false; | ||||||
|     private ImageView serviceArrow; |     private ImageView serviceArrow; | ||||||
| @@ -266,8 +268,8 @@ public class MainActivity extends AppCompatActivity { | |||||||
|  |  | ||||||
|         serviceArrow = hView.findViewById(R.id.drawer_arrow); |         serviceArrow = hView.findViewById(R.id.drawer_arrow); | ||||||
|         headerServiceView = hView.findViewById(R.id.drawer_header_service_view); |         headerServiceView = hView.findViewById(R.id.drawer_header_service_view); | ||||||
|         Button action = hView.findViewById(R.id.drawer_header_action_button); |         toggleServiceButton = hView.findViewById(R.id.drawer_header_action_button); | ||||||
|         action.setOnClickListener(view -> { |         toggleServiceButton.setOnClickListener(view -> { | ||||||
|             toggleServices(); |             toggleServices(); | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| @@ -279,6 +281,7 @@ public class MainActivity extends AppCompatActivity { | |||||||
|         drawerItems.getMenu().removeGroup(R.id.menu_tabs_group); |         drawerItems.getMenu().removeGroup(R.id.menu_tabs_group); | ||||||
|         drawerItems.getMenu().removeGroup(R.id.menu_options_about_group); |         drawerItems.getMenu().removeGroup(R.id.menu_options_about_group); | ||||||
|  |  | ||||||
|  |  | ||||||
|         if(servicesShown) { |         if(servicesShown) { | ||||||
|             showServices(); |             showServices(); | ||||||
|         } else { |         } else { | ||||||
| @@ -359,12 +362,14 @@ public class MainActivity extends AppCompatActivity { | |||||||
|  |  | ||||||
|         // close drawer on return, and don't show animation, so its looks like the drawer isn't open |         // close drawer on return, and don't show animation, so its looks like the drawer isn't open | ||||||
|         // when the user returns to MainActivity |         // when the user returns to MainActivity | ||||||
|         drawer.closeDrawer(Gravity.START, false); |         drawer.closeDrawer(GravityCompat.START, false); | ||||||
|         try { |         try { | ||||||
|             String selectedServiceName = NewPipe.getService( |             String selectedServiceName = NewPipe.getService( | ||||||
|                     ServiceHelper.getSelectedServiceId(this)).getServiceInfo().getName(); |                     ServiceHelper.getSelectedServiceId(this)).getServiceInfo().getName(); | ||||||
|             headerServiceView.setText(selectedServiceName); |             headerServiceView.setText(selectedServiceName); | ||||||
|             headerServiceView.post(() -> headerServiceView.setSelected(true)); |             headerServiceView.post(() -> headerServiceView.setSelected(true)); | ||||||
|  |             toggleServiceButton.setContentDescription( | ||||||
|  |                     getString(R.string.drawer_header_description) + selectedServiceName); | ||||||
|         } catch (Exception e) { |         } catch (Exception e) { | ||||||
|             ErrorActivity.reportUiError(this, e); |             ErrorActivity.reportUiError(this, e); | ||||||
|         } |         } | ||||||
| @@ -558,6 +563,14 @@ public class MainActivity extends AppCompatActivity { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     private void updateDrawerHeaderString(String content) { | ||||||
|  |         NavigationView navigationView = findViewById(R.id.navigation); | ||||||
|  |         View hView =  navigationView.getHeaderView(0); | ||||||
|  |         Button action = hView.findViewById(R.id.drawer_header_action_button); | ||||||
|  |  | ||||||
|  |         action.setContentDescription(content); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     private void handleIntent(Intent intent) { |     private void handleIntent(Intent intent) { | ||||||
|         try { |         try { | ||||||
|             if (DEBUG) Log.d(TAG, "handleIntent() called with: intent = [" + intent + "]"); |             if (DEBUG) Log.d(TAG, "handleIntent() called with: intent = [" + intent + "]"); | ||||||
|   | |||||||
| @@ -1,8 +1,8 @@ | |||||||
| package org.schabi.newpipe; | package org.schabi.newpipe; | ||||||
|  |  | ||||||
| import android.arch.persistence.room.Room; | import androidx.room.Room; | ||||||
| import android.content.Context; | import android.content.Context; | ||||||
| import android.support.annotation.NonNull; | import androidx.annotation.NonNull; | ||||||
|  |  | ||||||
| import org.schabi.newpipe.database.AppDatabase; | import org.schabi.newpipe.database.AppDatabase; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -5,13 +5,12 @@ import android.content.Intent; | |||||||
| import android.graphics.Bitmap; | import android.graphics.Bitmap; | ||||||
| import android.os.Build; | import android.os.Build; | ||||||
| import android.os.Bundle; | import android.os.Bundle; | ||||||
| import android.support.v4.app.NavUtils; | import androidx.core.app.NavUtils; | ||||||
| import android.support.v7.app.ActionBar; | import androidx.appcompat.app.ActionBar; | ||||||
| import android.support.v7.app.AppCompatActivity; | import androidx.appcompat.app.AppCompatActivity; | ||||||
| import android.support.v7.widget.Toolbar; | import androidx.appcompat.widget.Toolbar; | ||||||
| import android.view.MenuItem; | import android.view.MenuItem; | ||||||
| import android.webkit.CookieManager; | import android.webkit.CookieManager; | ||||||
| import android.webkit.ValueCallback; |  | ||||||
| import android.webkit.WebSettings; | import android.webkit.WebSettings; | ||||||
| import android.webkit.WebView; | import android.webkit.WebView; | ||||||
| import android.webkit.WebViewClient; | import android.webkit.WebViewClient; | ||||||
| @@ -37,15 +36,24 @@ import android.webkit.WebViewClient; | |||||||
|  */ |  */ | ||||||
| public class ReCaptchaActivity extends AppCompatActivity { | public class ReCaptchaActivity extends AppCompatActivity { | ||||||
|     public static final int RECAPTCHA_REQUEST = 10; |     public static final int RECAPTCHA_REQUEST = 10; | ||||||
|  |     public static final String RECAPTCHA_URL_EXTRA = "recaptcha_url_extra"; | ||||||
|  |  | ||||||
|     public static final String TAG = ReCaptchaActivity.class.toString(); |     public static final String TAG = ReCaptchaActivity.class.toString(); | ||||||
|     public static final String YT_URL = "https://www.youtube.com"; |     public static final String YT_URL = "https://www.youtube.com"; | ||||||
|  |  | ||||||
|  |     private String url; | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     protected void onCreate(Bundle savedInstanceState) { |     protected void onCreate(Bundle savedInstanceState) { | ||||||
|         super.onCreate(savedInstanceState); |         super.onCreate(savedInstanceState); | ||||||
|         setContentView(R.layout.activity_recaptcha); |         setContentView(R.layout.activity_recaptcha); | ||||||
|  |  | ||||||
|  |         url = getIntent().getStringExtra(RECAPTCHA_URL_EXTRA); | ||||||
|  |         if (url == null || url.isEmpty()) { | ||||||
|  |             url = YT_URL; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |  | ||||||
|         // Set return to Cancel by default |         // Set return to Cancel by default | ||||||
|         setResult(RESULT_CANCELED); |         setResult(RESULT_CANCELED); | ||||||
|  |  | ||||||
| @@ -73,15 +81,12 @@ public class ReCaptchaActivity extends AppCompatActivity { | |||||||
|         myWebView.clearHistory(); |         myWebView.clearHistory(); | ||||||
|         android.webkit.CookieManager cookieManager = CookieManager.getInstance(); |         android.webkit.CookieManager cookieManager = CookieManager.getInstance(); | ||||||
|         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { |         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { | ||||||
|             cookieManager.removeAllCookies(new ValueCallback<Boolean>() { |             cookieManager.removeAllCookies(aBoolean -> {}); | ||||||
|                 @Override |  | ||||||
|                 public void onReceiveValue(Boolean aBoolean) {} |  | ||||||
|             }); |  | ||||||
|         } else { |         } else { | ||||||
|             cookieManager.removeAllCookie(); |             cookieManager.removeAllCookie(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         myWebView.loadUrl(YT_URL); |         myWebView.loadUrl(url); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private class ReCaptchaWebViewClient extends WebViewClient { |     private class ReCaptchaWebViewClient extends WebViewClient { | ||||||
|   | |||||||
| @@ -9,12 +9,12 @@ import android.content.SharedPreferences; | |||||||
| import android.content.pm.PackageManager; | import android.content.pm.PackageManager; | ||||||
| import android.os.Bundle; | import android.os.Bundle; | ||||||
| import android.preference.PreferenceManager; | import android.preference.PreferenceManager; | ||||||
| import android.support.annotation.DrawableRes; | import androidx.annotation.DrawableRes; | ||||||
| import android.support.annotation.NonNull; | import androidx.annotation.NonNull; | ||||||
| import android.support.annotation.Nullable; | import androidx.annotation.Nullable; | ||||||
| import android.support.v4.app.NotificationCompat; | import androidx.core.app.NotificationCompat; | ||||||
| import android.support.v7.app.AlertDialog; | import androidx.appcompat.app.AlertDialog; | ||||||
| import android.support.v7.app.AppCompatActivity; | import androidx.appcompat.app.AppCompatActivity; | ||||||
| import android.text.TextUtils; | import android.text.TextUtils; | ||||||
| import android.view.ContextThemeWrapper; | import android.view.ContextThemeWrapper; | ||||||
| import android.view.LayoutInflater; | import android.view.LayoutInflater; | ||||||
| @@ -26,6 +26,8 @@ import android.widget.RadioButton; | |||||||
| import android.widget.RadioGroup; | import android.widget.RadioGroup; | ||||||
| import android.widget.Toast; | import android.widget.Toast; | ||||||
|  |  | ||||||
|  | import androidx.fragment.app.FragmentManager; | ||||||
|  |  | ||||||
| import org.schabi.newpipe.download.DownloadDialog; | import org.schabi.newpipe.download.DownloadDialog; | ||||||
| import org.schabi.newpipe.extractor.Info; | import org.schabi.newpipe.extractor.Info; | ||||||
| import org.schabi.newpipe.extractor.NewPipe; | import org.schabi.newpipe.extractor.NewPipe; | ||||||
| @@ -74,10 +76,13 @@ import static org.schabi.newpipe.util.ThemeHelper.resolveResourceIdFromAttr; | |||||||
|  */ |  */ | ||||||
| public class RouterActivity extends AppCompatActivity { | public class RouterActivity extends AppCompatActivity { | ||||||
|  |  | ||||||
|     @State protected int currentServiceId = -1; |     @State | ||||||
|  |     protected int currentServiceId = -1; | ||||||
|     private StreamingService currentService; |     private StreamingService currentService; | ||||||
|     @State protected LinkType currentLinkType; |     @State | ||||||
|     @State protected int selectedRadioPosition = -1; |     protected LinkType currentLinkType; | ||||||
|  |     @State | ||||||
|  |     protected int selectedRadioPosition = -1; | ||||||
|     protected int selectedPreviously = -1; |     protected int selectedPreviously = -1; | ||||||
|  |  | ||||||
|     protected String currentUrl; |     protected String currentUrl; | ||||||
| @@ -430,7 +435,7 @@ public class RouterActivity extends AppCompatActivity { | |||||||
|                     int selectedVideoStreamIndex = ListHelper.getDefaultResolutionIndex(this, |                     int selectedVideoStreamIndex = ListHelper.getDefaultResolutionIndex(this, | ||||||
|                             sortedVideoStreams); |                             sortedVideoStreams); | ||||||
|  |  | ||||||
|                     android.support.v4.app.FragmentManager fm = getSupportFragmentManager(); |                     FragmentManager fm = getSupportFragmentManager(); | ||||||
|                     DownloadDialog downloadDialog = DownloadDialog.newInstance(result); |                     DownloadDialog downloadDialog = DownloadDialog.newInstance(result); | ||||||
|                     downloadDialog.setVideoStreams(sortedVideoStreams); |                     downloadDialog.setVideoStreams(sortedVideoStreams); | ||||||
|                     downloadDialog.setAudioStreams(result.getAudioStreams()); |                     downloadDialog.setAudioStreams(result.getAudioStreams()); | ||||||
| @@ -460,7 +465,8 @@ public class RouterActivity extends AppCompatActivity { | |||||||
|  |  | ||||||
|     private static class AdapterChoiceItem { |     private static class AdapterChoiceItem { | ||||||
|         final String description, key; |         final String description, key; | ||||||
|         @DrawableRes final int icon; |         @DrawableRes | ||||||
|  |         final int icon; | ||||||
|  |  | ||||||
|         AdapterChoiceItem(String key, String description, int icon) { |         AdapterChoiceItem(String key, String description, int icon) { | ||||||
|             this.description = description; |             this.description = description; | ||||||
| @@ -558,7 +564,8 @@ public class RouterActivity extends AppCompatActivity { | |||||||
|  |  | ||||||
|                 final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); |                 final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); | ||||||
|                 boolean isExtVideoEnabled = preferences.getBoolean(getString(R.string.use_external_video_player_key), false); |                 boolean isExtVideoEnabled = preferences.getBoolean(getString(R.string.use_external_video_player_key), false); | ||||||
|                 boolean isExtAudioEnabled = preferences.getBoolean(getString(R.string.use_external_audio_player_key), false);; |                 boolean isExtAudioEnabled = preferences.getBoolean(getString(R.string.use_external_audio_player_key), false); | ||||||
|  |                 ; | ||||||
|  |  | ||||||
|                 PlayQueue playQueue; |                 PlayQueue playQueue; | ||||||
|                 String playerChoice = choice.playerChoice; |                 String playerChoice = choice.playerChoice; | ||||||
| @@ -574,7 +581,7 @@ public class RouterActivity extends AppCompatActivity { | |||||||
|                         playQueue = new SinglePlayQueue((StreamInfo) info); |                         playQueue = new SinglePlayQueue((StreamInfo) info); | ||||||
|  |  | ||||||
|                         if (playerChoice.equals(videoPlayerKey)) { |                         if (playerChoice.equals(videoPlayerKey)) { | ||||||
|                             NavigationHelper.playOnMainPlayer(this, playQueue); |                             NavigationHelper.playOnMainPlayer(this, playQueue, true); | ||||||
|                         } else if (playerChoice.equals(backgroundPlayerKey)) { |                         } else if (playerChoice.equals(backgroundPlayerKey)) { | ||||||
|                             NavigationHelper.enqueueOnBackgroundPlayer(this, playQueue, true); |                             NavigationHelper.enqueueOnBackgroundPlayer(this, playQueue, true); | ||||||
|                         } else if (playerChoice.equals(popupPlayerKey)) { |                         } else if (playerChoice.equals(popupPlayerKey)) { | ||||||
| @@ -587,11 +594,11 @@ public class RouterActivity extends AppCompatActivity { | |||||||
|                     playQueue = info instanceof ChannelInfo ? new ChannelPlayQueue((ChannelInfo) info) : new PlaylistPlayQueue((PlaylistInfo) info); |                     playQueue = info instanceof ChannelInfo ? new ChannelPlayQueue((ChannelInfo) info) : new PlaylistPlayQueue((PlaylistInfo) info); | ||||||
|  |  | ||||||
|                     if (playerChoice.equals(videoPlayerKey)) { |                     if (playerChoice.equals(videoPlayerKey)) { | ||||||
|                         NavigationHelper.playOnMainPlayer(this, playQueue); |                         NavigationHelper.playOnMainPlayer(this, playQueue, true); | ||||||
|                     } else if (playerChoice.equals(backgroundPlayerKey)) { |                     } else if (playerChoice.equals(backgroundPlayerKey)) { | ||||||
|                         NavigationHelper.playOnBackgroundPlayer(this, playQueue); |                         NavigationHelper.playOnBackgroundPlayer(this, playQueue, true); | ||||||
|                     } else if (playerChoice.equals(popupPlayerKey)) { |                     } else if (playerChoice.equals(popupPlayerKey)) { | ||||||
|                         NavigationHelper.playOnPopupPlayer(this, playQueue); |                         NavigationHelper.playOnPopupPlayer(this, playQueue, true); | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             }; |             }; | ||||||
|   | |||||||
| @@ -4,13 +4,15 @@ import android.content.Context; | |||||||
| import android.content.Intent; | import android.content.Intent; | ||||||
| import android.net.Uri; | import android.net.Uri; | ||||||
| import android.os.Bundle; | import android.os.Bundle; | ||||||
| import android.support.design.widget.TabLayout; | import com.google.android.material.tabs.TabLayout; | ||||||
| import android.support.v4.app.Fragment; | import androidx.fragment.app.Fragment; | ||||||
| import android.support.v4.app.FragmentManager; | import androidx.fragment.app.FragmentManager; | ||||||
| import android.support.v4.app.FragmentPagerAdapter; | import androidx.fragment.app.FragmentPagerAdapter; | ||||||
| import android.support.v4.view.ViewPager; | import androidx.fragment.app.FragmentStatePagerAdapter; | ||||||
| import android.support.v7.app.AppCompatActivity; | import androidx.viewpager.widget.PagerAdapter; | ||||||
| import android.support.v7.widget.Toolbar; | import androidx.viewpager.widget.ViewPager; | ||||||
|  | import androidx.appcompat.app.AppCompatActivity; | ||||||
|  | import androidx.appcompat.widget.Toolbar; | ||||||
| import android.view.LayoutInflater; | import android.view.LayoutInflater; | ||||||
| import android.view.Menu; | import android.view.Menu; | ||||||
| import android.view.MenuItem; | import android.view.MenuItem; | ||||||
| @@ -36,7 +38,6 @@ public class AboutActivity extends AppCompatActivity { | |||||||
|             new SoftwareComponent("ACRA", "2013", "Kevin Gaudin", "http://www.acra.ch", StandardLicenses.APACHE2), |             new SoftwareComponent("ACRA", "2013", "Kevin Gaudin", "http://www.acra.ch", StandardLicenses.APACHE2), | ||||||
|             new SoftwareComponent("Universal Image Loader", "2011 - 2015", "Sergey Tarasevich", "https://github.com/nostra13/Android-Universal-Image-Loader", StandardLicenses.APACHE2), |             new SoftwareComponent("Universal Image Loader", "2011 - 2015", "Sergey Tarasevich", "https://github.com/nostra13/Android-Universal-Image-Loader", StandardLicenses.APACHE2), | ||||||
|             new SoftwareComponent("CircleImageView", "2014 - 2017", "Henning Dodenhof", "https://github.com/hdodenhof/CircleImageView", StandardLicenses.APACHE2), |             new SoftwareComponent("CircleImageView", "2014 - 2017", "Henning Dodenhof", "https://github.com/hdodenhof/CircleImageView", StandardLicenses.APACHE2), | ||||||
|             new SoftwareComponent("ParalaxScrollView", "2014", "Nir Hartmann", "https://github.com/nirhart/ParallaxScroll", StandardLicenses.MIT), |  | ||||||
|             new SoftwareComponent("NoNonsense-FilePicker", "2016", "Jonas Kalderstam", "https://github.com/spacecowboy/NoNonsense-FilePicker", StandardLicenses.MPL2), |             new SoftwareComponent("NoNonsense-FilePicker", "2016", "Jonas Kalderstam", "https://github.com/spacecowboy/NoNonsense-FilePicker", StandardLicenses.MPL2), | ||||||
|             new SoftwareComponent("ExoPlayer", "2014-2017", "Google Inc", "https://github.com/google/ExoPlayer", StandardLicenses.APACHE2), |             new SoftwareComponent("ExoPlayer", "2014-2017", "Google Inc", "https://github.com/google/ExoPlayer", StandardLicenses.APACHE2), | ||||||
|             new SoftwareComponent("RxAndroid", "2015", "The RxAndroid authors", "https://github.com/ReactiveX/RxAndroid", StandardLicenses.APACHE2), |             new SoftwareComponent("RxAndroid", "2015", "The RxAndroid authors", "https://github.com/ReactiveX/RxAndroid", StandardLicenses.APACHE2), | ||||||
| @@ -45,12 +46,12 @@ public class AboutActivity extends AppCompatActivity { | |||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * The {@link android.support.v4.view.PagerAdapter} that will provide |      * The {@link PagerAdapter} that will provide | ||||||
|      * fragments for each of the sections. We use a |      * fragments for each of the sections. We use a | ||||||
|      * {@link FragmentPagerAdapter} derivative, which will keep every |      * {@link FragmentPagerAdapter} derivative, which will keep every | ||||||
|      * loaded fragment in memory. If this becomes too memory intensive, it |      * loaded fragment in memory. If this becomes too memory intensive, it | ||||||
|      * may be best to switch to a |      * may be best to switch to a | ||||||
|      * {@link android.support.v4.app.FragmentStatePagerAdapter}. |      * {@link FragmentStatePagerAdapter}. | ||||||
|      */ |      */ | ||||||
|     private SectionsPagerAdapter mSectionsPagerAdapter; |     private SectionsPagerAdapter mSectionsPagerAdapter; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -5,8 +5,8 @@ import android.content.Context; | |||||||
| import android.content.Intent; | import android.content.Intent; | ||||||
| import android.net.Uri; | import android.net.Uri; | ||||||
| import android.os.Bundle; | import android.os.Bundle; | ||||||
| import android.support.annotation.Nullable; | import androidx.annotation.Nullable; | ||||||
| import android.support.v4.app.Fragment; | import androidx.fragment.app.Fragment; | ||||||
| import android.view.*; | import android.view.*; | ||||||
| import android.widget.TextView; | import android.widget.TextView; | ||||||
| import org.schabi.newpipe.R; | import org.schabi.newpipe.R; | ||||||
|   | |||||||
| @@ -4,8 +4,8 @@ import android.app.Activity; | |||||||
| import android.content.Context; | import android.content.Context; | ||||||
| import android.content.DialogInterface; | import android.content.DialogInterface; | ||||||
| import android.os.AsyncTask; | import android.os.AsyncTask; | ||||||
| import android.support.annotation.Nullable; | import androidx.annotation.Nullable; | ||||||
| import android.support.v7.app.AlertDialog; | import androidx.appcompat.app.AlertDialog; | ||||||
| import android.webkit.WebView; | import android.webkit.WebView; | ||||||
| import org.schabi.newpipe.R; | import org.schabi.newpipe.R; | ||||||
| import org.schabi.newpipe.util.ThemeHelper; | import org.schabi.newpipe.util.ThemeHelper; | ||||||
|   | |||||||
| @@ -1,8 +1,8 @@ | |||||||
| package org.schabi.newpipe.database; | package org.schabi.newpipe.database; | ||||||
|  |  | ||||||
| import android.arch.persistence.room.Database; | import androidx.room.Database; | ||||||
| import android.arch.persistence.room.RoomDatabase; | import androidx.room.RoomDatabase; | ||||||
| import android.arch.persistence.room.TypeConverters; | import androidx.room.TypeConverters; | ||||||
|  |  | ||||||
| import org.schabi.newpipe.database.history.dao.SearchHistoryDAO; | import org.schabi.newpipe.database.history.dao.SearchHistoryDAO; | ||||||
| import org.schabi.newpipe.database.history.dao.StreamHistoryDAO; | import org.schabi.newpipe.database.history.dao.StreamHistoryDAO; | ||||||
|   | |||||||
| @@ -1,10 +1,10 @@ | |||||||
| package org.schabi.newpipe.database; | package org.schabi.newpipe.database; | ||||||
|  |  | ||||||
| import android.arch.persistence.room.Dao; | import androidx.room.Dao; | ||||||
| import android.arch.persistence.room.Delete; | import androidx.room.Delete; | ||||||
| import android.arch.persistence.room.Insert; | import androidx.room.Insert; | ||||||
| import android.arch.persistence.room.OnConflictStrategy; | import androidx.room.OnConflictStrategy; | ||||||
| import android.arch.persistence.room.Update; | import androidx.room.Update; | ||||||
|  |  | ||||||
| import java.util.Collection; | import java.util.Collection; | ||||||
| import java.util.List; | import java.util.List; | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| package org.schabi.newpipe.database; | package org.schabi.newpipe.database; | ||||||
|  |  | ||||||
| import android.arch.persistence.room.TypeConverter; | import androidx.room.TypeConverter; | ||||||
|  |  | ||||||
| import org.schabi.newpipe.extractor.stream.StreamType; | import org.schabi.newpipe.extractor.stream.StreamType; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,17 +1,26 @@ | |||||||
| package org.schabi.newpipe.database; | package org.schabi.newpipe.database; | ||||||
|  |  | ||||||
| import android.arch.persistence.db.SupportSQLiteDatabase; | import androidx.sqlite.db.SupportSQLiteDatabase; | ||||||
| import android.arch.persistence.room.migration.Migration; | import androidx.room.migration.Migration; | ||||||
| import android.support.annotation.NonNull; | import androidx.annotation.NonNull; | ||||||
|  | import android.util.Log; | ||||||
|  |  | ||||||
|  | import org.schabi.newpipe.BuildConfig; | ||||||
|  |  | ||||||
| public class Migrations { | public class Migrations { | ||||||
|  |  | ||||||
|     public static final int DB_VER_11_0 = 1; |     public static final int DB_VER_11_0 = 1; | ||||||
|     public static final int DB_VER_12_0 = 2; |     public static final int DB_VER_12_0 = 2; | ||||||
|  |  | ||||||
|  |     public static final boolean DEBUG = !BuildConfig.BUILD_TYPE.equals("release"); | ||||||
|  |     private static final String TAG = Migrations.class.getName(); | ||||||
|  |  | ||||||
|     public static final Migration MIGRATION_11_12 = new Migration(DB_VER_11_0, DB_VER_12_0) { |     public static final Migration MIGRATION_11_12 = new Migration(DB_VER_11_0, DB_VER_12_0) { | ||||||
|         @Override |         @Override | ||||||
|         public void migrate(@NonNull SupportSQLiteDatabase database) { |         public void migrate(@NonNull SupportSQLiteDatabase database) { | ||||||
|  |             if(DEBUG) { | ||||||
|  |                 Log.d(TAG, "Start migrating database"); | ||||||
|  |             } | ||||||
|             /* |             /* | ||||||
|             * Unfortunately these queries must be hardcoded due to the possibility of |             * Unfortunately these queries must be hardcoded due to the possibility of | ||||||
|             * schema and names changing at a later date, thus invalidating the older migration |             * schema and names changing at a later date, thus invalidating the older migration | ||||||
| @@ -56,6 +65,10 @@ public class Migrations { | |||||||
|                     "ORDER BY creation_date DESC"); |                     "ORDER BY creation_date DESC"); | ||||||
|  |  | ||||||
|             database.execSQL("DROP TABLE IF EXISTS watch_history"); |             database.execSQL("DROP TABLE IF EXISTS watch_history"); | ||||||
|  |  | ||||||
|  |             if(DEBUG) { | ||||||
|  |                 Log.d(TAG, "Stop migrating database"); | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,8 +1,8 @@ | |||||||
| package org.schabi.newpipe.database.history.dao; | package org.schabi.newpipe.database.history.dao; | ||||||
|  |  | ||||||
| import android.arch.persistence.room.Dao; | import androidx.room.Dao; | ||||||
| import android.arch.persistence.room.Query; | import androidx.room.Query; | ||||||
| import android.support.annotation.Nullable; | import androidx.annotation.Nullable; | ||||||
|  |  | ||||||
| import org.schabi.newpipe.database.history.model.SearchHistoryEntry; | import org.schabi.newpipe.database.history.model.SearchHistoryEntry; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,9 +1,9 @@ | |||||||
| package org.schabi.newpipe.database.history.dao; | package org.schabi.newpipe.database.history.dao; | ||||||
|  |  | ||||||
|  |  | ||||||
| import android.arch.persistence.room.Dao; | import androidx.room.Dao; | ||||||
| import android.arch.persistence.room.Query; | import androidx.room.Query; | ||||||
| import android.support.annotation.Nullable; | import androidx.annotation.Nullable; | ||||||
|  |  | ||||||
| import org.schabi.newpipe.database.history.model.StreamHistoryEntry; | import org.schabi.newpipe.database.history.model.StreamHistoryEntry; | ||||||
| import org.schabi.newpipe.database.stream.StreamStatisticsEntry; | import org.schabi.newpipe.database.stream.StreamStatisticsEntry; | ||||||
| @@ -50,6 +50,11 @@ public abstract class StreamHistoryDAO implements HistoryDAO<StreamHistoryEntity | |||||||
|             " ORDER BY " + STREAM_ACCESS_DATE + " DESC") |             " ORDER BY " + STREAM_ACCESS_DATE + " DESC") | ||||||
|     public abstract Flowable<List<StreamHistoryEntry>> getHistory(); |     public abstract Flowable<List<StreamHistoryEntry>> getHistory(); | ||||||
|  |  | ||||||
|  |     @Query("SELECT * FROM " + STREAM_HISTORY_TABLE + " WHERE " + JOIN_STREAM_ID + | ||||||
|  |             " = :streamId ORDER BY " + STREAM_ACCESS_DATE + " DESC LIMIT 1") | ||||||
|  |     @Nullable | ||||||
|  |     public abstract StreamHistoryEntity getLatestEntry(final long streamId); | ||||||
|  |  | ||||||
|     @Query("DELETE FROM " + STREAM_HISTORY_TABLE + " WHERE " + JOIN_STREAM_ID + " = :streamId") |     @Query("DELETE FROM " + STREAM_HISTORY_TABLE + " WHERE " + JOIN_STREAM_ID + " = :streamId") | ||||||
|     public abstract int deleteStreamHistory(final long streamId); |     public abstract int deleteStreamHistory(final long streamId); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,10 +1,10 @@ | |||||||
| package org.schabi.newpipe.database.history.model; | package org.schabi.newpipe.database.history.model; | ||||||
|  |  | ||||||
| import android.arch.persistence.room.ColumnInfo; | import androidx.room.ColumnInfo; | ||||||
| import android.arch.persistence.room.Entity; | import androidx.room.Entity; | ||||||
| import android.arch.persistence.room.Ignore; | import androidx.room.Ignore; | ||||||
| import android.arch.persistence.room.Index; | import androidx.room.Index; | ||||||
| import android.arch.persistence.room.PrimaryKey; | import androidx.room.PrimaryKey; | ||||||
|  |  | ||||||
| import java.util.Date; | import java.util.Date; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,17 +1,17 @@ | |||||||
| package org.schabi.newpipe.database.history.model; | package org.schabi.newpipe.database.history.model; | ||||||
|  |  | ||||||
| import android.arch.persistence.room.ColumnInfo; | import androidx.room.ColumnInfo; | ||||||
| import android.arch.persistence.room.Entity; | import androidx.room.Entity; | ||||||
| import android.arch.persistence.room.ForeignKey; | import androidx.room.ForeignKey; | ||||||
| import android.arch.persistence.room.Ignore; | import androidx.room.Ignore; | ||||||
| import android.arch.persistence.room.Index; | import androidx.room.Index; | ||||||
| import android.support.annotation.NonNull; | import androidx.annotation.NonNull; | ||||||
|  |  | ||||||
| import org.schabi.newpipe.database.stream.model.StreamEntity; | import org.schabi.newpipe.database.stream.model.StreamEntity; | ||||||
|  |  | ||||||
| import java.util.Date; | import java.util.Date; | ||||||
|  |  | ||||||
| import static android.arch.persistence.room.ForeignKey.CASCADE; | import static androidx.room.ForeignKey.CASCADE; | ||||||
| import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STREAM_HISTORY_TABLE; | import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STREAM_HISTORY_TABLE; | ||||||
| import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.JOIN_STREAM_ID; | import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.JOIN_STREAM_ID; | ||||||
| import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STREAM_ACCESS_DATE; | import static org.schabi.newpipe.database.history.model.StreamHistoryEntity.STREAM_ACCESS_DATE; | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| package org.schabi.newpipe.database.history.model; | package org.schabi.newpipe.database.history.model; | ||||||
|  |  | ||||||
| import android.arch.persistence.room.ColumnInfo; | import androidx.room.ColumnInfo; | ||||||
|  |  | ||||||
| import org.schabi.newpipe.database.stream.model.StreamEntity; | import org.schabi.newpipe.database.stream.model.StreamEntity; | ||||||
| import org.schabi.newpipe.extractor.stream.StreamType; | import org.schabi.newpipe.extractor.stream.StreamType; | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| package org.schabi.newpipe.database.playlist; | package org.schabi.newpipe.database.playlist; | ||||||
|  |  | ||||||
| import android.arch.persistence.room.ColumnInfo; | import androidx.room.ColumnInfo; | ||||||
|  |  | ||||||
| import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_ID; | import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_ID; | ||||||
| import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_NAME; | import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_NAME; | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| package org.schabi.newpipe.database.playlist; | package org.schabi.newpipe.database.playlist; | ||||||
|  |  | ||||||
| import android.arch.persistence.room.ColumnInfo; | import androidx.room.ColumnInfo; | ||||||
|  |  | ||||||
| import org.schabi.newpipe.database.LocalItem; | import org.schabi.newpipe.database.LocalItem; | ||||||
| import org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity; | import org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity; | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| package org.schabi.newpipe.database.playlist.dao; | package org.schabi.newpipe.database.playlist.dao; | ||||||
|  |  | ||||||
| import android.arch.persistence.room.Dao; | import androidx.room.Dao; | ||||||
| import android.arch.persistence.room.Query; | import androidx.room.Query; | ||||||
|  |  | ||||||
| import org.schabi.newpipe.database.BasicDAO; | import org.schabi.newpipe.database.BasicDAO; | ||||||
| import org.schabi.newpipe.database.playlist.model.PlaylistEntity; | import org.schabi.newpipe.database.playlist.model.PlaylistEntity; | ||||||
|   | |||||||
| @@ -1,8 +1,8 @@ | |||||||
| package org.schabi.newpipe.database.playlist.dao; | package org.schabi.newpipe.database.playlist.dao; | ||||||
|  |  | ||||||
| import android.arch.persistence.room.Dao; | import androidx.room.Dao; | ||||||
| import android.arch.persistence.room.Query; | import androidx.room.Query; | ||||||
| import android.arch.persistence.room.Transaction; | import androidx.room.Transaction; | ||||||
|  |  | ||||||
| import org.schabi.newpipe.database.BasicDAO; | import org.schabi.newpipe.database.BasicDAO; | ||||||
| import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity; | import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity; | ||||||
|   | |||||||
| @@ -1,8 +1,8 @@ | |||||||
| package org.schabi.newpipe.database.playlist.dao; | package org.schabi.newpipe.database.playlist.dao; | ||||||
|  |  | ||||||
| import android.arch.persistence.room.Dao; | import androidx.room.Dao; | ||||||
| import android.arch.persistence.room.Query; | import androidx.room.Query; | ||||||
| import android.arch.persistence.room.Transaction; | import androidx.room.Transaction; | ||||||
|  |  | ||||||
| import org.schabi.newpipe.database.BasicDAO; | import org.schabi.newpipe.database.BasicDAO; | ||||||
| import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry; | import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry; | ||||||
|   | |||||||
| @@ -1,9 +1,9 @@ | |||||||
| package org.schabi.newpipe.database.playlist.model; | package org.schabi.newpipe.database.playlist.model; | ||||||
|  |  | ||||||
| import android.arch.persistence.room.ColumnInfo; | import androidx.room.ColumnInfo; | ||||||
| import android.arch.persistence.room.Entity; | import androidx.room.Entity; | ||||||
| import android.arch.persistence.room.Index; | import androidx.room.Index; | ||||||
| import android.arch.persistence.room.PrimaryKey; | import androidx.room.PrimaryKey; | ||||||
|  |  | ||||||
| import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_NAME; | import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_NAME; | ||||||
| import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_TABLE; | import static org.schabi.newpipe.database.playlist.model.PlaylistEntity.PLAYLIST_TABLE; | ||||||
|   | |||||||
| @@ -1,10 +1,10 @@ | |||||||
| package org.schabi.newpipe.database.playlist.model; | package org.schabi.newpipe.database.playlist.model; | ||||||
|  |  | ||||||
| import android.arch.persistence.room.ColumnInfo; | import androidx.room.ColumnInfo; | ||||||
| import android.arch.persistence.room.Entity; | import androidx.room.Entity; | ||||||
| import android.arch.persistence.room.Ignore; | import androidx.room.Ignore; | ||||||
| import android.arch.persistence.room.Index; | import androidx.room.Index; | ||||||
| import android.arch.persistence.room.PrimaryKey; | import androidx.room.PrimaryKey; | ||||||
|  |  | ||||||
| import org.schabi.newpipe.database.playlist.PlaylistLocalItem; | import org.schabi.newpipe.database.playlist.PlaylistLocalItem; | ||||||
| import org.schabi.newpipe.extractor.playlist.PlaylistInfo; | import org.schabi.newpipe.extractor.playlist.PlaylistInfo; | ||||||
|   | |||||||
| @@ -1,13 +1,13 @@ | |||||||
| package org.schabi.newpipe.database.playlist.model; | package org.schabi.newpipe.database.playlist.model; | ||||||
|  |  | ||||||
| import android.arch.persistence.room.ColumnInfo; | import androidx.room.ColumnInfo; | ||||||
| import android.arch.persistence.room.Entity; | import androidx.room.Entity; | ||||||
| import android.arch.persistence.room.ForeignKey; | import androidx.room.ForeignKey; | ||||||
| import android.arch.persistence.room.Index; | import androidx.room.Index; | ||||||
|  |  | ||||||
| import org.schabi.newpipe.database.stream.model.StreamEntity; | import org.schabi.newpipe.database.stream.model.StreamEntity; | ||||||
|  |  | ||||||
| import static android.arch.persistence.room.ForeignKey.CASCADE; | import static androidx.room.ForeignKey.CASCADE; | ||||||
| import static org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity.JOIN_INDEX; | import static org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity.JOIN_INDEX; | ||||||
| import static org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity.JOIN_PLAYLIST_ID; | import static org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity.JOIN_PLAYLIST_ID; | ||||||
| import static org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity.JOIN_STREAM_ID; | import static org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity.JOIN_STREAM_ID; | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| package org.schabi.newpipe.database.stream; | package org.schabi.newpipe.database.stream; | ||||||
|  |  | ||||||
| import android.arch.persistence.room.ColumnInfo; | import androidx.room.ColumnInfo; | ||||||
|  |  | ||||||
| import org.schabi.newpipe.database.LocalItem; | import org.schabi.newpipe.database.LocalItem; | ||||||
| import org.schabi.newpipe.database.history.model.StreamHistoryEntity; | import org.schabi.newpipe.database.history.model.StreamHistoryEntity; | ||||||
|   | |||||||
| @@ -1,10 +1,10 @@ | |||||||
| package org.schabi.newpipe.database.stream.dao; | package org.schabi.newpipe.database.stream.dao; | ||||||
|  |  | ||||||
| import android.arch.persistence.room.Dao; | import androidx.room.Dao; | ||||||
| import android.arch.persistence.room.Insert; | import androidx.room.Insert; | ||||||
| import android.arch.persistence.room.OnConflictStrategy; | import androidx.room.OnConflictStrategy; | ||||||
| import android.arch.persistence.room.Query; | import androidx.room.Query; | ||||||
| import android.arch.persistence.room.Transaction; | import androidx.room.Transaction; | ||||||
|  |  | ||||||
| import org.schabi.newpipe.database.BasicDAO; | import org.schabi.newpipe.database.BasicDAO; | ||||||
| import org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity; | import org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity; | ||||||
|   | |||||||
| @@ -1,10 +1,10 @@ | |||||||
| package org.schabi.newpipe.database.stream.dao; | package org.schabi.newpipe.database.stream.dao; | ||||||
|  |  | ||||||
| import android.arch.persistence.room.Dao; | import androidx.room.Dao; | ||||||
| import android.arch.persistence.room.Insert; | import androidx.room.Insert; | ||||||
| import android.arch.persistence.room.OnConflictStrategy; | import androidx.room.OnConflictStrategy; | ||||||
| import android.arch.persistence.room.Query; | import androidx.room.Query; | ||||||
| import android.arch.persistence.room.Transaction; | import androidx.room.Transaction; | ||||||
|  |  | ||||||
| import org.schabi.newpipe.database.BasicDAO; | import org.schabi.newpipe.database.BasicDAO; | ||||||
| import org.schabi.newpipe.database.stream.model.StreamStateEntity; | import org.schabi.newpipe.database.stream.model.StreamStateEntity; | ||||||
|   | |||||||
| @@ -1,10 +1,10 @@ | |||||||
| package org.schabi.newpipe.database.stream.model; | package org.schabi.newpipe.database.stream.model; | ||||||
|  |  | ||||||
| import android.arch.persistence.room.ColumnInfo; | import androidx.room.ColumnInfo; | ||||||
| import android.arch.persistence.room.Entity; | import androidx.room.Entity; | ||||||
| import android.arch.persistence.room.Ignore; | import androidx.room.Ignore; | ||||||
| import android.arch.persistence.room.Index; | import androidx.room.Index; | ||||||
| import android.arch.persistence.room.PrimaryKey; | import androidx.room.PrimaryKey; | ||||||
|  |  | ||||||
| import org.schabi.newpipe.extractor.stream.StreamInfo; | import org.schabi.newpipe.extractor.stream.StreamInfo; | ||||||
| import org.schabi.newpipe.extractor.stream.StreamInfoItem; | import org.schabi.newpipe.extractor.stream.StreamInfoItem; | ||||||
|   | |||||||
| @@ -1,11 +1,14 @@ | |||||||
| package org.schabi.newpipe.database.stream.model; | package org.schabi.newpipe.database.stream.model; | ||||||
|  |  | ||||||
|  |  | ||||||
| import android.arch.persistence.room.ColumnInfo; | import androidx.room.ColumnInfo; | ||||||
| import android.arch.persistence.room.Entity; | import androidx.room.Entity; | ||||||
| import android.arch.persistence.room.ForeignKey; | import androidx.room.ForeignKey; | ||||||
|  | import androidx.annotation.Nullable; | ||||||
|  |  | ||||||
| import static android.arch.persistence.room.ForeignKey.CASCADE; | import java.util.concurrent.TimeUnit; | ||||||
|  |  | ||||||
|  | import static androidx.room.ForeignKey.CASCADE; | ||||||
| import static org.schabi.newpipe.database.stream.model.StreamStateEntity.JOIN_STREAM_ID; | import static org.schabi.newpipe.database.stream.model.StreamStateEntity.JOIN_STREAM_ID; | ||||||
| import static org.schabi.newpipe.database.stream.model.StreamStateEntity.STREAM_STATE_TABLE; | import static org.schabi.newpipe.database.stream.model.StreamStateEntity.STREAM_STATE_TABLE; | ||||||
|  |  | ||||||
| @@ -22,6 +25,12 @@ public class StreamStateEntity { | |||||||
|     final public static String JOIN_STREAM_ID       = "stream_id"; |     final public static String JOIN_STREAM_ID       = "stream_id"; | ||||||
|     final public static String STREAM_PROGRESS_TIME = "progress_time"; |     final public static String STREAM_PROGRESS_TIME = "progress_time"; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     /** Playback state will not be saved, if playback time less than this threshold */ | ||||||
|  |     private static final int PLAYBACK_SAVE_THRESHOLD_START_SECONDS = 5; | ||||||
|  |     /** Playback state will not be saved, if time left less than this threshold */ | ||||||
|  |     private static final int PLAYBACK_SAVE_THRESHOLD_END_SECONDS = 10; | ||||||
|  |  | ||||||
|     @ColumnInfo(name = JOIN_STREAM_ID) |     @ColumnInfo(name = JOIN_STREAM_ID) | ||||||
|     private long streamUid; |     private long streamUid; | ||||||
|  |  | ||||||
| @@ -48,4 +57,18 @@ public class StreamStateEntity { | |||||||
|     public void setProgressTime(long progressTime) { |     public void setProgressTime(long progressTime) { | ||||||
|         this.progressTime = progressTime; |         this.progressTime = progressTime; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     public boolean isValid(int durationInSeconds) { | ||||||
|  |         final int seconds = (int) TimeUnit.MILLISECONDS.toSeconds(progressTime); | ||||||
|  |         return seconds > PLAYBACK_SAVE_THRESHOLD_START_SECONDS | ||||||
|  |                 && seconds < durationInSeconds - PLAYBACK_SAVE_THRESHOLD_END_SECONDS; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public boolean equals(@Nullable Object obj) { | ||||||
|  |         if (obj instanceof StreamStateEntity) { | ||||||
|  |             return ((StreamStateEntity) obj).streamUid == streamUid | ||||||
|  |                     && ((StreamStateEntity) obj).progressTime == progressTime; | ||||||
|  |         } else return false; | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,10 +1,10 @@ | |||||||
| package org.schabi.newpipe.database.subscription; | package org.schabi.newpipe.database.subscription; | ||||||
|  |  | ||||||
| import android.arch.persistence.room.Dao; | import androidx.room.Dao; | ||||||
| import android.arch.persistence.room.Insert; | import androidx.room.Insert; | ||||||
| import android.arch.persistence.room.OnConflictStrategy; | import androidx.room.OnConflictStrategy; | ||||||
| import android.arch.persistence.room.Query; | import androidx.room.Query; | ||||||
| import android.arch.persistence.room.Transaction; | import androidx.room.Transaction; | ||||||
|  |  | ||||||
| import org.schabi.newpipe.database.BasicDAO; | import org.schabi.newpipe.database.BasicDAO; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,11 +1,11 @@ | |||||||
| package org.schabi.newpipe.database.subscription; | package org.schabi.newpipe.database.subscription; | ||||||
|  |  | ||||||
| import android.arch.persistence.room.ColumnInfo; | import androidx.room.ColumnInfo; | ||||||
| import android.arch.persistence.room.Entity; | import androidx.room.Entity; | ||||||
| import android.arch.persistence.room.Ignore; | import androidx.room.Ignore; | ||||||
| import android.arch.persistence.room.Index; | import androidx.room.Index; | ||||||
| import android.arch.persistence.room.PrimaryKey; | import androidx.room.PrimaryKey; | ||||||
| import android.support.annotation.NonNull; | import androidx.annotation.NonNull; | ||||||
|  |  | ||||||
| import org.schabi.newpipe.extractor.channel.ChannelInfo; | import org.schabi.newpipe.extractor.channel.ChannelInfo; | ||||||
| import org.schabi.newpipe.extractor.channel.ChannelInfoItem; | import org.schabi.newpipe.extractor.channel.ChannelInfoItem; | ||||||
|   | |||||||
| @@ -3,9 +3,9 @@ package org.schabi.newpipe.download; | |||||||
| import android.app.FragmentTransaction; | import android.app.FragmentTransaction; | ||||||
| import android.content.Intent; | import android.content.Intent; | ||||||
| import android.os.Bundle; | import android.os.Bundle; | ||||||
| import android.support.v7.app.ActionBar; | import androidx.appcompat.app.ActionBar; | ||||||
| import android.support.v7.app.AppCompatActivity; | import androidx.appcompat.app.AppCompatActivity; | ||||||
| import android.support.v7.widget.Toolbar; | import androidx.appcompat.widget.Toolbar; | ||||||
| import android.view.Menu; | import android.view.Menu; | ||||||
| import android.view.MenuInflater; | import android.view.MenuInflater; | ||||||
| import android.view.MenuItem; | import android.view.MenuItem; | ||||||
| @@ -47,7 +47,7 @@ public class DownloadActivity extends AppCompatActivity { | |||||||
|             @Override |             @Override | ||||||
|             public void onGlobalLayout() { |             public void onGlobalLayout() { | ||||||
|                 updateFragments(); |                 updateFragments(); | ||||||
|                 getWindow().getDecorView().getViewTreeObserver().removeGlobalOnLayoutListener(this); |                 getWindow().getDecorView().getViewTreeObserver().removeOnGlobalLayoutListener(this); | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| @@ -55,12 +55,13 @@ public class DownloadActivity extends AppCompatActivity { | |||||||
|     private void updateFragments() { |     private void updateFragments() { | ||||||
|         MissionsFragment fragment = new MissionsFragment(); |         MissionsFragment fragment = new MissionsFragment(); | ||||||
|  |  | ||||||
|         getFragmentManager().beginTransaction() |         getSupportFragmentManager().beginTransaction() | ||||||
|                 .replace(R.id.frame, fragment, MISSIONS_FRAGMENT_TAG) |                 .replace(R.id.frame, fragment, MISSIONS_FRAGMENT_TAG) | ||||||
|                 .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE) |                 .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE) | ||||||
|                 .commit(); |                 .commit(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|     public boolean onCreateOptionsMenu(Menu menu) { |     public boolean onCreateOptionsMenu(Menu menu) { | ||||||
|         super.onCreateOptionsMenu(menu); |         super.onCreateOptionsMenu(menu); | ||||||
|         MenuInflater inflater = getMenuInflater(); |         MenuInflater inflater = getMenuInflater(); | ||||||
| @@ -86,9 +87,4 @@ public class DownloadActivity extends AppCompatActivity { | |||||||
|                 return super.onOptionsItemSelected(item); |                 return super.onOptionsItemSelected(item); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void onRestoreInstanceState(Bundle inState){ |  | ||||||
|         super.onRestoreInstanceState(inState); |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,15 +1,25 @@ | |||||||
| package org.schabi.newpipe.download; | package org.schabi.newpipe.download; | ||||||
|  |  | ||||||
|  | import android.app.Activity; | ||||||
|  | import android.content.ComponentName; | ||||||
| import android.content.Context; | import android.content.Context; | ||||||
|  | import android.content.Intent; | ||||||
|  | import android.content.ServiceConnection; | ||||||
| import android.content.SharedPreferences; | import android.content.SharedPreferences; | ||||||
|  | import android.net.Uri; | ||||||
| import android.os.Bundle; | import android.os.Bundle; | ||||||
|  | import android.os.Environment; | ||||||
|  | import android.os.IBinder; | ||||||
| import android.preference.PreferenceManager; | import android.preference.PreferenceManager; | ||||||
| import android.support.annotation.IdRes; | import androidx.annotation.IdRes; | ||||||
| import android.support.annotation.NonNull; | import androidx.annotation.NonNull; | ||||||
| import android.support.annotation.Nullable; | import androidx.annotation.Nullable; | ||||||
| import android.support.v4.app.DialogFragment; | import androidx.annotation.StringRes; | ||||||
| import android.support.v7.app.AlertDialog; | import androidx.fragment.app.DialogFragment; | ||||||
| import android.support.v7.widget.Toolbar; | import androidx.documentfile.provider.DocumentFile; | ||||||
|  | import androidx.appcompat.app.AlertDialog; | ||||||
|  | import androidx.appcompat.view.menu.ActionMenuItemView; | ||||||
|  | import androidx.appcompat.widget.Toolbar; | ||||||
| import android.util.Log; | import android.util.Log; | ||||||
| import android.util.SparseArray; | import android.util.SparseArray; | ||||||
| import android.view.LayoutInflater; | import android.view.LayoutInflater; | ||||||
| @@ -24,6 +34,8 @@ import android.widget.Spinner; | |||||||
| import android.widget.TextView; | import android.widget.TextView; | ||||||
| import android.widget.Toast; | import android.widget.Toast; | ||||||
|  |  | ||||||
|  | import com.nononsenseapps.filepicker.Utils; | ||||||
|  |  | ||||||
| import org.schabi.newpipe.MainActivity; | import org.schabi.newpipe.MainActivity; | ||||||
| import org.schabi.newpipe.R; | import org.schabi.newpipe.R; | ||||||
| import org.schabi.newpipe.extractor.MediaFormat; | import org.schabi.newpipe.extractor.MediaFormat; | ||||||
| @@ -34,7 +46,10 @@ import org.schabi.newpipe.extractor.stream.StreamInfo; | |||||||
| import org.schabi.newpipe.extractor.stream.SubtitlesStream; | import org.schabi.newpipe.extractor.stream.SubtitlesStream; | ||||||
| import org.schabi.newpipe.extractor.stream.VideoStream; | import org.schabi.newpipe.extractor.stream.VideoStream; | ||||||
| import org.schabi.newpipe.extractor.utils.Localization; | import org.schabi.newpipe.extractor.utils.Localization; | ||||||
|  | import org.schabi.newpipe.report.ErrorActivity; | ||||||
|  | import org.schabi.newpipe.report.UserAction; | ||||||
| import org.schabi.newpipe.settings.NewPipeSettings; | import org.schabi.newpipe.settings.NewPipeSettings; | ||||||
|  | import org.schabi.newpipe.util.FilePickerActivityHelper; | ||||||
| import org.schabi.newpipe.util.FilenameUtils; | import org.schabi.newpipe.util.FilenameUtils; | ||||||
| import org.schabi.newpipe.util.ListHelper; | import org.schabi.newpipe.util.ListHelper; | ||||||
| import org.schabi.newpipe.util.PermissionHelper; | import org.schabi.newpipe.util.PermissionHelper; | ||||||
| @@ -43,19 +58,28 @@ import org.schabi.newpipe.util.StreamItemAdapter; | |||||||
| import org.schabi.newpipe.util.StreamItemAdapter.StreamSizeWrapper; | import org.schabi.newpipe.util.StreamItemAdapter.StreamSizeWrapper; | ||||||
| import org.schabi.newpipe.util.ThemeHelper; | import org.schabi.newpipe.util.ThemeHelper; | ||||||
|  |  | ||||||
|  | import java.io.File; | ||||||
|  | import java.io.IOException; | ||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
|  | import java.util.Collections; | ||||||
| import java.util.List; | import java.util.List; | ||||||
| import java.util.Locale; | import java.util.Locale; | ||||||
|  |  | ||||||
| import icepick.Icepick; | import icepick.Icepick; | ||||||
| import icepick.State; | import icepick.State; | ||||||
| import io.reactivex.disposables.CompositeDisposable; | import io.reactivex.disposables.CompositeDisposable; | ||||||
|  | import us.shandian.giga.io.StoredDirectoryHelper; | ||||||
|  | import us.shandian.giga.io.StoredFileHelper; | ||||||
| import us.shandian.giga.postprocessing.Postprocessing; | import us.shandian.giga.postprocessing.Postprocessing; | ||||||
|  | import us.shandian.giga.service.DownloadManager; | ||||||
| import us.shandian.giga.service.DownloadManagerService; | import us.shandian.giga.service.DownloadManagerService; | ||||||
|  | import us.shandian.giga.service.DownloadManagerService.DownloadManagerBinder; | ||||||
|  | import us.shandian.giga.service.MissionState; | ||||||
|  |  | ||||||
| public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheckedChangeListener, AdapterView.OnItemSelectedListener { | public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheckedChangeListener, AdapterView.OnItemSelectedListener { | ||||||
|     private static final String TAG = "DialogFragment"; |     private static final String TAG = "DialogFragment"; | ||||||
|     private static final boolean DEBUG = MainActivity.DEBUG; |     private static final boolean DEBUG = MainActivity.DEBUG; | ||||||
|  |     private static final int REQUEST_DOWNLOAD_SAVE_AS = 0x1230; | ||||||
|  |  | ||||||
|     @State |     @State | ||||||
|     protected StreamInfo currentInfo; |     protected StreamInfo currentInfo; | ||||||
| @@ -80,7 +104,7 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck | |||||||
|  |  | ||||||
|     private EditText nameEditText; |     private EditText nameEditText; | ||||||
|     private Spinner streamsSpinner; |     private Spinner streamsSpinner; | ||||||
|     private RadioGroup radioVideoAudioGroup; |     private RadioGroup radioStreamsGroup; | ||||||
|     private TextView threadsCountTextView; |     private TextView threadsCountTextView; | ||||||
|     private SeekBar threadsSeekBar; |     private SeekBar threadsSeekBar; | ||||||
|  |  | ||||||
| @@ -155,12 +179,15 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck | |||||||
|         super.onCreate(savedInstanceState); |         super.onCreate(savedInstanceState); | ||||||
|         if (DEBUG) |         if (DEBUG) | ||||||
|             Log.d(TAG, "onCreate() called with: savedInstanceState = [" + savedInstanceState + "]"); |             Log.d(TAG, "onCreate() called with: savedInstanceState = [" + savedInstanceState + "]"); | ||||||
|  |  | ||||||
|         if (!PermissionHelper.checkStoragePermissions(getActivity(), PermissionHelper.DOWNLOAD_DIALOG_REQUEST_CODE)) { |         if (!PermissionHelper.checkStoragePermissions(getActivity(), PermissionHelper.DOWNLOAD_DIALOG_REQUEST_CODE)) { | ||||||
|             getDialog().dismiss(); |             getDialog().dismiss(); | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         setStyle(STYLE_NO_TITLE, ThemeHelper.getDialogTheme(getContext())); |         context = getContext(); | ||||||
|  |  | ||||||
|  |         setStyle(STYLE_NO_TITLE, ThemeHelper.getDialogTheme(context)); | ||||||
|         Icepick.restoreInstanceState(this, savedInstanceState); |         Icepick.restoreInstanceState(this, savedInstanceState); | ||||||
|  |  | ||||||
|         SparseArray<SecondaryStreamHelper<AudioStream>> secondaryStreams = new SparseArray<>(4); |         SparseArray<SecondaryStreamHelper<AudioStream>> secondaryStreams = new SparseArray<>(4); | ||||||
| @@ -177,9 +204,33 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         this.videoStreamsAdapter = new StreamItemAdapter<>(getContext(), wrappedVideoStreams, secondaryStreams); |         this.videoStreamsAdapter = new StreamItemAdapter<>(context, wrappedVideoStreams, secondaryStreams); | ||||||
|         this.audioStreamsAdapter = new StreamItemAdapter<>(getContext(), wrappedAudioStreams); |         this.audioStreamsAdapter = new StreamItemAdapter<>(context, wrappedAudioStreams); | ||||||
|         this.subtitleStreamsAdapter = new StreamItemAdapter<>(getContext(), wrappedSubtitleStreams); |         this.subtitleStreamsAdapter = new StreamItemAdapter<>(context, wrappedSubtitleStreams); | ||||||
|  |  | ||||||
|  |         Intent intent = new Intent(context, DownloadManagerService.class); | ||||||
|  |         context.startService(intent); | ||||||
|  |  | ||||||
|  |         context.bindService(intent, new ServiceConnection() { | ||||||
|  |             @Override | ||||||
|  |             public void onServiceConnected(ComponentName cname, IBinder service) { | ||||||
|  |                 DownloadManagerBinder mgr = (DownloadManagerBinder) service; | ||||||
|  |  | ||||||
|  |                 mainStorageAudio = mgr.getMainStorageAudio(); | ||||||
|  |                 mainStorageVideo = mgr.getMainStorageVideo(); | ||||||
|  |                 downloadManager = mgr.getDownloadManager(); | ||||||
|  |                 askForSavePath = mgr.askForSavePath(); | ||||||
|  |  | ||||||
|  |                 okButton.setEnabled(true); | ||||||
|  |  | ||||||
|  |                 context.unbindService(this); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             @Override | ||||||
|  |             public void onServiceDisconnected(ComponentName name) { | ||||||
|  |                 // nothing to do | ||||||
|  |             } | ||||||
|  |         }, Context.BIND_AUTO_CREATE); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
| @@ -204,8 +255,8 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck | |||||||
|         threadsCountTextView = view.findViewById(R.id.threads_count); |         threadsCountTextView = view.findViewById(R.id.threads_count); | ||||||
|         threadsSeekBar = view.findViewById(R.id.threads); |         threadsSeekBar = view.findViewById(R.id.threads); | ||||||
|  |  | ||||||
|         radioVideoAudioGroup = view.findViewById(R.id.video_audio_group); |         radioStreamsGroup = view.findViewById(R.id.video_audio_group); | ||||||
|         radioVideoAudioGroup.setOnCheckedChangeListener(this); |         radioStreamsGroup.setOnCheckedChangeListener(this); | ||||||
|  |  | ||||||
|         initToolbar(view.findViewById(R.id.toolbar)); |         initToolbar(view.findViewById(R.id.toolbar)); | ||||||
|         setupDownloadOptions(); |         setupDownloadOptions(); | ||||||
| @@ -240,17 +291,17 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck | |||||||
|         disposables.clear(); |         disposables.clear(); | ||||||
|  |  | ||||||
|         disposables.add(StreamSizeWrapper.fetchSizeForWrapper(wrappedVideoStreams).subscribe(result -> { |         disposables.add(StreamSizeWrapper.fetchSizeForWrapper(wrappedVideoStreams).subscribe(result -> { | ||||||
|             if (radioVideoAudioGroup.getCheckedRadioButtonId() == R.id.video_button) { |             if (radioStreamsGroup.getCheckedRadioButtonId() == R.id.video_button) { | ||||||
|                 setupVideoSpinner(); |                 setupVideoSpinner(); | ||||||
|             } |             } | ||||||
|         })); |         })); | ||||||
|         disposables.add(StreamSizeWrapper.fetchSizeForWrapper(wrappedAudioStreams).subscribe(result -> { |         disposables.add(StreamSizeWrapper.fetchSizeForWrapper(wrappedAudioStreams).subscribe(result -> { | ||||||
|             if (radioVideoAudioGroup.getCheckedRadioButtonId() == R.id.audio_button) { |             if (radioStreamsGroup.getCheckedRadioButtonId() == R.id.audio_button) { | ||||||
|                 setupAudioSpinner(); |                 setupAudioSpinner(); | ||||||
|             } |             } | ||||||
|         })); |         })); | ||||||
|         disposables.add(StreamSizeWrapper.fetchSizeForWrapper(wrappedSubtitleStreams).subscribe(result -> { |         disposables.add(StreamSizeWrapper.fetchSizeForWrapper(wrappedSubtitleStreams).subscribe(result -> { | ||||||
|             if (radioVideoAudioGroup.getCheckedRadioButtonId() == R.id.subtitle_button) { |             if (radioStreamsGroup.getCheckedRadioButtonId() == R.id.subtitle_button) { | ||||||
|                 setupSubtitleSpinner(); |                 setupSubtitleSpinner(); | ||||||
|             } |             } | ||||||
|         })); |         })); | ||||||
| @@ -263,21 +314,55 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public void onSaveInstanceState(Bundle outState) { |     public void onSaveInstanceState(@NonNull Bundle outState) { | ||||||
|         super.onSaveInstanceState(outState); |         super.onSaveInstanceState(outState); | ||||||
|         Icepick.saveInstanceState(this, outState); |         Icepick.saveInstanceState(this, outState); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void onActivityResult(int requestCode, int resultCode, Intent data) { | ||||||
|  |         super.onActivityResult(requestCode, resultCode, data); | ||||||
|  |  | ||||||
|  |         if (requestCode == REQUEST_DOWNLOAD_SAVE_AS && resultCode == Activity.RESULT_OK) { | ||||||
|  |             if (data.getData() == null) { | ||||||
|  |                 showFailedDialog(R.string.general_error); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (FilePickerActivityHelper.isOwnFileUri(context, data.getData())) { | ||||||
|  |                 File file = Utils.getFileForUri(data.getData()); | ||||||
|  |                 checkSelectedDownload(null, Uri.fromFile(file), file.getName(), StoredFileHelper.DEFAULT_MIME); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             DocumentFile docFile = DocumentFile.fromSingleUri(context, data.getData()); | ||||||
|  |             if (docFile == null) { | ||||||
|  |                 showFailedDialog(R.string.general_error); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // check if the selected file was previously used | ||||||
|  |             checkSelectedDownload(null, data.getData(), docFile.getName(), docFile.getType()); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     /*////////////////////////////////////////////////////////////////////////// |     /*////////////////////////////////////////////////////////////////////////// | ||||||
|     // Inits |     // Inits | ||||||
|     //////////////////////////////////////////////////////////////////////////*/ |     //////////////////////////////////////////////////////////////////////////*/ | ||||||
|  |  | ||||||
|     private void initToolbar(Toolbar toolbar) { |     private void initToolbar(Toolbar toolbar) { | ||||||
|         if (DEBUG) Log.d(TAG, "initToolbar() called with: toolbar = [" + toolbar + "]"); |         if (DEBUG) Log.d(TAG, "initToolbar() called with: toolbar = [" + toolbar + "]"); | ||||||
|  |  | ||||||
|  |         boolean isLight = ThemeHelper.isLightThemeSelected(getActivity()); | ||||||
|  |  | ||||||
|         toolbar.setTitle(R.string.download_dialog_title); |         toolbar.setTitle(R.string.download_dialog_title); | ||||||
|         toolbar.setNavigationIcon(ThemeHelper.isLightThemeSelected(getActivity()) ? R.drawable.ic_arrow_back_black_24dp : R.drawable.ic_arrow_back_white_24dp); |         toolbar.setNavigationIcon(isLight ? R.drawable.ic_arrow_back_black_24dp : R.drawable.ic_arrow_back_white_24dp); | ||||||
|         toolbar.inflateMenu(R.menu.dialog_url); |         toolbar.inflateMenu(R.menu.dialog_url); | ||||||
|         toolbar.setNavigationOnClickListener(v -> getDialog().dismiss()); |         toolbar.setNavigationOnClickListener(v -> getDialog().dismiss()); | ||||||
|  |         toolbar.setNavigationContentDescription(R.string.cancel); | ||||||
|  |  | ||||||
|  |         okButton = toolbar.findViewById(R.id.okay); | ||||||
|  |         okButton.setEnabled(false);// disable until the download service connection is done | ||||||
|  |  | ||||||
|         toolbar.setOnMenuItemClickListener(item -> { |         toolbar.setOnMenuItemClickListener(item -> { | ||||||
|             if (item.getItemId() == R.id.okay) { |             if (item.getItemId() == R.id.okay) { | ||||||
| @@ -346,7 +431,7 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck | |||||||
|     public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { |     public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { | ||||||
|         if (DEBUG) |         if (DEBUG) | ||||||
|             Log.d(TAG, "onItemSelected() called with: parent = [" + parent + "], view = [" + view + "], position = [" + position + "], id = [" + id + "]"); |             Log.d(TAG, "onItemSelected() called with: parent = [" + parent + "], view = [" + view + "], position = [" + position + "], id = [" + id + "]"); | ||||||
|         switch (radioVideoAudioGroup.getCheckedRadioButtonId()) { |         switch (radioStreamsGroup.getCheckedRadioButtonId()) { | ||||||
|             case R.id.audio_button: |             case R.id.audio_button: | ||||||
|                 selectedAudioIndex = position; |                 selectedAudioIndex = position; | ||||||
|                 break; |                 break; | ||||||
| @@ -370,9 +455,9 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck | |||||||
|     protected void setupDownloadOptions() { |     protected void setupDownloadOptions() { | ||||||
|         setRadioButtonsState(false); |         setRadioButtonsState(false); | ||||||
|  |  | ||||||
|         final RadioButton audioButton = radioVideoAudioGroup.findViewById(R.id.audio_button); |         final RadioButton audioButton = radioStreamsGroup.findViewById(R.id.audio_button); | ||||||
|         final RadioButton videoButton = radioVideoAudioGroup.findViewById(R.id.video_button); |         final RadioButton videoButton = radioStreamsGroup.findViewById(R.id.video_button); | ||||||
|         final RadioButton subtitleButton = radioVideoAudioGroup.findViewById(R.id.subtitle_button); |         final RadioButton subtitleButton = radioStreamsGroup.findViewById(R.id.subtitle_button); | ||||||
|         final boolean isVideoStreamsAvailable = videoStreamsAdapter.getCount() > 0; |         final boolean isVideoStreamsAvailable = videoStreamsAdapter.getCount() > 0; | ||||||
|         final boolean isAudioStreamsAvailable = audioStreamsAdapter.getCount() > 0; |         final boolean isAudioStreamsAvailable = audioStreamsAdapter.getCount() > 0; | ||||||
|         final boolean isSubtitleStreamsAvailable = subtitleStreamsAdapter.getCount() > 0; |         final boolean isSubtitleStreamsAvailable = subtitleStreamsAdapter.getCount() > 0; | ||||||
| @@ -397,9 +482,9 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     private void setRadioButtonsState(boolean enabled) { |     private void setRadioButtonsState(boolean enabled) { | ||||||
|         radioVideoAudioGroup.findViewById(R.id.audio_button).setEnabled(enabled); |         radioStreamsGroup.findViewById(R.id.audio_button).setEnabled(enabled); | ||||||
|         radioVideoAudioGroup.findViewById(R.id.video_button).setEnabled(enabled); |         radioStreamsGroup.findViewById(R.id.video_button).setEnabled(enabled); | ||||||
|         radioVideoAudioGroup.findViewById(R.id.subtitle_button).setEnabled(enabled); |         radioStreamsGroup.findViewById(R.id.subtitle_button).setEnabled(enabled); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private int getSubtitleIndexBy(List<SubtitlesStream> streams) { |     private int getSubtitleIndexBy(List<SubtitlesStream> streams) { | ||||||
| @@ -434,92 +519,310 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck | |||||||
|         return 0; |         return 0; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     StoredDirectoryHelper mainStorageAudio = null; | ||||||
|  |     StoredDirectoryHelper mainStorageVideo = null; | ||||||
|  |     DownloadManager downloadManager = null; | ||||||
|  |     ActionMenuItemView okButton = null; | ||||||
|  |     Context context; | ||||||
|  |     boolean askForSavePath; | ||||||
|  |  | ||||||
|  |     private String getNameEditText() { | ||||||
|  |         String str = nameEditText.getText().toString().trim(); | ||||||
|  |  | ||||||
|  |         return FilenameUtils.createFilename(context, str.isEmpty() ? currentInfo.getName() : str); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private void showFailedDialog(@StringRes int msg) { | ||||||
|  |         new AlertDialog.Builder(context) | ||||||
|  |                 .setTitle(R.string.general_error) | ||||||
|  |                 .setMessage(msg) | ||||||
|  |                 .setNegativeButton(android.R.string.ok, null) | ||||||
|  |                 .create() | ||||||
|  |                 .show(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private void showErrorActivity(Exception e) { | ||||||
|  |         ErrorActivity.reportError( | ||||||
|  |                 context, | ||||||
|  |                 Collections.singletonList(e), | ||||||
|  |                 null, | ||||||
|  |                 null, | ||||||
|  |                 ErrorActivity.ErrorInfo.make(UserAction.SOMETHING_ELSE, "-", "-", R.string.general_error) | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     private void prepareSelectedDownload() { |     private void prepareSelectedDownload() { | ||||||
|         final Context context = getContext(); |         StoredDirectoryHelper mainStorage; | ||||||
|         Stream stream; |         MediaFormat format; | ||||||
|         String location; |         String mime; | ||||||
|         char kind; |  | ||||||
|  |  | ||||||
|         String fileName = nameEditText.getText().toString().trim(); |         // first, build the filename and get the output folder (if possible) | ||||||
|         if (fileName.isEmpty()) |         // later, run a very very very large file checking logic | ||||||
|             fileName = FilenameUtils.createFilename(context, currentInfo.getName()); |  | ||||||
|  |  | ||||||
|         switch (radioVideoAudioGroup.getCheckedRadioButtonId()) { |         String filename = getNameEditText().concat("."); | ||||||
|  |  | ||||||
|  |         switch (radioStreamsGroup.getCheckedRadioButtonId()) { | ||||||
|             case R.id.audio_button: |             case R.id.audio_button: | ||||||
|                 stream = audioStreamsAdapter.getItem(selectedAudioIndex); |                 mainStorage = mainStorageAudio; | ||||||
|                 location = NewPipeSettings.getAudioDownloadPath(context); |                 format = audioStreamsAdapter.getItem(selectedAudioIndex).getFormat(); | ||||||
|                 kind = 'a'; |                 mime = format.mimeType; | ||||||
|  |                 filename += format.suffix; | ||||||
|                 break; |                 break; | ||||||
|             case R.id.video_button: |             case R.id.video_button: | ||||||
|                 stream = videoStreamsAdapter.getItem(selectedVideoIndex); |                 mainStorage = mainStorageVideo; | ||||||
|                 location = NewPipeSettings.getVideoDownloadPath(context); |                 format = videoStreamsAdapter.getItem(selectedVideoIndex).getFormat(); | ||||||
|                 kind = 'v'; |                 mime = format.mimeType; | ||||||
|  |                 filename += format.suffix; | ||||||
|                 break; |                 break; | ||||||
|             case R.id.subtitle_button: |             case R.id.subtitle_button: | ||||||
|                 stream = subtitleStreamsAdapter.getItem(selectedSubtitleIndex); |                 mainStorage = mainStorageVideo;// subtitle & video files go together | ||||||
|                 location = NewPipeSettings.getVideoDownloadPath(context);// assume that subtitle & video files go together |                 format = subtitleStreamsAdapter.getItem(selectedSubtitleIndex).getFormat(); | ||||||
|                 kind = 's'; |                 mime = format.mimeType; | ||||||
|  |                 filename += format == MediaFormat.TTML ? MediaFormat.SRT.suffix : format.suffix; | ||||||
|  |                 break; | ||||||
|  |             default: | ||||||
|  |                 throw new RuntimeException("No stream selected"); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (mainStorage == null || askForSavePath) { | ||||||
|  |             // This part is called if with SAF preferred: | ||||||
|  |             //  * older android version running | ||||||
|  |             //  * save path not defined (via download settings) | ||||||
|  |             //  * the user checked the "ask where to download" option | ||||||
|  |  | ||||||
|  |             if (!askForSavePath) | ||||||
|  |                 Toast.makeText(context, getString(R.string.no_available_dir), Toast.LENGTH_LONG).show(); | ||||||
|  |  | ||||||
|  |             if (NewPipeSettings.useStorageAccessFramework(context)) { | ||||||
|  |                 StoredFileHelper.requestSafWithFileCreation(this, REQUEST_DOWNLOAD_SAVE_AS, filename, mime); | ||||||
|  |             } else { | ||||||
|  |                 File initialSavePath; | ||||||
|  |                 if (radioStreamsGroup.getCheckedRadioButtonId() == R.id.audio_button) | ||||||
|  |                     initialSavePath = NewPipeSettings.getDir(Environment.DIRECTORY_MUSIC); | ||||||
|  |                 else | ||||||
|  |                     initialSavePath = NewPipeSettings.getDir(Environment.DIRECTORY_MOVIES); | ||||||
|  |  | ||||||
|  |                 initialSavePath = new File(initialSavePath, filename); | ||||||
|  |                 startActivityForResult( | ||||||
|  |                         FilePickerActivityHelper.chooseFileToSave(context, initialSavePath.getAbsolutePath()), | ||||||
|  |                         REQUEST_DOWNLOAD_SAVE_AS | ||||||
|  |                 ); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // check for existing file with the same name | ||||||
|  |         checkSelectedDownload(mainStorage, mainStorage.findFile(filename), filename, mime); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private void checkSelectedDownload(StoredDirectoryHelper mainStorage, Uri targetFile, String filename, String mime) { | ||||||
|  |         StoredFileHelper storage; | ||||||
|  |  | ||||||
|  |         try { | ||||||
|  |             if (mainStorage == null) { | ||||||
|  |                 // using SAF on older android version | ||||||
|  |                 storage = new StoredFileHelper(context, null, targetFile, ""); | ||||||
|  |             } else if (targetFile == null) { | ||||||
|  |                 // the file does not exist, but it is probably used in a pending download | ||||||
|  |                 storage = new StoredFileHelper(mainStorage.getUri(), filename, mime, mainStorage.getTag()); | ||||||
|  |             } else { | ||||||
|  |                 // the target filename is already use, attempt to use it | ||||||
|  |                 storage = new StoredFileHelper(context, mainStorage.getUri(), targetFile, mainStorage.getTag()); | ||||||
|  |             } | ||||||
|  |         } catch (Exception e) { | ||||||
|  |             showErrorActivity(e); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // check if is our file | ||||||
|  |         MissionState state = downloadManager.checkForExistingMission(storage); | ||||||
|  |         @StringRes int msgBtn; | ||||||
|  |         @StringRes int msgBody; | ||||||
|  |  | ||||||
|  |         switch (state) { | ||||||
|  |             case Finished: | ||||||
|  |                 msgBtn = R.string.overwrite; | ||||||
|  |                 msgBody = R.string.overwrite_finished_warning; | ||||||
|  |                 break; | ||||||
|  |             case Pending: | ||||||
|  |                 msgBtn = R.string.overwrite; | ||||||
|  |                 msgBody = R.string.download_already_pending; | ||||||
|  |                 break; | ||||||
|  |             case PendingRunning: | ||||||
|  |                 msgBtn = R.string.generate_unique_name; | ||||||
|  |                 msgBody = R.string.download_already_running; | ||||||
|  |                 break; | ||||||
|  |             case None: | ||||||
|  |                 if (mainStorage == null) { | ||||||
|  |                     // This part is called if: | ||||||
|  |                     // * using SAF on older android version | ||||||
|  |                     // * save path not defined | ||||||
|  |                     // * if the file exists overwrite it, is not necessary ask | ||||||
|  |                     if (!storage.existsAsFile() && !storage.create()) { | ||||||
|  |                         showFailedDialog(R.string.error_file_creation); | ||||||
|  |                         return; | ||||||
|  |                     } | ||||||
|  |                     continueSelectedDownload(storage); | ||||||
|  |                     return; | ||||||
|  |                 } else if (targetFile == null) { | ||||||
|  |                     // This part is called if: | ||||||
|  |                     // * the filename is not used in a pending/finished download | ||||||
|  |                     // * the file does not exists, create | ||||||
|  |  | ||||||
|  |                     if (!mainStorage.mkdirs()) { | ||||||
|  |                         showFailedDialog(R.string.error_path_creation); | ||||||
|  |                         return; | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     storage = mainStorage.createFile(filename, mime); | ||||||
|  |                     if (storage == null || !storage.canWrite()) { | ||||||
|  |                         showFailedDialog(R.string.error_file_creation); | ||||||
|  |                         return; | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     continueSelectedDownload(storage); | ||||||
|  |                     return; | ||||||
|  |                 } | ||||||
|  |                 msgBtn = R.string.overwrite; | ||||||
|  |                 msgBody = R.string.overwrite_unrelated_warning; | ||||||
|                 break; |                 break; | ||||||
|             default: |             default: | ||||||
|                 return; |                 return; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         int threads; |  | ||||||
|  |  | ||||||
|         if (radioVideoAudioGroup.getCheckedRadioButtonId() == R.id.subtitle_button) { |         AlertDialog.Builder askDialog = new AlertDialog.Builder(context) | ||||||
|             threads = 1;// use unique thread for subtitles due small file size |                 .setTitle(R.string.download_dialog_title) | ||||||
|             fileName += ".srt";// final subtitle format |                 .setMessage(msgBody) | ||||||
|         } else { |                 .setNegativeButton(android.R.string.cancel, null); | ||||||
|             threads = threadsSeekBar.getProgress() + 1; |         final StoredFileHelper finalStorage = storage; | ||||||
|             fileName += "." + stream.getFormat().getSuffix(); |  | ||||||
|  |  | ||||||
|  |         if (mainStorage == null) { | ||||||
|  |             // This part is called if: | ||||||
|  |             // * using SAF on older android version | ||||||
|  |             // * save path not defined | ||||||
|  |             switch (state) { | ||||||
|  |                 case Pending: | ||||||
|  |                 case Finished: | ||||||
|  |                     askDialog.setPositiveButton(msgBtn, (dialog, which) -> { | ||||||
|  |                         dialog.dismiss(); | ||||||
|  |                         downloadManager.forgetMission(finalStorage); | ||||||
|  |                         continueSelectedDownload(finalStorage); | ||||||
|  |                     }); | ||||||
|  |                     break; | ||||||
|             } |             } | ||||||
|  |  | ||||||
|         final String finalFileName = fileName; |             askDialog.create().show(); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|         DownloadManagerService.checkForRunningMission(context, location, fileName, (listed, finished) -> { |         askDialog.setPositiveButton(msgBtn, (dialog, which) -> { | ||||||
|             if (listed) { |             dialog.dismiss(); | ||||||
|                 AlertDialog.Builder builder = new AlertDialog.Builder(context); |  | ||||||
|                 builder.setTitle(R.string.download_dialog_title) |             StoredFileHelper storageNew; | ||||||
|                         .setMessage(finished ? R.string.overwrite_warning : R.string.download_already_running) |             switch (state) { | ||||||
|                         .setPositiveButton( |                 case Finished: | ||||||
|                                 finished ? R.string.overwrite : R.string.generate_unique_name, |                 case Pending: | ||||||
|                                 (dialog, which) -> downloadSelected(context, stream, location, finalFileName, kind, threads) |                     downloadManager.forgetMission(finalStorage); | ||||||
|                         ) |                 case None: | ||||||
|                         .setNegativeButton(android.R.string.cancel, (dialog, which) -> { |                     if (targetFile == null) { | ||||||
|                             dialog.cancel(); |                         storageNew = mainStorage.createFile(filename, mime); | ||||||
|                         }) |  | ||||||
|                         .create() |  | ||||||
|                         .show(); |  | ||||||
|                     } else { |                     } else { | ||||||
|                 downloadSelected(context, stream, location, finalFileName, kind, threads); |                         try { | ||||||
|  |                             // try take (or steal) the file | ||||||
|  |                             storageNew = new StoredFileHelper(context, mainStorage.getUri(), targetFile, mainStorage.getTag()); | ||||||
|  |                         } catch (IOException e) { | ||||||
|  |                             Log.e(TAG, "Failed to take (or steal) the file in " + targetFile.toString()); | ||||||
|  |                             storageNew = null; | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     if (storageNew != null && storageNew.canWrite()) | ||||||
|  |                         continueSelectedDownload(storageNew); | ||||||
|  |                     else | ||||||
|  |                         showFailedDialog(R.string.error_file_creation); | ||||||
|  |                     break; | ||||||
|  |                 case PendingRunning: | ||||||
|  |                     storageNew = mainStorage.createUniqueFile(filename, mime); | ||||||
|  |                     if (storageNew == null) | ||||||
|  |                         showFailedDialog(R.string.error_file_creation); | ||||||
|  |                     else | ||||||
|  |                         continueSelectedDownload(storageNew); | ||||||
|  |                     break; | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|  |         askDialog.create().show(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private void downloadSelected(Context context, Stream selectedStream, String location, String fileName, char kind, int threads) { |     private void continueSelectedDownload(@NonNull StoredFileHelper storage) { | ||||||
|  |         if (!storage.canWrite()) { | ||||||
|  |             showFailedDialog(R.string.permission_denied); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // check if the selected file has to be overwritten, by simply checking its length | ||||||
|  |         try { | ||||||
|  |             if (storage.length() > 0) storage.truncate(); | ||||||
|  |         } catch (IOException e) { | ||||||
|  |             Log.e(TAG, "failed to truncate the file: " + storage.getUri().toString(), e); | ||||||
|  |             showFailedDialog(R.string.overwrite_failed); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         Stream selectedStream; | ||||||
|  |         char kind; | ||||||
|  |         int threads = threadsSeekBar.getProgress() + 1; | ||||||
|         String[] urls; |         String[] urls; | ||||||
|         String psName = null; |         String psName = null; | ||||||
|         String[] psArgs = null; |         String[] psArgs = null; | ||||||
|         String secondaryStreamUrl = null; |         String secondaryStreamUrl = null; | ||||||
|         long nearLength = 0; |         long nearLength = 0; | ||||||
|  |  | ||||||
|         if (selectedStream instanceof VideoStream) { |         // more download logic: select muxer, subtitle converter, etc. | ||||||
|  |         switch (radioStreamsGroup.getCheckedRadioButtonId()) { | ||||||
|  |             case R.id.audio_button: | ||||||
|  |                 kind = 'a'; | ||||||
|  |                 selectedStream = audioStreamsAdapter.getItem(selectedAudioIndex); | ||||||
|  |  | ||||||
|  |                 if (selectedStream.getFormat() == MediaFormat.M4A) { | ||||||
|  |                     psName = Postprocessing.ALGORITHM_M4A_NO_DASH; | ||||||
|  |                 } | ||||||
|  |                 break; | ||||||
|  |             case R.id.video_button: | ||||||
|  |                 kind = 'v'; | ||||||
|  |                 selectedStream = videoStreamsAdapter.getItem(selectedVideoIndex); | ||||||
|  |  | ||||||
|                 SecondaryStreamHelper<AudioStream> secondaryStream = videoStreamsAdapter |                 SecondaryStreamHelper<AudioStream> secondaryStream = videoStreamsAdapter | ||||||
|                         .getAllSecondary() |                         .getAllSecondary() | ||||||
|                         .get(wrappedVideoStreams.getStreamsList().indexOf(selectedStream)); |                         .get(wrappedVideoStreams.getStreamsList().indexOf(selectedStream)); | ||||||
|  |  | ||||||
|                 if (secondaryStream != null) { |                 if (secondaryStream != null) { | ||||||
|                     secondaryStreamUrl = secondaryStream.getStream().getUrl(); |                     secondaryStreamUrl = secondaryStream.getStream().getUrl(); | ||||||
|                 psName = selectedStream.getFormat() == MediaFormat.MPEG_4 ? Postprocessing.ALGORITHM_MP4_MUXER : Postprocessing.ALGORITHM_WEBM_MUXER; |  | ||||||
|  |                     if (selectedStream.getFormat() == MediaFormat.MPEG_4) | ||||||
|  |                         psName = Postprocessing.ALGORITHM_MP4_FROM_DASH_MUXER; | ||||||
|  |                     else | ||||||
|  |                         psName = Postprocessing.ALGORITHM_WEBM_MUXER; | ||||||
|  |  | ||||||
|                     psArgs = null; |                     psArgs = null; | ||||||
|                     long videoSize = wrappedVideoStreams.getSizeInBytes((VideoStream) selectedStream); |                     long videoSize = wrappedVideoStreams.getSizeInBytes((VideoStream) selectedStream); | ||||||
|  |  | ||||||
|                 // set nearLength, only, if both sizes are fetched or known. this probably does not work on slow networks |                     // set nearLength, only, if both sizes are fetched or known. This probably | ||||||
|  |                     // does not work on slow networks but is later updated in the downloader | ||||||
|                     if (secondaryStream.getSizeInBytes() > 0 && videoSize > 0) { |                     if (secondaryStream.getSizeInBytes() > 0 && videoSize > 0) { | ||||||
|                         nearLength = secondaryStream.getSizeInBytes() + videoSize; |                         nearLength = secondaryStream.getSizeInBytes() + videoSize; | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|         } else if ((selectedStream instanceof SubtitlesStream) && selectedStream.getFormat() == MediaFormat.TTML) { |                 break; | ||||||
|  |             case R.id.subtitle_button: | ||||||
|  |                 threads = 1;// use unique thread for subtitles due small file size | ||||||
|  |                 kind = 's'; | ||||||
|  |                 selectedStream = subtitleStreamsAdapter.getItem(selectedSubtitleIndex); | ||||||
|  |  | ||||||
|  |                 if (selectedStream.getFormat() == MediaFormat.TTML) { | ||||||
|                     psName = Postprocessing.ALGORITHM_TTML_CONVERTER; |                     psName = Postprocessing.ALGORITHM_TTML_CONVERTER; | ||||||
|                     psArgs = new String[]{ |                     psArgs = new String[]{ | ||||||
|                             selectedStream.getFormat().getSuffix(), |                             selectedStream.getFormat().getSuffix(), | ||||||
| @@ -527,6 +830,10 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck | |||||||
|                             "false",// detect youtube duplicate lines |                             "false",// detect youtube duplicate lines | ||||||
|                     }; |                     }; | ||||||
|                 } |                 } | ||||||
|  |                 break; | ||||||
|  |             default: | ||||||
|  |                 return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|         if (secondaryStreamUrl == null) { |         if (secondaryStreamUrl == null) { | ||||||
|             urls = new String[]{selectedStream.getUrl()}; |             urls = new String[]{selectedStream.getUrl()}; | ||||||
| @@ -534,8 +841,8 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck | |||||||
|             urls = new String[]{selectedStream.getUrl(), secondaryStreamUrl}; |             urls = new String[]{selectedStream.getUrl(), secondaryStreamUrl}; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         DownloadManagerService.startMission(context, urls, location, fileName, kind, threads, currentInfo.getUrl(), psName, psArgs, nearLength); |         DownloadManagerService.startMission(context, urls, storage, kind, threads, currentInfo.getUrl(), psName, psArgs, nearLength); | ||||||
|  |  | ||||||
|         getDialog().dismiss(); |         dismiss(); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,10 +1,9 @@ | |||||||
| package org.schabi.newpipe.fragments; | package org.schabi.newpipe.fragments; | ||||||
|  |  | ||||||
| import android.content.Intent; | import android.content.Intent; | ||||||
| import android.net.Uri; |  | ||||||
| import android.os.Bundle; | import android.os.Bundle; | ||||||
| import android.support.annotation.Nullable; | import androidx.annotation.Nullable; | ||||||
| import android.support.annotation.StringRes; | import androidx.annotation.StringRes; | ||||||
| import android.util.Log; | import android.util.Log; | ||||||
| import android.view.View; | import android.view.View; | ||||||
| import android.widget.Button; | import android.widget.Button; | ||||||
| @@ -180,7 +179,7 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (exception instanceof ReCaptchaException) { |         if (exception instanceof ReCaptchaException) { | ||||||
|             onReCaptchaException(); |             onReCaptchaException((ReCaptchaException) exception); | ||||||
|             return true; |             return true; | ||||||
|         } else if (exception instanceof IOException) { |         } else if (exception instanceof IOException) { | ||||||
|             showError(getString(R.string.network_error), true); |             showError(getString(R.string.network_error), true); | ||||||
| @@ -190,11 +189,13 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC | |||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public void onReCaptchaException() { |     public void onReCaptchaException(ReCaptchaException exception) { | ||||||
|         if (DEBUG) Log.d(TAG, "onReCaptchaException() called"); |         if (DEBUG) Log.d(TAG, "onReCaptchaException() called"); | ||||||
|         Toast.makeText(activity, R.string.recaptcha_request_toast, Toast.LENGTH_LONG).show(); |         Toast.makeText(activity, R.string.recaptcha_request_toast, Toast.LENGTH_LONG).show(); | ||||||
|         // Starting ReCaptcha Challenge Activity |         // Starting ReCaptcha Challenge Activity | ||||||
|         startActivityForResult(new Intent(activity, ReCaptchaActivity.class), ReCaptchaActivity.RECAPTCHA_REQUEST); |         Intent intent = new Intent(activity, ReCaptchaActivity.class); | ||||||
|  |         intent.putExtra(ReCaptchaActivity.RECAPTCHA_URL_EXTRA, exception.getUrl()); | ||||||
|  |         startActivityForResult(intent, ReCaptchaActivity.RECAPTCHA_REQUEST); | ||||||
|  |  | ||||||
|         showError(getString(R.string.recaptcha_request_toast), false); |         showError(getString(R.string.recaptcha_request_toast), false); | ||||||
|     } |     } | ||||||
| @@ -230,21 +231,4 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC | |||||||
|         ErrorActivity.reportError(getContext(), exception, MainActivity.class, rootView, |         ErrorActivity.reportError(getContext(), exception, MainActivity.class, rootView, | ||||||
|                 ErrorActivity.ErrorInfo.make(userAction, serviceName, request, errorId)); |                 ErrorActivity.ErrorInfo.make(userAction, serviceName, request, errorId)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /*////////////////////////////////////////////////////////////////////////// |  | ||||||
|     // Utils |  | ||||||
|     //////////////////////////////////////////////////////////////////////////*/ |  | ||||||
|  |  | ||||||
|     protected void openUrlInBrowser(String url) { |  | ||||||
|         Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); |  | ||||||
|         startActivity(Intent.createChooser(intent, activity.getString(R.string.share_dialog_title))); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     protected void shareUrl(String subject, String url) { |  | ||||||
|         Intent intent = new Intent(Intent.ACTION_SEND); |  | ||||||
|         intent.setType("text/plain"); |  | ||||||
|         intent.putExtra(Intent.EXTRA_SUBJECT, subject); |  | ||||||
|         intent.putExtra(Intent.EXTRA_TEXT, url); |  | ||||||
|         startActivity(Intent.createChooser(intent, getString(R.string.share_dialog_title))); |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| package org.schabi.newpipe.fragments; | package org.schabi.newpipe.fragments; | ||||||
|  |  | ||||||
| import android.os.Bundle; | import android.os.Bundle; | ||||||
| import android.support.annotation.Nullable; | import androidx.annotation.Nullable; | ||||||
| import android.view.LayoutInflater; | import android.view.LayoutInflater; | ||||||
| import android.view.View; | import android.view.View; | ||||||
| import android.view.ViewGroup; | import android.view.ViewGroup; | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| package org.schabi.newpipe.fragments; | package org.schabi.newpipe.fragments; | ||||||
|  |  | ||||||
| import android.os.Bundle; | import android.os.Bundle; | ||||||
| import android.support.annotation.Nullable; | import androidx.annotation.Nullable; | ||||||
| import android.view.LayoutInflater; | import android.view.LayoutInflater; | ||||||
| import android.view.View; | import android.view.View; | ||||||
| import android.view.ViewGroup; | import android.view.ViewGroup; | ||||||
|   | |||||||
| @@ -1,15 +1,6 @@ | |||||||
| package org.schabi.newpipe.fragments; | package org.schabi.newpipe.fragments; | ||||||
|  |  | ||||||
| import android.os.Bundle; | import android.os.Bundle; | ||||||
| import android.support.annotation.NonNull; |  | ||||||
| import android.support.annotation.Nullable; |  | ||||||
| import android.support.design.widget.TabLayout; |  | ||||||
| import android.support.v4.app.Fragment; |  | ||||||
| import android.support.v4.app.FragmentManager; |  | ||||||
| import android.support.v4.app.FragmentPagerAdapter; |  | ||||||
| import android.support.v4.view.ViewPager; |  | ||||||
| import android.support.v7.app.ActionBar; |  | ||||||
| import android.support.v7.app.AppCompatActivity; |  | ||||||
| import android.util.Log; | import android.util.Log; | ||||||
| import android.view.LayoutInflater; | import android.view.LayoutInflater; | ||||||
| import android.view.Menu; | import android.view.Menu; | ||||||
| @@ -18,6 +9,17 @@ import android.view.MenuItem; | |||||||
| import android.view.View; | import android.view.View; | ||||||
| import android.view.ViewGroup; | import android.view.ViewGroup; | ||||||
|  |  | ||||||
|  | import androidx.annotation.NonNull; | ||||||
|  | import androidx.annotation.Nullable; | ||||||
|  | import androidx.appcompat.app.ActionBar; | ||||||
|  | import androidx.appcompat.app.AppCompatActivity; | ||||||
|  | import androidx.fragment.app.Fragment; | ||||||
|  | import androidx.fragment.app.FragmentManager; | ||||||
|  | import androidx.fragment.app.FragmentPagerAdapter; | ||||||
|  | import androidx.viewpager.widget.ViewPager; | ||||||
|  |  | ||||||
|  | import com.google.android.material.tabs.TabLayout; | ||||||
|  |  | ||||||
| import org.schabi.newpipe.BaseFragment; | import org.schabi.newpipe.BaseFragment; | ||||||
| import org.schabi.newpipe.R; | import org.schabi.newpipe.R; | ||||||
| import org.schabi.newpipe.extractor.exceptions.ExtractionException; | import org.schabi.newpipe.extractor.exceptions.ExtractionException; | ||||||
| @@ -50,6 +52,8 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte | |||||||
|         super.onCreate(savedInstanceState); |         super.onCreate(savedInstanceState); | ||||||
|         setHasOptionsMenu(true); |         setHasOptionsMenu(true); | ||||||
|  |  | ||||||
|  |         destroyOldFragments(); | ||||||
|  |  | ||||||
|         tabsManager = TabsManager.getManager(activity); |         tabsManager = TabsManager.getManager(activity); | ||||||
|         tabsManager.setSavedTabsListener(() -> { |         tabsManager.setSavedTabsListener(() -> { | ||||||
|             if (DEBUG) { |             if (DEBUG) { | ||||||
| @@ -63,6 +67,17 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte | |||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     private void destroyOldFragments() { | ||||||
|  |         for (Fragment fragment : getChildFragmentManager().getFragments()) { | ||||||
|  |             if (fragment != null) { | ||||||
|  |                 getChildFragmentManager() | ||||||
|  |                         .beginTransaction() | ||||||
|  |                         .remove(fragment) | ||||||
|  |                         .commitNowAllowingStateLoss(); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { |     public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { | ||||||
|         return inflater.inflate(R.layout.fragment_main, container, false); |         return inflater.inflate(R.layout.fragment_main, container, false); | ||||||
| @@ -98,6 +113,7 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte | |||||||
|     public void onDestroy() { |     public void onDestroy() { | ||||||
|         super.onDestroy(); |         super.onDestroy(); | ||||||
|         tabsManager.unsetSavedTabsListener(); |         tabsManager.unsetSavedTabsListener(); | ||||||
|  |         if (viewPager != null) viewPager.setAdapter(null); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /*////////////////////////////////////////////////////////////////////////// |     /*////////////////////////////////////////////////////////////////////////// | ||||||
| @@ -144,6 +160,7 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte | |||||||
|  |  | ||||||
|         viewPager.setOffscreenPageLimit(pagerAdapter.getCount()); |         viewPager.setOffscreenPageLimit(pagerAdapter.getCount()); | ||||||
|         updateTabsIcon(); |         updateTabsIcon(); | ||||||
|  |         updateTabsContentDescription(); | ||||||
|         updateCurrentTitle(); |         updateCurrentTitle(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -156,6 +173,17 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     private void updateTabsContentDescription() { | ||||||
|  |         for (int i = 0; i < tabsList.size(); i++) { | ||||||
|  |             final TabLayout.Tab tabToSet = tabLayout.getTabAt(i); | ||||||
|  |             if (tabToSet != null) { | ||||||
|  |                 final Tab t = tabsList.get(i); | ||||||
|  |                 tabToSet.setIcon(t.getTabIconRes(activity)); | ||||||
|  |                 tabToSet.setContentDescription(t.getTabName(activity)); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     private void updateCurrentTitle() { |     private void updateCurrentTitle() { | ||||||
|         setTitle(tabsList.get(viewPager.getCurrentItem()).getTabName(requireContext())); |         setTitle(tabsList.get(viewPager.getCurrentItem()).getTabName(requireContext())); | ||||||
|     } |     } | ||||||
| @@ -177,6 +205,7 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     private class SelectedTabsPagerAdapter extends FragmentPagerAdapter { |     private class SelectedTabsPagerAdapter extends FragmentPagerAdapter { | ||||||
|  |  | ||||||
|         private SelectedTabsPagerAdapter(FragmentManager fragmentManager) { |         private SelectedTabsPagerAdapter(FragmentManager fragmentManager) { | ||||||
|             super(fragmentManager); |             super(fragmentManager); | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -1,8 +1,8 @@ | |||||||
| package org.schabi.newpipe.fragments; | package org.schabi.newpipe.fragments; | ||||||
|  |  | ||||||
| import android.support.v7.widget.LinearLayoutManager; | import androidx.recyclerview.widget.LinearLayoutManager; | ||||||
| import android.support.v7.widget.RecyclerView; | import androidx.recyclerview.widget.RecyclerView; | ||||||
| import android.support.v7.widget.StaggeredGridLayoutManager; | import androidx.recyclerview.widget.StaggeredGridLayoutManager; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Recycler view scroll listener which calls the method {@link #onScrolledDown(RecyclerView)} |  * Recycler view scroll listener which calls the method {@link #onScrolledDown(RecyclerView)} | ||||||
|   | |||||||
| @@ -1,8 +1,9 @@ | |||||||
| package org.schabi.newpipe.fragments.detail; | package org.schabi.newpipe.fragments.detail; | ||||||
|  |  | ||||||
| import android.support.v4.app.Fragment; | import androidx.annotation.Nullable; | ||||||
| import android.support.v4.app.FragmentManager; | import androidx.fragment.app.Fragment; | ||||||
| import android.support.v4.app.FragmentPagerAdapter; | import androidx.fragment.app.FragmentManager; | ||||||
|  | import androidx.fragment.app.FragmentPagerAdapter; | ||||||
| import android.view.ViewGroup; | import android.view.ViewGroup; | ||||||
|  |  | ||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
| @@ -61,6 +62,18 @@ public class TabAdaptor extends FragmentPagerAdapter { | |||||||
|         else return POSITION_NONE; |         else return POSITION_NONE; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     public int getItemPositionByTitle(String title) { | ||||||
|  |         return mFragmentTitleList.indexOf(title); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Nullable | ||||||
|  |     public String getItemTitle(int position) { | ||||||
|  |         if (position < 0 || position >= mFragmentTitleList.size()) { | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |         return mFragmentTitleList.get(position); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     public void notifyDataSetUpdate(){ |     public void notifyDataSetUpdate(){ | ||||||
|         notifyDataSetChanged(); |         notifyDataSetChanged(); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -9,17 +9,17 @@ import android.net.Uri; | |||||||
| import android.os.Build; | import android.os.Build; | ||||||
| import android.os.Bundle; | import android.os.Bundle; | ||||||
| import android.preference.PreferenceManager; | import android.preference.PreferenceManager; | ||||||
| import android.support.annotation.DrawableRes; | import androidx.annotation.DrawableRes; | ||||||
| import android.support.annotation.NonNull; | import androidx.annotation.NonNull; | ||||||
| import android.support.annotation.Nullable; | import androidx.annotation.Nullable; | ||||||
| import android.support.design.widget.AppBarLayout; | import com.google.android.material.appbar.AppBarLayout; | ||||||
| import android.support.design.widget.TabLayout; | import com.google.android.material.tabs.TabLayout; | ||||||
| import android.support.v4.app.Fragment; | import androidx.fragment.app.Fragment; | ||||||
| import android.support.v4.content.ContextCompat; | import androidx.core.content.ContextCompat; | ||||||
| import android.support.v4.view.ViewPager; | import androidx.viewpager.widget.ViewPager; | ||||||
| import android.support.v7.app.ActionBar; | import androidx.appcompat.app.ActionBar; | ||||||
| import android.support.v7.app.AlertDialog; | import androidx.appcompat.app.AlertDialog; | ||||||
| import android.support.v7.app.AppCompatActivity; | import androidx.appcompat.app.AppCompatActivity; | ||||||
| import android.text.Html; | import android.text.Html; | ||||||
| import android.text.Spanned; | import android.text.Spanned; | ||||||
| import android.text.TextUtils; | import android.text.TextUtils; | ||||||
| @@ -60,7 +60,6 @@ import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubeStreamExt | |||||||
| import org.schabi.newpipe.extractor.stream.AudioStream; | import org.schabi.newpipe.extractor.stream.AudioStream; | ||||||
| import org.schabi.newpipe.extractor.stream.Stream; | import org.schabi.newpipe.extractor.stream.Stream; | ||||||
| import org.schabi.newpipe.extractor.stream.StreamInfo; | import org.schabi.newpipe.extractor.stream.StreamInfo; | ||||||
| import org.schabi.newpipe.extractor.stream.StreamInfoItem; |  | ||||||
| import org.schabi.newpipe.extractor.stream.StreamType; | import org.schabi.newpipe.extractor.stream.StreamType; | ||||||
| import org.schabi.newpipe.extractor.stream.VideoStream; | import org.schabi.newpipe.extractor.stream.VideoStream; | ||||||
| import org.schabi.newpipe.fragments.BackPressable; | import org.schabi.newpipe.fragments.BackPressable; | ||||||
| @@ -68,7 +67,6 @@ import org.schabi.newpipe.fragments.BaseStateFragment; | |||||||
| import org.schabi.newpipe.fragments.EmptyFragment; | import org.schabi.newpipe.fragments.EmptyFragment; | ||||||
| import org.schabi.newpipe.fragments.list.comments.CommentsFragment; | import org.schabi.newpipe.fragments.list.comments.CommentsFragment; | ||||||
| import org.schabi.newpipe.fragments.list.videos.RelatedVideosFragment; | import org.schabi.newpipe.fragments.list.videos.RelatedVideosFragment; | ||||||
| import org.schabi.newpipe.info_list.InfoItemDialog; |  | ||||||
| import org.schabi.newpipe.local.dialog.PlaylistAppendDialog; | import org.schabi.newpipe.local.dialog.PlaylistAppendDialog; | ||||||
| import org.schabi.newpipe.local.history.HistoryRecordManager; | import org.schabi.newpipe.local.history.HistoryRecordManager; | ||||||
| import org.schabi.newpipe.player.MainVideoPlayer; | import org.schabi.newpipe.player.MainVideoPlayer; | ||||||
| @@ -77,7 +75,6 @@ import org.schabi.newpipe.player.playqueue.PlayQueue; | |||||||
| import org.schabi.newpipe.player.playqueue.SinglePlayQueue; | import org.schabi.newpipe.player.playqueue.SinglePlayQueue; | ||||||
| import org.schabi.newpipe.report.ErrorActivity; | import org.schabi.newpipe.report.ErrorActivity; | ||||||
| import org.schabi.newpipe.report.UserAction; | import org.schabi.newpipe.report.UserAction; | ||||||
| import org.schabi.newpipe.util.AnimationUtils; |  | ||||||
| import org.schabi.newpipe.util.Constants; | import org.schabi.newpipe.util.Constants; | ||||||
| import org.schabi.newpipe.util.ExtractorHelper; | import org.schabi.newpipe.util.ExtractorHelper; | ||||||
| import org.schabi.newpipe.util.ImageDisplayConstants; | import org.schabi.newpipe.util.ImageDisplayConstants; | ||||||
| @@ -86,14 +83,16 @@ import org.schabi.newpipe.util.ListHelper; | |||||||
| import org.schabi.newpipe.util.Localization; | import org.schabi.newpipe.util.Localization; | ||||||
| import org.schabi.newpipe.util.NavigationHelper; | import org.schabi.newpipe.util.NavigationHelper; | ||||||
| import org.schabi.newpipe.util.PermissionHelper; | import org.schabi.newpipe.util.PermissionHelper; | ||||||
|  | import org.schabi.newpipe.util.ShareUtils; | ||||||
| import org.schabi.newpipe.util.StreamItemAdapter; | import org.schabi.newpipe.util.StreamItemAdapter; | ||||||
| import org.schabi.newpipe.util.StreamItemAdapter.StreamSizeWrapper; | import org.schabi.newpipe.util.StreamItemAdapter.StreamSizeWrapper; | ||||||
|  | import org.schabi.newpipe.views.AnimatedProgressBar; | ||||||
|  |  | ||||||
| import java.io.Serializable; | import java.io.Serializable; | ||||||
| import java.util.Collection; | import java.util.Collection; | ||||||
| import java.util.Collections; |  | ||||||
| import java.util.LinkedList; | import java.util.LinkedList; | ||||||
| import java.util.List; | import java.util.List; | ||||||
|  | import java.util.concurrent.TimeUnit; | ||||||
|  |  | ||||||
| import icepick.State; | import icepick.State; | ||||||
| import io.reactivex.Single; | import io.reactivex.Single; | ||||||
| @@ -117,11 +116,12 @@ public class VideoDetailFragment | |||||||
|     private static final int RELATED_STREAMS_UPDATE_FLAG = 0x1; |     private static final int RELATED_STREAMS_UPDATE_FLAG = 0x1; | ||||||
|     private static final int RESOLUTIONS_MENU_UPDATE_FLAG = 0x2; |     private static final int RESOLUTIONS_MENU_UPDATE_FLAG = 0x2; | ||||||
|     private static final int TOOLBAR_ITEMS_UPDATE_FLAG = 0x4; |     private static final int TOOLBAR_ITEMS_UPDATE_FLAG = 0x4; | ||||||
|     private static final int COMMENTS_UPDATE_FLAG = 0x4; |     private static final int COMMENTS_UPDATE_FLAG = 0x8; | ||||||
|  |  | ||||||
|     private boolean autoPlayEnabled; |     private boolean autoPlayEnabled; | ||||||
|     private boolean showRelatedStreams; |     private boolean showRelatedStreams; | ||||||
|     private boolean showComments; |     private boolean showComments; | ||||||
|  |     private String selectedTabTag; | ||||||
|  |  | ||||||
|     @State |     @State | ||||||
|     protected int serviceId = Constants.NO_SERVICE_ID; |     protected int serviceId = Constants.NO_SERVICE_ID; | ||||||
| @@ -134,6 +134,8 @@ public class VideoDetailFragment | |||||||
|     private Disposable currentWorker; |     private Disposable currentWorker; | ||||||
|     @NonNull |     @NonNull | ||||||
|     private CompositeDisposable disposables = new CompositeDisposable(); |     private CompositeDisposable disposables = new CompositeDisposable(); | ||||||
|  |     @Nullable | ||||||
|  |     private Disposable positionSubscriber = null; | ||||||
|  |  | ||||||
|     private List<VideoStream> sortedVideoStreams; |     private List<VideoStream> sortedVideoStreams; | ||||||
|     private int selectedVideoStreamIndex = -1; |     private int selectedVideoStreamIndex = -1; | ||||||
| @@ -151,6 +153,7 @@ public class VideoDetailFragment | |||||||
|     private View thumbnailBackgroundButton; |     private View thumbnailBackgroundButton; | ||||||
|     private ImageView thumbnailImageView; |     private ImageView thumbnailImageView; | ||||||
|     private ImageView thumbnailPlayButton; |     private ImageView thumbnailPlayButton; | ||||||
|  |     private AnimatedProgressBar positionView; | ||||||
|  |  | ||||||
|     private View videoTitleRoot; |     private View videoTitleRoot; | ||||||
|     private TextView videoTitleTextView; |     private TextView videoTitleTextView; | ||||||
| @@ -163,6 +166,7 @@ public class VideoDetailFragment | |||||||
|     private TextView detailControlsDownload; |     private TextView detailControlsDownload; | ||||||
|     private TextView appendControlsDetail; |     private TextView appendControlsDetail; | ||||||
|     private TextView detailDurationView; |     private TextView detailDurationView; | ||||||
|  |     private TextView detailPositionView; | ||||||
|  |  | ||||||
|     private LinearLayout videoDescriptionRootLayout; |     private LinearLayout videoDescriptionRootLayout; | ||||||
|     private TextView videoUploadDateView; |     private TextView videoUploadDateView; | ||||||
| @@ -213,6 +217,9 @@ public class VideoDetailFragment | |||||||
|         showComments = PreferenceManager.getDefaultSharedPreferences(activity) |         showComments = PreferenceManager.getDefaultSharedPreferences(activity) | ||||||
|                 .getBoolean(getString(R.string.show_comments_key), true); |                 .getBoolean(getString(R.string.show_comments_key), true); | ||||||
|  |  | ||||||
|  |         selectedTabTag = PreferenceManager.getDefaultSharedPreferences(activity) | ||||||
|  |                 .getString(getString(R.string.stream_info_selected_tab_key), COMMENTS_TAB_TAG); | ||||||
|  |  | ||||||
|         PreferenceManager.getDefaultSharedPreferences(activity) |         PreferenceManager.getDefaultSharedPreferences(activity) | ||||||
|                 .registerOnSharedPreferenceChangeListener(this); |                 .registerOnSharedPreferenceChangeListener(this); | ||||||
|     } |     } | ||||||
| @@ -226,6 +233,10 @@ public class VideoDetailFragment | |||||||
|     public void onPause() { |     public void onPause() { | ||||||
|         super.onPause(); |         super.onPause(); | ||||||
|         if (currentWorker != null) currentWorker.dispose(); |         if (currentWorker != null) currentWorker.dispose(); | ||||||
|  |         PreferenceManager.getDefaultSharedPreferences(getContext()) | ||||||
|  |                 .edit() | ||||||
|  |                 .putString(getString(R.string.stream_info_selected_tab_key), pageAdapter.getItemTitle(viewPager.getCurrentItem())) | ||||||
|  |                 .apply(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
| @@ -250,6 +261,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 if (currentInfo != null) { | ||||||
|  |             updateProgressInfo(currentInfo); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -259,8 +272,10 @@ public class VideoDetailFragment | |||||||
|         PreferenceManager.getDefaultSharedPreferences(activity) |         PreferenceManager.getDefaultSharedPreferences(activity) | ||||||
|                 .unregisterOnSharedPreferenceChangeListener(this); |                 .unregisterOnSharedPreferenceChangeListener(this); | ||||||
|  |  | ||||||
|  |         if (positionSubscriber != null) positionSubscriber.dispose(); | ||||||
|         if (currentWorker != null) currentWorker.dispose(); |         if (currentWorker != null) currentWorker.dispose(); | ||||||
|         if (disposables != null) disposables.clear(); |         if (disposables != null) disposables.clear(); | ||||||
|  |         positionSubscriber = null; | ||||||
|         currentWorker = null; |         currentWorker = null; | ||||||
|         disposables = null; |         disposables = null; | ||||||
|     } |     } | ||||||
| @@ -453,6 +468,7 @@ public class VideoDetailFragment | |||||||
|         videoTitleTextView = rootView.findViewById(R.id.detail_video_title_view); |         videoTitleTextView = rootView.findViewById(R.id.detail_video_title_view); | ||||||
|         videoTitleToggleArrow = rootView.findViewById(R.id.detail_toggle_description_view); |         videoTitleToggleArrow = rootView.findViewById(R.id.detail_toggle_description_view); | ||||||
|         videoCountView = rootView.findViewById(R.id.detail_view_count_view); |         videoCountView = rootView.findViewById(R.id.detail_view_count_view); | ||||||
|  |         positionView = rootView.findViewById(R.id.position_view); | ||||||
|  |  | ||||||
|         detailControlsBackground = rootView.findViewById(R.id.detail_controls_background); |         detailControlsBackground = rootView.findViewById(R.id.detail_controls_background); | ||||||
|         detailControlsPopup = rootView.findViewById(R.id.detail_controls_popup); |         detailControlsPopup = rootView.findViewById(R.id.detail_controls_popup); | ||||||
| @@ -460,6 +476,7 @@ public class VideoDetailFragment | |||||||
|         detailControlsDownload = rootView.findViewById(R.id.detail_controls_download); |         detailControlsDownload = rootView.findViewById(R.id.detail_controls_download); | ||||||
|         appendControlsDetail = rootView.findViewById(R.id.touch_append_detail); |         appendControlsDetail = rootView.findViewById(R.id.touch_append_detail); | ||||||
|         detailDurationView = rootView.findViewById(R.id.detail_duration_view); |         detailDurationView = rootView.findViewById(R.id.detail_duration_view); | ||||||
|  |         detailPositionView = rootView.findViewById(R.id.detail_position_view); | ||||||
|  |  | ||||||
|         videoDescriptionRootLayout = rootView.findViewById(R.id.detail_description_root_layout); |         videoDescriptionRootLayout = rootView.findViewById(R.id.detail_description_root_layout); | ||||||
|         videoUploadDateView = rootView.findViewById(R.id.detail_upload_date_view); |         videoUploadDateView = rootView.findViewById(R.id.detail_upload_date_view); | ||||||
| @@ -467,7 +484,6 @@ public class VideoDetailFragment | |||||||
|         videoDescriptionView.setMovementMethod(LinkMovementMethod.getInstance()); |         videoDescriptionView.setMovementMethod(LinkMovementMethod.getInstance()); | ||||||
|         videoDescriptionView.setAutoLinkMask(Linkify.WEB_URLS); |         videoDescriptionView.setAutoLinkMask(Linkify.WEB_URLS); | ||||||
|  |  | ||||||
|         //thumbsRootLayout = rootView.findViewById(R.id.detail_thumbs_root_layout); |  | ||||||
|         thumbsUpTextView = rootView.findViewById(R.id.detail_thumbs_up_count_view); |         thumbsUpTextView = rootView.findViewById(R.id.detail_thumbs_up_count_view); | ||||||
|         thumbsUpImageView = rootView.findViewById(R.id.detail_thumbs_up_img_view); |         thumbsUpImageView = rootView.findViewById(R.id.detail_thumbs_up_img_view); | ||||||
|         thumbsDownTextView = rootView.findViewById(R.id.detail_thumbs_down_count_view); |         thumbsDownTextView = rootView.findViewById(R.id.detail_thumbs_down_count_view); | ||||||
| @@ -513,42 +529,6 @@ public class VideoDetailFragment | |||||||
|         detailControlsPopup.setOnTouchListener(getOnControlsTouchListener()); |         detailControlsPopup.setOnTouchListener(getOnControlsTouchListener()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private void showStreamDialog(final StreamInfoItem item) { |  | ||||||
|         final Context context = getContext(); |  | ||||||
|         if (context == null || context.getResources() == null || getActivity() == null) return; |  | ||||||
|  |  | ||||||
|         final String[] commands = new String[]{ |  | ||||||
|                 context.getResources().getString(R.string.enqueue_on_background), |  | ||||||
|                 context.getResources().getString(R.string.enqueue_on_popup), |  | ||||||
|                 context.getResources().getString(R.string.append_playlist), |  | ||||||
|                 context.getResources().getString(R.string.share) |  | ||||||
|         }; |  | ||||||
|  |  | ||||||
|         final DialogInterface.OnClickListener actions = (DialogInterface dialogInterface, int i) -> { |  | ||||||
|             switch (i) { |  | ||||||
|                 case 0: |  | ||||||
|                     NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(item)); |  | ||||||
|                     break; |  | ||||||
|                 case 1: |  | ||||||
|                     NavigationHelper.enqueueOnPopupPlayer(getActivity(), new SinglePlayQueue(item)); |  | ||||||
|                     break; |  | ||||||
|                 case 2: |  | ||||||
|                     if (getFragmentManager() != null) { |  | ||||||
|                         PlaylistAppendDialog.fromStreamInfoItems(Collections.singletonList(item)) |  | ||||||
|                                 .show(getFragmentManager(), TAG); |  | ||||||
|                     } |  | ||||||
|                     break; |  | ||||||
|                 case 3: |  | ||||||
|                     shareUrl(item.getName(), item.getUrl()); |  | ||||||
|                     break; |  | ||||||
|                 default: |  | ||||||
|                     break; |  | ||||||
|             } |  | ||||||
|         }; |  | ||||||
|  |  | ||||||
|         new InfoItemDialog(getActivity(), item, commands, actions).show(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private View.OnTouchListener getOnControlsTouchListener() { |     private View.OnTouchListener getOnControlsTouchListener() { | ||||||
|         return (View view, MotionEvent motionEvent) -> { |         return (View view, MotionEvent motionEvent) -> { | ||||||
|             if (!PreferenceManager.getDefaultSharedPreferences(activity) |             if (!PreferenceManager.getDefaultSharedPreferences(activity) | ||||||
| @@ -628,13 +608,13 @@ public class VideoDetailFragment | |||||||
|         switch (id) { |         switch (id) { | ||||||
|             case R.id.menu_item_share: { |             case R.id.menu_item_share: { | ||||||
|                 if (currentInfo != null) { |                 if (currentInfo != null) { | ||||||
|                     shareUrl(currentInfo.getName(), currentInfo.getOriginalUrl()); |                     ShareUtils.shareUrl(this.getContext(), currentInfo.getName(), currentInfo.getOriginalUrl()); | ||||||
|                 } |                 } | ||||||
|                 return true; |                 return true; | ||||||
|             } |             } | ||||||
|             case R.id.menu_item_openInBrowser: { |             case R.id.menu_item_openInBrowser: { | ||||||
|                 if (currentInfo != null) { |                 if (currentInfo != null) { | ||||||
|                     openUrlInBrowser(currentInfo.getOriginalUrl()); |                     ShareUtils.openUrlInBrowser(this.getContext(), currentInfo.getOriginalUrl()); | ||||||
|                 } |                 } | ||||||
|                 return true; |                 return true; | ||||||
|             } |             } | ||||||
| @@ -815,6 +795,9 @@ public class VideoDetailFragment | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     private void initTabs() { |     private void initTabs() { | ||||||
|  |         if (pageAdapter.getCount() != 0) { | ||||||
|  |             selectedTabTag = pageAdapter.getItemTitle(viewPager.getCurrentItem()); | ||||||
|  |         } | ||||||
|         pageAdapter.clearAllItems(); |         pageAdapter.clearAllItems(); | ||||||
|  |  | ||||||
|         if(shouldShowComments()){ |         if(shouldShowComments()){ | ||||||
| @@ -835,6 +818,8 @@ public class VideoDetailFragment | |||||||
|         if(pageAdapter.getCount() < 2){ |         if(pageAdapter.getCount() < 2){ | ||||||
|             tabLayout.setVisibility(View.GONE); |             tabLayout.setVisibility(View.GONE); | ||||||
|         }else{ |         }else{ | ||||||
|  |             int position = pageAdapter.getItemPositionByTitle(selectedTabTag); | ||||||
|  |             if(position != -1) viewPager.setCurrentItem(position); | ||||||
|             tabLayout.setVisibility(View.VISIBLE); |             tabLayout.setVisibility(View.VISIBLE); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @@ -876,11 +861,11 @@ public class VideoDetailFragment | |||||||
|  |  | ||||||
|         final PlayQueue itemQueue = new SinglePlayQueue(currentInfo); |         final PlayQueue itemQueue = new SinglePlayQueue(currentInfo); | ||||||
|         if (append) { |         if (append) { | ||||||
|             NavigationHelper.enqueueOnPopupPlayer(activity, itemQueue); |             NavigationHelper.enqueueOnPopupPlayer(activity, itemQueue, false); | ||||||
|         } else { |         } else { | ||||||
|             Toast.makeText(activity, R.string.popup_playing_toast, Toast.LENGTH_SHORT).show(); |             Toast.makeText(activity, R.string.popup_playing_toast, Toast.LENGTH_SHORT).show(); | ||||||
|             final Intent intent = NavigationHelper.getPlayerIntent( |             final Intent intent = NavigationHelper.getPlayerIntent( | ||||||
|                     activity, PopupVideoPlayer.class, itemQueue, getSelectedVideoStream().resolution |                     activity, PopupVideoPlayer.class, itemQueue, getSelectedVideoStream().resolution, true | ||||||
|             ); |             ); | ||||||
|             activity.startService(intent); |             activity.startService(intent); | ||||||
|         } |         } | ||||||
| @@ -900,9 +885,9 @@ public class VideoDetailFragment | |||||||
|     private void openNormalBackgroundPlayer(final boolean append) { |     private void openNormalBackgroundPlayer(final boolean append) { | ||||||
|         final PlayQueue itemQueue = new SinglePlayQueue(currentInfo); |         final PlayQueue itemQueue = new SinglePlayQueue(currentInfo); | ||||||
|         if (append) { |         if (append) { | ||||||
|             NavigationHelper.enqueueOnBackgroundPlayer(activity, itemQueue); |             NavigationHelper.enqueueOnBackgroundPlayer(activity, itemQueue, false); | ||||||
|         } else { |         } else { | ||||||
|             NavigationHelper.playOnBackgroundPlayer(activity, itemQueue); |             NavigationHelper.playOnBackgroundPlayer(activity, itemQueue, true); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -912,7 +897,7 @@ public class VideoDetailFragment | |||||||
|         mIntent = NavigationHelper.getPlayerIntent(activity, |         mIntent = NavigationHelper.getPlayerIntent(activity, | ||||||
|                 MainVideoPlayer.class, |                 MainVideoPlayer.class, | ||||||
|                 playQueue, |                 playQueue, | ||||||
|                 getSelectedVideoStream().getResolution()); |                 getSelectedVideoStream().getResolution(), true); | ||||||
|         startActivity(mIntent); |         startActivity(mIntent); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -979,7 +964,7 @@ public class VideoDetailFragment | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     private void showContent() { |     private void showContent() { | ||||||
|         AnimationUtils.slideUp(contentRootLayoutHiding,120, 96, 0.06f); |         contentRootLayoutHiding.setVisibility(View.VISIBLE); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     protected void setInitialData(int serviceId, String url, String name) { |     protected void setInitialData(int serviceId, String url, String name) { | ||||||
| @@ -1012,12 +997,19 @@ public class VideoDetailFragment | |||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public void showLoading() { |     public void showLoading() { | ||||||
|  |  | ||||||
|         super.showLoading(); |         super.showLoading(); | ||||||
|  |  | ||||||
|  |         //if data is already cached, transition from VISIBLE -> INVISIBLE -> VISIBLE is not required | ||||||
|  |         if(!ExtractorHelper.isCached(serviceId, url, InfoItem.InfoType.STREAM)){ | ||||||
|             contentRootLayoutHiding.setVisibility(View.INVISIBLE); |             contentRootLayoutHiding.setVisibility(View.INVISIBLE); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         animateView(spinnerToolbar, false, 200); |         animateView(spinnerToolbar, false, 200); | ||||||
|         animateView(thumbnailPlayButton, false, 50); |         animateView(thumbnailPlayButton, false, 50); | ||||||
|         animateView(detailDurationView, false, 100); |         animateView(detailDurationView, false, 100); | ||||||
|  |         animateView(detailPositionView, false, 100); | ||||||
|  |         animateView(positionView, false, 50); | ||||||
|  |  | ||||||
|         videoTitleTextView.setText(name != null ? name : ""); |         videoTitleTextView.setText(name != null ? name : ""); | ||||||
|         videoTitleTextView.setMaxLines(1); |         videoTitleTextView.setMaxLines(1); | ||||||
| @@ -1132,6 +1124,7 @@ public class VideoDetailFragment | |||||||
|             videoUploadDateView.setText(Localization.localizeDate(activity, info.getUploadDate())); |             videoUploadDateView.setText(Localization.localizeDate(activity, info.getUploadDate())); | ||||||
|         } |         } | ||||||
|         prepareDescription(info.getDescription()); |         prepareDescription(info.getDescription()); | ||||||
|  |         updateProgressInfo(info); | ||||||
|  |  | ||||||
|         animateView(spinnerToolbar, true, 500); |         animateView(spinnerToolbar, true, 500); | ||||||
|         setupActionBar(info); |         setupActionBar(info); | ||||||
| @@ -1181,7 +1174,7 @@ public class VideoDetailFragment | |||||||
|                 downloadDialog.setSelectedVideoStream(selectedVideoStreamIndex); |                 downloadDialog.setSelectedVideoStream(selectedVideoStreamIndex); | ||||||
|                 downloadDialog.setSubtitleStreams(currentInfo.getSubtitles()); |                 downloadDialog.setSubtitleStreams(currentInfo.getSubtitles()); | ||||||
|  |  | ||||||
|                 downloadDialog.show(activity.getSupportFragmentManager(), "downloadDialog"); |                 downloadDialog.show(getActivity().getSupportFragmentManager(), "downloadDialog"); | ||||||
|             } catch (Exception e) { |             } catch (Exception e) { | ||||||
|                 ErrorActivity.ErrorInfo info = ErrorActivity.ErrorInfo.make(UserAction.UI_ERROR, |                 ErrorActivity.ErrorInfo info = ErrorActivity.ErrorInfo.make(UserAction.UI_ERROR, | ||||||
|                         ServiceList.all() |                         ServiceList.all() | ||||||
| @@ -1206,9 +1199,7 @@ public class VideoDetailFragment | |||||||
|     protected boolean onError(Throwable exception) { |     protected boolean onError(Throwable exception) { | ||||||
|         if (super.onError(exception)) return true; |         if (super.onError(exception)) return true; | ||||||
|  |  | ||||||
|         if (exception instanceof YoutubeStreamExtractor.GemaException) { |         else if (exception instanceof ContentNotAvailableException) { | ||||||
|             onBlockedByGemaError(); |  | ||||||
|         } else if (exception instanceof ContentNotAvailableException) { |  | ||||||
|             showError(getString(R.string.content_not_available), false); |             showError(getString(R.string.content_not_available), false); | ||||||
|         } else { |         } else { | ||||||
|             int errorId = exception instanceof YoutubeStreamExtractor.DecryptException |             int errorId = exception instanceof YoutubeStreamExtractor.DecryptException | ||||||
| @@ -1226,14 +1217,36 @@ public class VideoDetailFragment | |||||||
|         return true; |         return true; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public void onBlockedByGemaError() { |     private void updateProgressInfo(@NonNull final StreamInfo info) { | ||||||
|         thumbnailBackgroundButton.setOnClickListener((View v) -> { |         if (positionSubscriber != null) { | ||||||
|             Intent intent = new Intent(); |             positionSubscriber.dispose(); | ||||||
|             intent.setAction(Intent.ACTION_VIEW); |         } | ||||||
|             intent.setData(Uri.parse(getString(R.string.c3s_url))); |         final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity); | ||||||
|             startActivity(intent); |         final boolean playbackResumeEnabled = | ||||||
|  |                 prefs.getBoolean(activity.getString(R.string.enable_watch_history_key), true) | ||||||
|  |                         && prefs.getBoolean(activity.getString(R.string.enable_playback_resume_key), true); | ||||||
|  |         if (!playbackResumeEnabled || info.getDuration() <= 0) { | ||||||
|  |             positionView.setVisibility(View.INVISIBLE); | ||||||
|  |             detailPositionView.setVisibility(View.GONE); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         final HistoryRecordManager recordManager = new HistoryRecordManager(requireContext()); | ||||||
|  |         positionSubscriber = recordManager.loadStreamState(info) | ||||||
|  |                 .subscribeOn(Schedulers.io()) | ||||||
|  |                 .onErrorComplete() | ||||||
|  |                 .observeOn(AndroidSchedulers.mainThread()) | ||||||
|  |                 .subscribe(state -> { | ||||||
|  |                     final int seconds = (int) TimeUnit.MILLISECONDS.toSeconds(state.getProgressTime()); | ||||||
|  |                     positionView.setMax((int) info.getDuration()); | ||||||
|  |                     positionView.setProgressAnimated(seconds); | ||||||
|  |                     detailPositionView.setText(Localization.getDurationString(seconds)); | ||||||
|  |                     animateView(positionView, true, 500); | ||||||
|  |                     animateView(detailPositionView, true, 500); | ||||||
|  |                 }, e -> { | ||||||
|  |                     if (DEBUG) e.printStackTrace(); | ||||||
|  |                 }, () -> { | ||||||
|  |                     animateView(positionView, false, 500); | ||||||
|  |                     animateView(detailPositionView, false, 500); | ||||||
|                 }); |                 }); | ||||||
|  |  | ||||||
|         showError(getString(R.string.blocked_by_gema), false, R.drawable.gruese_die_gema); |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -2,18 +2,17 @@ package org.schabi.newpipe.fragments.list; | |||||||
|  |  | ||||||
| import android.app.Activity; | import android.app.Activity; | ||||||
| import android.content.Context; | import android.content.Context; | ||||||
| import android.content.DialogInterface; |  | ||||||
| import android.content.SharedPreferences; | import android.content.SharedPreferences; | ||||||
| import android.content.res.Configuration; | import android.content.res.Configuration; | ||||||
| import android.content.res.Resources; | import android.content.res.Resources; | ||||||
| import android.os.Bundle; | import android.os.Bundle; | ||||||
| import android.preference.PreferenceManager; | import android.preference.PreferenceManager; | ||||||
| import android.support.annotation.NonNull; | import androidx.annotation.NonNull; | ||||||
| import android.support.v7.app.ActionBar; | import androidx.appcompat.app.ActionBar; | ||||||
| import android.support.v7.app.AppCompatActivity; | import androidx.appcompat.app.AppCompatActivity; | ||||||
| import android.support.v7.widget.GridLayoutManager; | import androidx.recyclerview.widget.GridLayoutManager; | ||||||
| import android.support.v7.widget.LinearLayoutManager; | import androidx.recyclerview.widget.LinearLayoutManager; | ||||||
| import android.support.v7.widget.RecyclerView; | import androidx.recyclerview.widget.RecyclerView; | ||||||
| import android.util.Log; | import android.util.Log; | ||||||
| import android.view.Menu; | import android.view.Menu; | ||||||
| import android.view.MenuInflater; | import android.view.MenuInflater; | ||||||
| @@ -25,18 +24,17 @@ import org.schabi.newpipe.extractor.channel.ChannelInfoItem; | |||||||
| import org.schabi.newpipe.extractor.comments.CommentsInfoItem; | import org.schabi.newpipe.extractor.comments.CommentsInfoItem; | ||||||
| import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem; | 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.extractor.stream.StreamType; | ||||||
| import org.schabi.newpipe.fragments.BaseStateFragment; | import org.schabi.newpipe.fragments.BaseStateFragment; | ||||||
| import org.schabi.newpipe.fragments.OnScrollBelowItemsListener; | import org.schabi.newpipe.fragments.OnScrollBelowItemsListener; | ||||||
| import org.schabi.newpipe.info_list.InfoItemDialog; | import org.schabi.newpipe.info_list.InfoItemDialog; | ||||||
| import org.schabi.newpipe.info_list.InfoListAdapter; | import org.schabi.newpipe.info_list.InfoListAdapter; | ||||||
| import org.schabi.newpipe.local.dialog.PlaylistAppendDialog; |  | ||||||
| import org.schabi.newpipe.player.playqueue.SinglePlayQueue; |  | ||||||
| import org.schabi.newpipe.report.ErrorActivity; | import org.schabi.newpipe.report.ErrorActivity; | ||||||
| import org.schabi.newpipe.util.NavigationHelper; | import org.schabi.newpipe.util.NavigationHelper; | ||||||
| import org.schabi.newpipe.util.OnClickGesture; | import org.schabi.newpipe.util.OnClickGesture; | ||||||
| import org.schabi.newpipe.util.StateSaver; | import org.schabi.newpipe.util.StateSaver; | ||||||
|  | import org.schabi.newpipe.util.StreamDialogEntry; | ||||||
|  |  | ||||||
| import java.util.Collections; |  | ||||||
| import java.util.List; | import java.util.List; | ||||||
| import java.util.Queue; | import java.util.Queue; | ||||||
|  |  | ||||||
| @@ -64,6 +62,11 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem | |||||||
|         infoListAdapter = new InfoListAdapter(activity); |         infoListAdapter = new InfoListAdapter(activity); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void onDetach() { | ||||||
|  |         super.onDetach(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public void onCreate(Bundle savedInstanceState) { |     public void onCreate(Bundle savedInstanceState) { | ||||||
|         super.onCreate(savedInstanceState); |         super.onCreate(savedInstanceState); | ||||||
| @@ -249,41 +252,32 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I> implem | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     protected void showStreamDialog(final StreamInfoItem item) { |     protected void showStreamDialog(final StreamInfoItem item) { | ||||||
|         final Context context = getContext(); |         final Context context = getContext(); | ||||||
|         final Activity activity = getActivity(); |         final Activity activity = getActivity(); | ||||||
|         if (context == null || context.getResources() == null || getActivity() == null) return; |         if (context == null || context.getResources() == null || activity == null) return; | ||||||
|  |  | ||||||
|         final String[] commands = new String[]{ |         if (item.getStreamType() == StreamType.AUDIO_STREAM) { | ||||||
|                 context.getResources().getString(R.string.enqueue_on_background), |             StreamDialogEntry.setEnabledEntries( | ||||||
|                 context.getResources().getString(R.string.enqueue_on_popup), |                     StreamDialogEntry.enqueue_on_background, | ||||||
|                 context.getResources().getString(R.string.append_playlist), |                     StreamDialogEntry.start_here_on_background, | ||||||
|                 context.getResources().getString(R.string.share) |                     StreamDialogEntry.append_playlist, | ||||||
|         }; |                     StreamDialogEntry.share); | ||||||
|  |         } else { | ||||||
|         final DialogInterface.OnClickListener actions = (dialogInterface, i) -> { |             StreamDialogEntry.setEnabledEntries( | ||||||
|             switch (i) { |                     StreamDialogEntry.enqueue_on_background, | ||||||
|                 case 0: |                     StreamDialogEntry.enqueue_on_popup, | ||||||
|                     NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(item)); |                     StreamDialogEntry.start_here_on_background, | ||||||
|                     break; |                     StreamDialogEntry.start_here_on_popup, | ||||||
|                 case 1: |                     StreamDialogEntry.append_playlist, | ||||||
|                     NavigationHelper.enqueueOnPopupPlayer(activity, new SinglePlayQueue(item)); |                     StreamDialogEntry.share); | ||||||
|                     break; |  | ||||||
|                 case 2: |  | ||||||
|                     if (getFragmentManager() != null) { |  | ||||||
|                         PlaylistAppendDialog.fromStreamInfoItems(Collections.singletonList(item)) |  | ||||||
|                                 .show(getFragmentManager(), TAG); |  | ||||||
|         } |         } | ||||||
|                     break; |  | ||||||
|                 case 3: |  | ||||||
|                     shareUrl(item.getName(), item.getUrl()); |  | ||||||
|                     break; |  | ||||||
|                 default: |  | ||||||
|                     break; |  | ||||||
|             } |  | ||||||
|         }; |  | ||||||
|  |  | ||||||
|         new InfoItemDialog(getActivity(), item, commands, actions).show(); |         new InfoItemDialog(activity, item, StreamDialogEntry.getCommands(context), (dialog, which) -> | ||||||
|  |             StreamDialogEntry.clickOn(which, this, item)).show(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /*////////////////////////////////////////////////////////////////////////// |     /*////////////////////////////////////////////////////////////////////////// | ||||||
|   | |||||||
| @@ -1,11 +1,12 @@ | |||||||
| package org.schabi.newpipe.fragments.list; | package org.schabi.newpipe.fragments.list; | ||||||
|  |  | ||||||
| import android.os.Bundle; | import android.os.Bundle; | ||||||
| import android.support.annotation.NonNull; |  | ||||||
| import android.text.TextUtils; | import android.text.TextUtils; | ||||||
| import android.util.Log; | import android.util.Log; | ||||||
| import android.view.View; | import android.view.View; | ||||||
|  |  | ||||||
|  | import androidx.annotation.NonNull; | ||||||
|  |  | ||||||
| import org.schabi.newpipe.extractor.ListExtractor; | import org.schabi.newpipe.extractor.ListExtractor; | ||||||
| import org.schabi.newpipe.extractor.ListInfo; | import org.schabi.newpipe.extractor.ListInfo; | ||||||
| import org.schabi.newpipe.util.Constants; | import org.schabi.newpipe.util.Constants; | ||||||
| @@ -61,9 +62,11 @@ public abstract class BaseListInfoFragment<I extends ListInfo> | |||||||
|     @Override |     @Override | ||||||
|     public void onDestroy() { |     public void onDestroy() { | ||||||
|         super.onDestroy(); |         super.onDestroy(); | ||||||
|         if (currentWorker != null) currentWorker.dispose(); |         if (currentWorker != null) { | ||||||
|  |             currentWorker.dispose(); | ||||||
|             currentWorker = null; |             currentWorker = null; | ||||||
|         } |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     /*////////////////////////////////////////////////////////////////////////// |     /*////////////////////////////////////////////////////////////////////////// | ||||||
|     // State Saving |     // State Saving | ||||||
|   | |||||||
| @@ -1,15 +1,13 @@ | |||||||
| package org.schabi.newpipe.fragments.list.channel; | package org.schabi.newpipe.fragments.list.channel; | ||||||
|  |  | ||||||
| import android.app.Activity; |  | ||||||
| import android.content.Context; | import android.content.Context; | ||||||
| import android.content.DialogInterface; |  | ||||||
| import android.content.Intent; | import android.content.Intent; | ||||||
| import android.net.Uri; | import android.net.Uri; | ||||||
| import android.os.Bundle; | import android.os.Bundle; | ||||||
| import android.support.annotation.NonNull; | import androidx.annotation.NonNull; | ||||||
| import android.support.annotation.Nullable; | import androidx.annotation.Nullable; | ||||||
| import android.support.v4.content.ContextCompat; | import androidx.core.content.ContextCompat; | ||||||
| import android.support.v7.app.ActionBar; | import androidx.appcompat.app.ActionBar; | ||||||
| import android.text.TextUtils; | import android.text.TextUtils; | ||||||
| import android.util.Log; | import android.util.Log; | ||||||
| import android.view.LayoutInflater; | import android.view.LayoutInflater; | ||||||
| @@ -35,21 +33,18 @@ import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException; | |||||||
| import org.schabi.newpipe.extractor.exceptions.ExtractionException; | import org.schabi.newpipe.extractor.exceptions.ExtractionException; | ||||||
| import org.schabi.newpipe.extractor.stream.StreamInfoItem; | import org.schabi.newpipe.extractor.stream.StreamInfoItem; | ||||||
| import org.schabi.newpipe.fragments.list.BaseListInfoFragment; | 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.local.subscription.SubscriptionService; | ||||||
| import org.schabi.newpipe.player.playqueue.ChannelPlayQueue; | import org.schabi.newpipe.player.playqueue.ChannelPlayQueue; | ||||||
| import org.schabi.newpipe.player.playqueue.PlayQueue; | import org.schabi.newpipe.player.playqueue.PlayQueue; | ||||||
| import org.schabi.newpipe.player.playqueue.SinglePlayQueue; |  | ||||||
| import org.schabi.newpipe.report.UserAction; | import org.schabi.newpipe.report.UserAction; | ||||||
| import org.schabi.newpipe.util.AnimationUtils; | import org.schabi.newpipe.util.AnimationUtils; | ||||||
| import org.schabi.newpipe.util.ExtractorHelper; | import org.schabi.newpipe.util.ExtractorHelper; | ||||||
| import org.schabi.newpipe.util.ImageDisplayConstants; | import org.schabi.newpipe.util.ImageDisplayConstants; | ||||||
| import org.schabi.newpipe.util.Localization; | import org.schabi.newpipe.util.Localization; | ||||||
| import org.schabi.newpipe.util.NavigationHelper; | import org.schabi.newpipe.util.NavigationHelper; | ||||||
|  | import org.schabi.newpipe.util.ShareUtils; | ||||||
|  |  | ||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
| import java.util.Collections; |  | ||||||
| import java.util.List; | import java.util.List; | ||||||
| import java.util.concurrent.TimeUnit; | import java.util.concurrent.TimeUnit; | ||||||
|  |  | ||||||
| @@ -150,56 +145,6 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> { | |||||||
|         return headerRootLayout; |         return headerRootLayout; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     protected void showStreamDialog(final StreamInfoItem item) { |  | ||||||
|         final Activity activity = getActivity(); |  | ||||||
|         final Context context = getContext(); |  | ||||||
|         if (context == null || context.getResources() == null || getActivity() == null) return; |  | ||||||
|  |  | ||||||
|         final String[] commands = new String[]{ |  | ||||||
|                 context.getResources().getString(R.string.enqueue_on_background), |  | ||||||
|                 context.getResources().getString(R.string.enqueue_on_popup), |  | ||||||
|                 context.getResources().getString(R.string.start_here_on_main), |  | ||||||
|                 context.getResources().getString(R.string.start_here_on_background), |  | ||||||
|                 context.getResources().getString(R.string.start_here_on_popup), |  | ||||||
|                 context.getResources().getString(R.string.append_playlist), |  | ||||||
|                 context.getResources().getString(R.string.share) |  | ||||||
|         }; |  | ||||||
|  |  | ||||||
|         final DialogInterface.OnClickListener actions = (DialogInterface dialogInterface, int i) -> { |  | ||||||
|             final int index = Math.max(infoListAdapter.getItemsList().indexOf(item), 0); |  | ||||||
|             switch (i) { |  | ||||||
|                 case 0: |  | ||||||
|                     NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(item)); |  | ||||||
|                     break; |  | ||||||
|                 case 1: |  | ||||||
|                     NavigationHelper.enqueueOnPopupPlayer(activity, new SinglePlayQueue(item)); |  | ||||||
|                     break; |  | ||||||
|                 case 2: |  | ||||||
|                     NavigationHelper.playOnMainPlayer(context, getPlayQueue(index)); |  | ||||||
|                     break; |  | ||||||
|                 case 3: |  | ||||||
|                     NavigationHelper.playOnBackgroundPlayer(context, getPlayQueue(index)); |  | ||||||
|                     break; |  | ||||||
|                 case 4: |  | ||||||
|                     NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(index)); |  | ||||||
|                     break; |  | ||||||
|                 case 5: |  | ||||||
|                     if (getFragmentManager() != null) { |  | ||||||
|                         PlaylistAppendDialog.fromStreamInfoItems(Collections.singletonList(item)) |  | ||||||
|                                 .show(getFragmentManager(), TAG); |  | ||||||
|                     } |  | ||||||
|                     break; |  | ||||||
|                 case 6: |  | ||||||
|                     shareUrl(item.getName(), item.getUrl()); |  | ||||||
|                     break; |  | ||||||
|                 default: |  | ||||||
|                     break; |  | ||||||
|             } |  | ||||||
|         }; |  | ||||||
|  |  | ||||||
|         new InfoItemDialog(getActivity(), item, commands, actions).show(); |  | ||||||
|     } |  | ||||||
|     /*////////////////////////////////////////////////////////////////////////// |     /*////////////////////////////////////////////////////////////////////////// | ||||||
|     // Menu |     // Menu | ||||||
|     //////////////////////////////////////////////////////////////////////////*/ |     //////////////////////////////////////////////////////////////////////////*/ | ||||||
| @@ -234,10 +179,10 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> { | |||||||
|                 openRssFeed(); |                 openRssFeed(); | ||||||
|                 break; |                 break; | ||||||
|             case R.id.menu_item_openInBrowser: |             case R.id.menu_item_openInBrowser: | ||||||
|                 openUrlInBrowser(currentInfo.getOriginalUrl()); |                 ShareUtils.openUrlInBrowser(this.getContext(), currentInfo.getOriginalUrl()); | ||||||
|                 break; |                 break; | ||||||
|             case R.id.menu_item_share: |             case R.id.menu_item_share: | ||||||
|                 shareUrl(name, currentInfo.getOriginalUrl()); |                 ShareUtils.shareUrl(this.getContext(), name, currentInfo.getOriginalUrl()); | ||||||
|                 break; |                 break; | ||||||
|             default: |             default: | ||||||
|                 return super.onOptionsItemSelected(item); |                 return super.onOptionsItemSelected(item); | ||||||
| @@ -440,11 +385,11 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> { | |||||||
|         monitorSubscription(result); |         monitorSubscription(result); | ||||||
|  |  | ||||||
|         headerPlayAllButton.setOnClickListener( |         headerPlayAllButton.setOnClickListener( | ||||||
|                 view -> NavigationHelper.playOnMainPlayer(activity, getPlayQueue())); |                 view -> NavigationHelper.playOnMainPlayer(activity, getPlayQueue(), false)); | ||||||
|         headerPopupButton.setOnClickListener( |         headerPopupButton.setOnClickListener( | ||||||
|                 view -> NavigationHelper.playOnPopupPlayer(activity, getPlayQueue())); |                 view -> NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(), false)); | ||||||
|         headerBackgroundButton.setOnClickListener( |         headerBackgroundButton.setOnClickListener( | ||||||
|                 view -> NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue())); |                 view -> NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue(), false)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private PlayQueue getPlayQueue() { |     private PlayQueue getPlayQueue() { | ||||||
|   | |||||||
| @@ -2,8 +2,8 @@ package org.schabi.newpipe.fragments.list.comments; | |||||||
|  |  | ||||||
| import android.content.Context; | import android.content.Context; | ||||||
| import android.os.Bundle; | import android.os.Bundle; | ||||||
| import android.support.annotation.NonNull; | import androidx.annotation.NonNull; | ||||||
| import android.support.annotation.Nullable; | import androidx.annotation.Nullable; | ||||||
| import android.view.LayoutInflater; | import android.view.LayoutInflater; | ||||||
| import android.view.Menu; | import android.view.Menu; | ||||||
| import android.view.MenuInflater; | import android.view.MenuInflater; | ||||||
| @@ -93,7 +93,7 @@ public class CommentsFragment extends BaseListInfoFragment<CommentsInfo> { | |||||||
|     public void handleResult(@NonNull CommentsInfo result) { |     public void handleResult(@NonNull CommentsInfo result) { | ||||||
|         super.handleResult(result); |         super.handleResult(result); | ||||||
|  |  | ||||||
|         AnimationUtils.slideUp(getView(),120, 96, 0.06f); |         AnimationUtils.slideUp(getView(),120, 150, 0.06f); | ||||||
|  |  | ||||||
|         if (!result.getErrors().isEmpty()) { |         if (!result.getErrors().isEmpty()) { | ||||||
|             showSnackBarError(result.getErrors(), UserAction.REQUESTED_COMMENTS, NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0); |             showSnackBarError(result.getErrors(), UserAction.REQUESTED_COMMENTS, NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0); | ||||||
|   | |||||||
| @@ -1,10 +1,9 @@ | |||||||
| package org.schabi.newpipe.fragments.list.kiosk; | package org.schabi.newpipe.fragments.list.kiosk; | ||||||
|  |  | ||||||
| import android.os.Bundle; | import android.os.Bundle; | ||||||
| import android.preference.PreferenceManager; | import androidx.annotation.NonNull; | ||||||
| import android.support.annotation.NonNull; | import androidx.annotation.Nullable; | ||||||
| import android.support.annotation.Nullable; | import androidx.appcompat.app.ActionBar; | ||||||
| import android.support.v7.app.ActionBar; |  | ||||||
| import android.view.LayoutInflater; | import android.view.LayoutInflater; | ||||||
| import android.view.Menu; | import android.view.Menu; | ||||||
| import android.view.MenuInflater; | import android.view.MenuInflater; | ||||||
| @@ -155,9 +154,7 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> { | |||||||
|         super.handleResult(result); |         super.handleResult(result); | ||||||
|  |  | ||||||
|         name = kioskTranslatedName; |         name = kioskTranslatedName; | ||||||
|         if(!useAsFrontPage) { |  | ||||||
|         setTitle(kioskTranslatedName); |         setTitle(kioskTranslatedName); | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if (!result.getErrors().isEmpty()) { |         if (!result.getErrors().isEmpty()) { | ||||||
|             showSnackBarError(result.getErrors(), |             showSnackBarError(result.getErrors(), | ||||||
|   | |||||||
| @@ -2,11 +2,10 @@ package org.schabi.newpipe.fragments.list.playlist; | |||||||
|  |  | ||||||
| import android.app.Activity; | import android.app.Activity; | ||||||
| import android.content.Context; | import android.content.Context; | ||||||
| import android.content.DialogInterface; |  | ||||||
| import android.os.Bundle; | import android.os.Bundle; | ||||||
| import android.support.annotation.NonNull; | import androidx.annotation.NonNull; | ||||||
| import android.support.annotation.Nullable; | import androidx.annotation.Nullable; | ||||||
| import android.support.v7.app.AppCompatActivity; | import androidx.appcompat.app.AppCompatActivity; | ||||||
| import android.text.TextUtils; | import android.text.TextUtils; | ||||||
| import android.util.Log; | import android.util.Log; | ||||||
| import android.view.LayoutInflater; | import android.view.LayoutInflater; | ||||||
| @@ -29,17 +28,19 @@ import org.schabi.newpipe.extractor.NewPipe; | |||||||
| import org.schabi.newpipe.extractor.exceptions.ExtractionException; | import org.schabi.newpipe.extractor.exceptions.ExtractionException; | ||||||
| import org.schabi.newpipe.extractor.playlist.PlaylistInfo; | import org.schabi.newpipe.extractor.playlist.PlaylistInfo; | ||||||
| import org.schabi.newpipe.extractor.stream.StreamInfoItem; | import org.schabi.newpipe.extractor.stream.StreamInfoItem; | ||||||
|  | import org.schabi.newpipe.extractor.stream.StreamType; | ||||||
| import org.schabi.newpipe.fragments.list.BaseListInfoFragment; | import org.schabi.newpipe.fragments.list.BaseListInfoFragment; | ||||||
| import org.schabi.newpipe.info_list.InfoItemDialog; | import org.schabi.newpipe.info_list.InfoItemDialog; | ||||||
| import org.schabi.newpipe.local.playlist.RemotePlaylistManager; | import org.schabi.newpipe.local.playlist.RemotePlaylistManager; | ||||||
| import org.schabi.newpipe.player.playqueue.PlayQueue; | import org.schabi.newpipe.player.playqueue.PlayQueue; | ||||||
| import org.schabi.newpipe.player.playqueue.PlaylistPlayQueue; | import org.schabi.newpipe.player.playqueue.PlaylistPlayQueue; | ||||||
| import org.schabi.newpipe.player.playqueue.SinglePlayQueue; |  | ||||||
| import org.schabi.newpipe.report.ErrorActivity; | import org.schabi.newpipe.report.ErrorActivity; | ||||||
| import org.schabi.newpipe.report.UserAction; | import org.schabi.newpipe.report.UserAction; | ||||||
| import org.schabi.newpipe.util.ExtractorHelper; | import org.schabi.newpipe.util.ExtractorHelper; | ||||||
| import org.schabi.newpipe.util.ImageDisplayConstants; | import org.schabi.newpipe.util.ImageDisplayConstants; | ||||||
| import org.schabi.newpipe.util.NavigationHelper; | import org.schabi.newpipe.util.NavigationHelper; | ||||||
|  | import org.schabi.newpipe.util.ShareUtils; | ||||||
|  | import org.schabi.newpipe.util.StreamDialogEntry; | ||||||
| import org.schabi.newpipe.util.ThemeHelper; | import org.schabi.newpipe.util.ThemeHelper; | ||||||
|  |  | ||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
| @@ -134,48 +135,40 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> { | |||||||
|         infoListAdapter.useMiniItemVariants(true); |         infoListAdapter.useMiniItemVariants(true); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     private PlayQueue getPlayQueueStartingAt(StreamInfoItem infoItem) { | ||||||
|  |         return getPlayQueue(Math.max(infoListAdapter.getItemsList().indexOf(infoItem), 0)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     protected void showStreamDialog(final StreamInfoItem item) { |     protected void showStreamDialog(StreamInfoItem item) { | ||||||
|         final Context context = getContext(); |         final Context context = getContext(); | ||||||
|         final Activity activity = getActivity(); |         final Activity activity = getActivity(); | ||||||
|         if (context == null || context.getResources() == null || getActivity() == null) return; |         if (context == null || context.getResources() == null || activity == null) return; | ||||||
|  |  | ||||||
|         final String[] commands = new String[]{ |         if (item.getStreamType() == StreamType.AUDIO_STREAM) { | ||||||
|                 context.getResources().getString(R.string.enqueue_on_background), |             StreamDialogEntry.setEnabledEntries( | ||||||
|                 context.getResources().getString(R.string.enqueue_on_popup), |                     StreamDialogEntry.enqueue_on_background, | ||||||
|                 context.getResources().getString(R.string.start_here_on_main), |                     StreamDialogEntry.start_here_on_background, | ||||||
|                 context.getResources().getString(R.string.start_here_on_background), |                     StreamDialogEntry.append_playlist, | ||||||
|                 context.getResources().getString(R.string.start_here_on_popup), |                     StreamDialogEntry.share); | ||||||
|                 context.getResources().getString(R.string.share) |         } else { | ||||||
|         }; |             StreamDialogEntry.setEnabledEntries( | ||||||
|  |                     StreamDialogEntry.enqueue_on_background, | ||||||
|  |                     StreamDialogEntry.enqueue_on_popup, | ||||||
|  |                     StreamDialogEntry.start_here_on_background, | ||||||
|  |                     StreamDialogEntry.start_here_on_popup, | ||||||
|  |                     StreamDialogEntry.append_playlist, | ||||||
|  |                     StreamDialogEntry.share); | ||||||
|  |  | ||||||
|         final DialogInterface.OnClickListener actions = (dialogInterface, i) -> { |             StreamDialogEntry.start_here_on_popup.setCustomAction( | ||||||
|             final int index = Math.max(infoListAdapter.getItemsList().indexOf(item), 0); |                     (fragment, infoItem) -> NavigationHelper.playOnPopupPlayer(context, getPlayQueueStartingAt(infoItem), true)); | ||||||
|             switch (i) { |  | ||||||
|                 case 0: |  | ||||||
|                     NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(item)); |  | ||||||
|                     break; |  | ||||||
|                 case 1: |  | ||||||
|                     NavigationHelper.enqueueOnPopupPlayer(activity, new SinglePlayQueue(item)); |  | ||||||
|                     break; |  | ||||||
|                 case 2: |  | ||||||
|                     NavigationHelper.playOnMainPlayer(context, getPlayQueue(index)); |  | ||||||
|                     break; |  | ||||||
|                 case 3: |  | ||||||
|                     NavigationHelper.playOnBackgroundPlayer(context, getPlayQueue(index)); |  | ||||||
|                     break; |  | ||||||
|                 case 4: |  | ||||||
|                     NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(index)); |  | ||||||
|                     break; |  | ||||||
|                 case 5: |  | ||||||
|                     shareUrl(item.getName(), item.getUrl()); |  | ||||||
|                     break; |  | ||||||
|                 default: |  | ||||||
|                     break; |  | ||||||
|         } |         } | ||||||
|         }; |  | ||||||
|  |  | ||||||
|         new InfoItemDialog(getActivity(), item, commands, actions).show(); |         StreamDialogEntry.start_here_on_background.setCustomAction( | ||||||
|  |                 (fragment, infoItem) -> NavigationHelper.playOnBackgroundPlayer(context, getPlayQueueStartingAt(infoItem), true)); | ||||||
|  |  | ||||||
|  |         new InfoItemDialog(activity, item, StreamDialogEntry.getCommands(context), (dialog, which) -> | ||||||
|  |                 StreamDialogEntry.clickOn(which, this, item)).show(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
| @@ -230,10 +223,10 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> { | |||||||
|     public boolean onOptionsItemSelected(MenuItem item) { |     public boolean onOptionsItemSelected(MenuItem item) { | ||||||
|         switch (item.getItemId()) { |         switch (item.getItemId()) { | ||||||
|             case R.id.menu_item_openInBrowser: |             case R.id.menu_item_openInBrowser: | ||||||
|                 openUrlInBrowser(url); |                 ShareUtils.openUrlInBrowser(this.getContext(), url); | ||||||
|                 break; |                 break; | ||||||
|             case R.id.menu_item_share: |             case R.id.menu_item_share: | ||||||
|                 shareUrl(name, url); |                 ShareUtils.shareUrl(this.getContext(), name, url); | ||||||
|                 break; |                 break; | ||||||
|             case R.id.menu_item_bookmark: |             case R.id.menu_item_bookmark: | ||||||
|                 onBookmarkClicked(); |                 onBookmarkClicked(); | ||||||
| @@ -300,19 +293,19 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> { | |||||||
|                 .subscribe(getPlaylistBookmarkSubscriber()); |                 .subscribe(getPlaylistBookmarkSubscriber()); | ||||||
|  |  | ||||||
|         headerPlayAllButton.setOnClickListener(view -> |         headerPlayAllButton.setOnClickListener(view -> | ||||||
|                 NavigationHelper.playOnMainPlayer(activity, getPlayQueue())); |                 NavigationHelper.playOnMainPlayer(activity, getPlayQueue(), false)); | ||||||
|         headerPopupButton.setOnClickListener(view -> |         headerPopupButton.setOnClickListener(view -> | ||||||
|                 NavigationHelper.playOnPopupPlayer(activity, getPlayQueue())); |                 NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(), false)); | ||||||
|         headerBackgroundButton.setOnClickListener(view -> |         headerBackgroundButton.setOnClickListener(view -> | ||||||
|                 NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue())); |                 NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue(), false)); | ||||||
|  |  | ||||||
|         headerPopupButton.setOnLongClickListener(view -> { |         headerPopupButton.setOnLongClickListener(view -> { | ||||||
|             NavigationHelper.enqueueOnPopupPlayer(activity, getPlayQueue()); |             NavigationHelper.enqueueOnPopupPlayer(activity, getPlayQueue(), true); | ||||||
|             return true; |             return true; | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         headerBackgroundButton.setOnLongClickListener(view -> { |         headerBackgroundButton.setOnLongClickListener(view -> { | ||||||
|             NavigationHelper.enqueueOnBackgroundPlayer(activity, getPlayQueue()); |             NavigationHelper.enqueueOnBackgroundPlayer(activity, getPlayQueue(), true); | ||||||
|             return true; |             return true; | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -6,13 +6,13 @@ import android.content.Intent; | |||||||
| import android.content.SharedPreferences; | import android.content.SharedPreferences; | ||||||
| import android.os.Bundle; | import android.os.Bundle; | ||||||
| import android.preference.PreferenceManager; | import android.preference.PreferenceManager; | ||||||
| import android.support.annotation.NonNull; | import androidx.annotation.NonNull; | ||||||
| import android.support.annotation.Nullable; | import androidx.annotation.Nullable; | ||||||
| import android.support.v7.app.ActionBar; | import androidx.appcompat.app.ActionBar; | ||||||
| import android.support.v7.app.AlertDialog; | import androidx.appcompat.app.AlertDialog; | ||||||
| import android.support.v7.widget.RecyclerView; | import androidx.recyclerview.widget.RecyclerView; | ||||||
| import android.support.v7.widget.TooltipCompat; | import androidx.appcompat.widget.TooltipCompat; | ||||||
| import android.support.v7.widget.helper.ItemTouchHelper; | import androidx.recyclerview.widget.ItemTouchHelper; | ||||||
| import android.text.Editable; | import android.text.Editable; | ||||||
| import android.text.TextUtils; | import android.text.TextUtils; | ||||||
| import android.text.TextWatcher; | import android.text.TextWatcher; | ||||||
| @@ -73,7 +73,7 @@ import io.reactivex.disposables.Disposable; | |||||||
| import io.reactivex.schedulers.Schedulers; | import io.reactivex.schedulers.Schedulers; | ||||||
| import io.reactivex.subjects.PublishSubject; | import io.reactivex.subjects.PublishSubject; | ||||||
|  |  | ||||||
| import static android.support.v7.widget.helper.ItemTouchHelper.Callback.makeMovementFlags; | import static androidx.recyclerview.widget.ItemTouchHelper.Callback.makeMovementFlags; | ||||||
| import static java.util.Arrays.asList; | import static java.util.Arrays.asList; | ||||||
| import static org.schabi.newpipe.util.AnimationUtils.animateView; | import static org.schabi.newpipe.util.AnimationUtils.animateView; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -2,8 +2,8 @@ package org.schabi.newpipe.fragments.list.search; | |||||||
|  |  | ||||||
| import android.content.Context; | import android.content.Context; | ||||||
| import android.content.res.TypedArray; | import android.content.res.TypedArray; | ||||||
| import android.support.annotation.AttrRes; | import androidx.annotation.AttrRes; | ||||||
| import android.support.v7.widget.RecyclerView; | import androidx.recyclerview.widget.RecyclerView; | ||||||
| import android.view.LayoutInflater; | import android.view.LayoutInflater; | ||||||
| import android.view.View; | import android.view.View; | ||||||
| import android.view.ViewGroup; | import android.view.ViewGroup; | ||||||
|   | |||||||
| @@ -4,8 +4,8 @@ import android.content.Context; | |||||||
| import android.content.SharedPreferences; | import android.content.SharedPreferences; | ||||||
| import android.os.Bundle; | import android.os.Bundle; | ||||||
| import android.preference.PreferenceManager; | import android.preference.PreferenceManager; | ||||||
| import android.support.annotation.NonNull; | import androidx.annotation.NonNull; | ||||||
| import android.support.annotation.Nullable; | import androidx.annotation.Nullable; | ||||||
| import android.view.LayoutInflater; | import android.view.LayoutInflater; | ||||||
| import android.view.Menu; | import android.view.Menu; | ||||||
| import android.view.MenuInflater; | import android.view.MenuInflater; | ||||||
|   | |||||||
| @@ -1,8 +1,7 @@ | |||||||
| package org.schabi.newpipe.info_list; | package org.schabi.newpipe.info_list; | ||||||
|  |  | ||||||
| import android.content.Context; | import android.content.Context; | ||||||
| import android.support.annotation.NonNull; | import androidx.annotation.NonNull; | ||||||
| import android.util.Log; |  | ||||||
| import android.view.View; | import android.view.View; | ||||||
| import android.view.ViewGroup; | import android.view.ViewGroup; | ||||||
|  |  | ||||||
| @@ -22,6 +21,7 @@ import org.schabi.newpipe.info_list.holder.PlaylistInfoItemHolder; | |||||||
| import org.schabi.newpipe.info_list.holder.PlaylistMiniInfoItemHolder; | import org.schabi.newpipe.info_list.holder.PlaylistMiniInfoItemHolder; | ||||||
| import org.schabi.newpipe.info_list.holder.StreamInfoItemHolder; | import org.schabi.newpipe.info_list.holder.StreamInfoItemHolder; | ||||||
| import org.schabi.newpipe.info_list.holder.StreamMiniInfoItemHolder; | import org.schabi.newpipe.info_list.holder.StreamMiniInfoItemHolder; | ||||||
|  | import org.schabi.newpipe.local.history.HistoryRecordManager; | ||||||
| import org.schabi.newpipe.util.OnClickGesture; | import org.schabi.newpipe.util.OnClickGesture; | ||||||
|  |  | ||||||
| /* | /* | ||||||
| @@ -59,13 +59,14 @@ public class InfoItemBuilder { | |||||||
|         this.context = context; |         this.context = context; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public View buildView(@NonNull ViewGroup parent, @NonNull final InfoItem infoItem) { |     public View buildView(@NonNull ViewGroup parent, @NonNull final InfoItem infoItem, final HistoryRecordManager historyRecordManager) { | ||||||
|         return buildView(parent, infoItem, false); |         return buildView(parent, infoItem, historyRecordManager, false); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public View buildView(@NonNull ViewGroup parent, @NonNull final InfoItem infoItem, boolean useMiniVariant) { |     public View buildView(@NonNull ViewGroup parent, @NonNull final InfoItem infoItem, | ||||||
|  |                           final HistoryRecordManager historyRecordManager, boolean useMiniVariant) { | ||||||
|         InfoItemHolder holder = holderFromInfoType(parent, infoItem.getInfoType(), useMiniVariant); |         InfoItemHolder holder = holderFromInfoType(parent, infoItem.getInfoType(), useMiniVariant); | ||||||
|         holder.updateFromItem(infoItem); |         holder.updateFromItem(infoItem, historyRecordManager); | ||||||
|         return holder.itemView; |         return holder.itemView; | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -80,7 +81,6 @@ public class InfoItemBuilder { | |||||||
|             case COMMENT: |             case COMMENT: | ||||||
|                 return useMiniVariant ? new CommentsMiniInfoItemHolder(this, parent) : new CommentsInfoItemHolder(this, parent); |                 return useMiniVariant ? new CommentsMiniInfoItemHolder(this, parent) : new CommentsInfoItemHolder(this, parent); | ||||||
|             default: |             default: | ||||||
|                 Log.e(TAG, "Trollolo"); |  | ||||||
|                 throw new RuntimeException("InfoType not expected = " + infoType.name()); |                 throw new RuntimeException("InfoType not expected = " + infoType.name()); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -3,8 +3,8 @@ package org.schabi.newpipe.info_list; | |||||||
| import android.app.Activity; | import android.app.Activity; | ||||||
| import android.app.AlertDialog; | import android.app.AlertDialog; | ||||||
| import android.content.DialogInterface; | import android.content.DialogInterface; | ||||||
| import android.support.annotation.NonNull; | import androidx.annotation.NonNull; | ||||||
| import android.support.annotation.Nullable; | import androidx.annotation.Nullable; | ||||||
| import android.view.View; | import android.view.View; | ||||||
| import android.widget.TextView; | import android.widget.TextView; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,22 +1,25 @@ | |||||||
| package org.schabi.newpipe.info_list; | package org.schabi.newpipe.info_list; | ||||||
|  |  | ||||||
| import android.app.Activity; | import android.content.Context; | ||||||
| import android.support.v7.widget.GridLayoutManager; | import androidx.annotation.NonNull; | ||||||
| import android.support.v7.widget.RecyclerView; | import androidx.annotation.Nullable; | ||||||
|  | import androidx.recyclerview.widget.GridLayoutManager; | ||||||
|  | import androidx.recyclerview.widget.RecyclerView; | ||||||
| import android.util.Log; | import android.util.Log; | ||||||
| import android.view.View; | import android.view.View; | ||||||
| import android.view.ViewGroup; | import android.view.ViewGroup; | ||||||
|  |  | ||||||
|  | import org.schabi.newpipe.database.stream.model.StreamStateEntity; | ||||||
| import org.schabi.newpipe.extractor.InfoItem; | import org.schabi.newpipe.extractor.InfoItem; | ||||||
| import org.schabi.newpipe.extractor.channel.ChannelInfoItem; | import org.schabi.newpipe.extractor.channel.ChannelInfoItem; | ||||||
| import org.schabi.newpipe.extractor.comments.CommentsInfoItem; | import org.schabi.newpipe.extractor.comments.CommentsInfoItem; | ||||||
| import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem; | 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.ChannelGridInfoItemHolder; | ||||||
| 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.CommentsInfoItemHolder; | ||||||
| import org.schabi.newpipe.info_list.holder.CommentsMiniInfoItemHolder; | import org.schabi.newpipe.info_list.holder.CommentsMiniInfoItemHolder; | ||||||
| import org.schabi.newpipe.info_list.holder.ChannelGridInfoItemHolder; |  | ||||||
| import org.schabi.newpipe.info_list.holder.InfoItemHolder; | import org.schabi.newpipe.info_list.holder.InfoItemHolder; | ||||||
| import org.schabi.newpipe.info_list.holder.PlaylistGridInfoItemHolder; | import org.schabi.newpipe.info_list.holder.PlaylistGridInfoItemHolder; | ||||||
| import org.schabi.newpipe.info_list.holder.PlaylistInfoItemHolder; | import org.schabi.newpipe.info_list.holder.PlaylistInfoItemHolder; | ||||||
| @@ -24,6 +27,7 @@ import org.schabi.newpipe.info_list.holder.PlaylistMiniInfoItemHolder; | |||||||
| import org.schabi.newpipe.info_list.holder.StreamGridInfoItemHolder; | import org.schabi.newpipe.info_list.holder.StreamGridInfoItemHolder; | ||||||
| import org.schabi.newpipe.info_list.holder.StreamInfoItemHolder; | import org.schabi.newpipe.info_list.holder.StreamInfoItemHolder; | ||||||
| import org.schabi.newpipe.info_list.holder.StreamMiniInfoItemHolder; | import org.schabi.newpipe.info_list.holder.StreamMiniInfoItemHolder; | ||||||
|  | import org.schabi.newpipe.local.history.HistoryRecordManager; | ||||||
| import org.schabi.newpipe.util.FallbackViewHolder; | import org.schabi.newpipe.util.FallbackViewHolder; | ||||||
| import org.schabi.newpipe.util.OnClickGesture; | import org.schabi.newpipe.util.OnClickGesture; | ||||||
|  |  | ||||||
| @@ -71,6 +75,8 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde | |||||||
|  |  | ||||||
|     private final InfoItemBuilder infoItemBuilder; |     private final InfoItemBuilder infoItemBuilder; | ||||||
|     private final ArrayList<InfoItem> infoItemList; |     private final ArrayList<InfoItem> infoItemList; | ||||||
|  |     private final HistoryRecordManager recordManager; | ||||||
|  |  | ||||||
|     private boolean useMiniVariant = false; |     private boolean useMiniVariant = false; | ||||||
|     private boolean useGridVariant = false; |     private boolean useGridVariant = false; | ||||||
|     private boolean showFooter = false; |     private boolean showFooter = false; | ||||||
| @@ -86,8 +92,9 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public InfoListAdapter(Activity a) { |     public InfoListAdapter(Context context) { | ||||||
|         infoItemBuilder = new InfoItemBuilder(a); |         this.recordManager = new HistoryRecordManager(context); | ||||||
|  |         infoItemBuilder = new InfoItemBuilder(context); | ||||||
|         infoItemList = new ArrayList<>(); |         infoItemList = new ArrayList<>(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -115,50 +122,53 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde | |||||||
|         this.useGridVariant = useGridVariant; |         this.useGridVariant = useGridVariant; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public void addInfoItemList(List<InfoItem> data) { |     public void addInfoItemList(@Nullable final List<InfoItem> data) { | ||||||
|         if (data != null) { |         if (data == null) { | ||||||
|             if (DEBUG) { |             return; | ||||||
|                 Log.d(TAG, "addInfoItemList() before > infoItemList.size() = " + infoItemList.size() + ", data.size() = " + data.size()); |  | ||||||
|         } |         } | ||||||
|  |         if (DEBUG) Log.d(TAG, "addInfoItemList() before > infoItemList.size() = " + | ||||||
|  |                 infoItemList.size() + ", data.size() = " + data.size()); | ||||||
|  |  | ||||||
|         int offsetStart = sizeConsideringHeaderOffset(); |         int offsetStart = sizeConsideringHeaderOffset(); | ||||||
|         infoItemList.addAll(data); |         infoItemList.addAll(data); | ||||||
|  |  | ||||||
|             if (DEBUG) { |         if (DEBUG) Log.d(TAG, "addInfoItemList() after > offsetStart = " + offsetStart + | ||||||
|                 Log.d(TAG, "addInfoItemList() after > offsetStart = " + offsetStart + ", infoItemList.size() = " + infoItemList.size() + ", header = " + header + ", footer = " + footer + ", showFooter = " + showFooter); |                 ", infoItemList.size() = " + infoItemList.size() + | ||||||
|             } |                 ", header = " + header + ", footer = " + footer + | ||||||
|  |                 ", showFooter = " + showFooter); | ||||||
|         notifyItemRangeInserted(offsetStart, data.size()); |         notifyItemRangeInserted(offsetStart, data.size()); | ||||||
|  |  | ||||||
|         if (footer != null && showFooter) { |         if (footer != null && showFooter) { | ||||||
|             int footerNow = sizeConsideringHeaderOffset(); |             int footerNow = sizeConsideringHeaderOffset(); | ||||||
|             notifyItemMoved(offsetStart, footerNow); |             notifyItemMoved(offsetStart, footerNow); | ||||||
|  |  | ||||||
|                 if (DEBUG) Log.d(TAG, "addInfoItemList() footer from " + offsetStart + " to " + footerNow); |             if (DEBUG) Log.d(TAG, "addInfoItemList() footer from " + offsetStart + | ||||||
|             } |                     " to " + footerNow); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public void addInfoItem(InfoItem data) { |     public void addInfoItem(@Nullable InfoItem data) { | ||||||
|         if (data != null) { |         if (data == null) { | ||||||
|             if (DEBUG) { |             return; | ||||||
|                 Log.d(TAG, "addInfoItem() before > infoItemList.size() = " + infoItemList.size() + ", thread = " + Thread.currentThread()); |  | ||||||
|         } |         } | ||||||
|  |         if (DEBUG) Log.d(TAG, "addInfoItem() before > infoItemList.size() = " + | ||||||
|  |                 infoItemList.size() + ", thread = " + Thread.currentThread()); | ||||||
|  |  | ||||||
|         int positionInserted = sizeConsideringHeaderOffset(); |         int positionInserted = sizeConsideringHeaderOffset(); | ||||||
|         infoItemList.add(data); |         infoItemList.add(data); | ||||||
|  |  | ||||||
|             if (DEBUG) { |         if (DEBUG) Log.d(TAG, "addInfoItem() after > position = " + positionInserted + | ||||||
|                 Log.d(TAG, "addInfoItem() after > position = " + positionInserted + ", infoItemList.size() = " + infoItemList.size() + ", header = " + header + ", footer = " + footer + ", showFooter = " + showFooter); |                 ", infoItemList.size() = " + infoItemList.size() + | ||||||
|             } |                 ", header = " + header + ", footer = " + footer + | ||||||
|  |                 ", showFooter = " + showFooter); | ||||||
|         notifyItemInserted(positionInserted); |         notifyItemInserted(positionInserted); | ||||||
|  |  | ||||||
|         if (footer != null && showFooter) { |         if (footer != null && showFooter) { | ||||||
|             int footerNow = sizeConsideringHeaderOffset(); |             int footerNow = sizeConsideringHeaderOffset(); | ||||||
|             notifyItemMoved(positionInserted, footerNow); |             notifyItemMoved(positionInserted, footerNow); | ||||||
|  |  | ||||||
|                 if (DEBUG) Log.d(TAG, "addInfoItem() footer from " + positionInserted + " to " + footerNow); |             if (DEBUG) Log.d(TAG, "addInfoItem() footer from " + positionInserted + | ||||||
|             } |                     " to " + footerNow); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -235,13 +245,13 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde | |||||||
|             case COMMENT: |             case COMMENT: | ||||||
|                 return useMiniVariant ? MINI_COMMENT_HOLDER_TYPE : COMMENT_HOLDER_TYPE; |                 return useMiniVariant ? MINI_COMMENT_HOLDER_TYPE : COMMENT_HOLDER_TYPE; | ||||||
|             default: |             default: | ||||||
|                 Log.e(TAG, "Trollolo"); |  | ||||||
|                 return -1; |                 return -1; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @NonNull | ||||||
|     @Override |     @Override | ||||||
|     public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int type) { |     public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int type) { | ||||||
|         if (DEBUG) |         if (DEBUG) | ||||||
|             Log.d(TAG, "onCreateViewHolder() called with: parent = [" + parent + "], type = [" + type + "]"); |             Log.d(TAG, "onCreateViewHolder() called with: parent = [" + parent + "], type = [" + type + "]"); | ||||||
|         switch (type) { |         switch (type) { | ||||||
| @@ -272,19 +282,18 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde | |||||||
|             case COMMENT_HOLDER_TYPE: |             case COMMENT_HOLDER_TYPE: | ||||||
|                 return new CommentsInfoItemHolder(infoItemBuilder, parent); |                 return new CommentsInfoItemHolder(infoItemBuilder, parent); | ||||||
|             default: |             default: | ||||||
|                 Log.e(TAG, "Trollolo"); |  | ||||||
|                 return new FallbackViewHolder(new View(parent.getContext())); |                 return new FallbackViewHolder(new View(parent.getContext())); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { |     public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { | ||||||
|         if (DEBUG) Log.d(TAG, "onBindViewHolder() called with: holder = [" + holder.getClass().getSimpleName() + "], position = [" + position + "]"); |         if (DEBUG) Log.d(TAG, "onBindViewHolder() called with: holder = [" + holder.getClass().getSimpleName() + "], position = [" + position + "]"); | ||||||
|         if (holder instanceof InfoItemHolder) { |         if (holder instanceof InfoItemHolder) { | ||||||
|             // If header isn't null, offset the items by -1 |             // If header isn't null, offset the items by -1 | ||||||
|             if (header != null) position--; |             if (header != null) position--; | ||||||
|  |  | ||||||
|             ((InfoItemHolder) holder).updateFromItem(infoItemList.get(position)); |             ((InfoItemHolder) holder).updateFromItem(infoItemList.get(position), recordManager); | ||||||
|         } else if (holder instanceof HFHolder && position == 0 && header != null) { |         } else if (holder instanceof HFHolder && position == 0 && header != null) { | ||||||
|             ((HFHolder) holder).view = header; |             ((HFHolder) holder).view = header; | ||||||
|         } else if (holder instanceof HFHolder && position == sizeConsideringHeaderOffset() && footer != null && showFooter) { |         } else if (holder instanceof HFHolder && position == sizeConsideringHeaderOffset() && footer != null && showFooter) { | ||||||
| @@ -292,6 +301,21 @@ public class InfoListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position, @NonNull List<Object> payloads) { | ||||||
|  |         if (!payloads.isEmpty() && holder instanceof InfoItemHolder) { | ||||||
|  |             for (Object payload : payloads) { | ||||||
|  |                 if (payload instanceof StreamStateEntity) { | ||||||
|  |                     ((InfoItemHolder) holder).updateState(infoItemList.get(header == null ? position : position - 1), recordManager); | ||||||
|  |                 } else if (payload instanceof Boolean) { | ||||||
|  |                     ((InfoItemHolder) holder).updateState(infoItemList.get(header == null ? position : position - 1), recordManager); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } else { | ||||||
|  |             onBindViewHolder(holder, position); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     public GridLayoutManager.SpanSizeLookup getSpanSizeLookup(final int spanCount) { |     public GridLayoutManager.SpanSizeLookup getSpanSizeLookup(final int spanCount) { | ||||||
|         return new GridLayoutManager.SpanSizeLookup() { |         return new GridLayoutManager.SpanSizeLookup() { | ||||||
|             @Override |             @Override | ||||||
|   | |||||||
| @@ -7,6 +7,7 @@ import org.schabi.newpipe.R; | |||||||
| import org.schabi.newpipe.extractor.InfoItem; | import org.schabi.newpipe.extractor.InfoItem; | ||||||
| import org.schabi.newpipe.extractor.channel.ChannelInfoItem; | import org.schabi.newpipe.extractor.channel.ChannelInfoItem; | ||||||
| import org.schabi.newpipe.info_list.InfoItemBuilder; | import org.schabi.newpipe.info_list.InfoItemBuilder; | ||||||
|  | import org.schabi.newpipe.local.history.HistoryRecordManager; | ||||||
| import org.schabi.newpipe.util.Localization; | import org.schabi.newpipe.util.Localization; | ||||||
|  |  | ||||||
| /* | /* | ||||||
| @@ -38,8 +39,8 @@ public class ChannelInfoItemHolder extends ChannelMiniInfoItemHolder { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public void updateFromItem(final InfoItem infoItem) { |     public void updateFromItem(final InfoItem infoItem, final HistoryRecordManager historyRecordManager) { | ||||||
|         super.updateFromItem(infoItem); |         super.updateFromItem(infoItem, historyRecordManager); | ||||||
|  |  | ||||||
|         if (!(infoItem instanceof ChannelInfoItem)) return; |         if (!(infoItem instanceof ChannelInfoItem)) return; | ||||||
|         final ChannelInfoItem item = (ChannelInfoItem) infoItem; |         final ChannelInfoItem item = (ChannelInfoItem) infoItem; | ||||||
|   | |||||||
| @@ -7,6 +7,7 @@ import org.schabi.newpipe.R; | |||||||
| import org.schabi.newpipe.extractor.InfoItem; | import org.schabi.newpipe.extractor.InfoItem; | ||||||
| import org.schabi.newpipe.extractor.channel.ChannelInfoItem; | import org.schabi.newpipe.extractor.channel.ChannelInfoItem; | ||||||
| import org.schabi.newpipe.info_list.InfoItemBuilder; | import org.schabi.newpipe.info_list.InfoItemBuilder; | ||||||
|  | import org.schabi.newpipe.local.history.HistoryRecordManager; | ||||||
| import org.schabi.newpipe.util.ImageDisplayConstants; | import org.schabi.newpipe.util.ImageDisplayConstants; | ||||||
| import org.schabi.newpipe.util.Localization; | import org.schabi.newpipe.util.Localization; | ||||||
|  |  | ||||||
| @@ -30,7 +31,7 @@ public class ChannelMiniInfoItemHolder extends InfoItemHolder { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public void updateFromItem(final InfoItem infoItem) { |     public void updateFromItem(final InfoItem infoItem, final HistoryRecordManager historyRecordManager) { | ||||||
|         if (!(infoItem instanceof ChannelInfoItem)) return; |         if (!(infoItem instanceof ChannelInfoItem)) return; | ||||||
|         final ChannelInfoItem item = (ChannelInfoItem) infoItem; |         final ChannelInfoItem item = (ChannelInfoItem) infoItem; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -5,10 +5,9 @@ import android.widget.TextView; | |||||||
|  |  | ||||||
| import org.schabi.newpipe.R; | import org.schabi.newpipe.R; | ||||||
| import org.schabi.newpipe.extractor.InfoItem; | import org.schabi.newpipe.extractor.InfoItem; | ||||||
| import org.schabi.newpipe.extractor.channel.ChannelInfoItem; |  | ||||||
| import org.schabi.newpipe.extractor.comments.CommentsInfoItem; | import org.schabi.newpipe.extractor.comments.CommentsInfoItem; | ||||||
| import org.schabi.newpipe.info_list.InfoItemBuilder; | import org.schabi.newpipe.info_list.InfoItemBuilder; | ||||||
| import org.schabi.newpipe.util.Localization; | import org.schabi.newpipe.local.history.HistoryRecordManager; | ||||||
|  |  | ||||||
| /* | /* | ||||||
|  * Created by Christian Schabesberger on 12.02.17. |  * Created by Christian Schabesberger on 12.02.17. | ||||||
| @@ -41,8 +40,8 @@ public class CommentsInfoItemHolder extends CommentsMiniInfoItemHolder { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public void updateFromItem(final InfoItem infoItem) { |     public void updateFromItem(final InfoItem infoItem, final HistoryRecordManager historyRecordManager) { | ||||||
|         super.updateFromItem(infoItem); |         super.updateFromItem(infoItem, historyRecordManager); | ||||||
|  |  | ||||||
|         if (!(infoItem instanceof CommentsInfoItem)) return; |         if (!(infoItem instanceof CommentsInfoItem)) return; | ||||||
|         final CommentsInfoItem item = (CommentsInfoItem) infoItem; |         final CommentsInfoItem item = (CommentsInfoItem) infoItem; | ||||||
|   | |||||||
| @@ -1,15 +1,16 @@ | |||||||
| package org.schabi.newpipe.info_list.holder; | package org.schabi.newpipe.info_list.holder; | ||||||
|  |  | ||||||
| import android.support.v7.app.AppCompatActivity; | import androidx.appcompat.app.AppCompatActivity; | ||||||
| import android.text.util.Linkify; | import android.text.util.Linkify; | ||||||
| import android.view.View; |  | ||||||
| import android.view.ViewGroup; | import android.view.ViewGroup; | ||||||
| import android.widget.TextView; | import android.widget.TextView; | ||||||
|  |  | ||||||
|  | import org.jsoup.helper.StringUtil; | ||||||
| import org.schabi.newpipe.R; | import org.schabi.newpipe.R; | ||||||
| import org.schabi.newpipe.extractor.InfoItem; | import org.schabi.newpipe.extractor.InfoItem; | ||||||
| import org.schabi.newpipe.extractor.comments.CommentsInfoItem; | import org.schabi.newpipe.extractor.comments.CommentsInfoItem; | ||||||
| import org.schabi.newpipe.info_list.InfoItemBuilder; | import org.schabi.newpipe.info_list.InfoItemBuilder; | ||||||
|  | import org.schabi.newpipe.local.history.HistoryRecordManager; | ||||||
| import org.schabi.newpipe.report.ErrorActivity; | import org.schabi.newpipe.report.ErrorActivity; | ||||||
| import org.schabi.newpipe.util.CommentTextOnTouchListener; | import org.schabi.newpipe.util.CommentTextOnTouchListener; | ||||||
| import org.schabi.newpipe.util.ImageDisplayConstants; | import org.schabi.newpipe.util.ImageDisplayConstants; | ||||||
| @@ -45,7 +46,7 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder { | |||||||
|             if(hours != null) timestamp += (Integer.parseInt(hours.replace(":", ""))*3600); |             if(hours != null) timestamp += (Integer.parseInt(hours.replace(":", ""))*3600); | ||||||
|             if(minutes != null) timestamp += (Integer.parseInt(minutes.replace(":", ""))*60); |             if(minutes != null) timestamp += (Integer.parseInt(minutes.replace(":", ""))*60); | ||||||
|             if(seconds != null) timestamp += (Integer.parseInt(seconds)); |             if(seconds != null) timestamp += (Integer.parseInt(seconds)); | ||||||
|             return streamUrl + url.replace(match.group(0), "&t=" + String.valueOf(timestamp)); |             return streamUrl + url.replace(match.group(0), "#timestamp=" + timestamp); | ||||||
|         } |         } | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
| @@ -64,7 +65,7 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public void updateFromItem(final InfoItem infoItem) { |     public void updateFromItem(final InfoItem infoItem, final HistoryRecordManager historyRecordManager) { | ||||||
|         if (!(infoItem instanceof CommentsInfoItem)) return; |         if (!(infoItem instanceof CommentsInfoItem)) return; | ||||||
|         final CommentsInfoItem item = (CommentsInfoItem) infoItem; |         final CommentsInfoItem item = (CommentsInfoItem) infoItem; | ||||||
|  |  | ||||||
| @@ -73,9 +74,8 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder { | |||||||
|                         itemThumbnailView, |                         itemThumbnailView, | ||||||
|                         ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS); |                         ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS); | ||||||
|  |  | ||||||
|         itemThumbnailView.setOnClickListener(new View.OnClickListener() { |         itemThumbnailView.setOnClickListener(view -> { | ||||||
|             @Override |             if(StringUtil.isBlank(item.getAuthorEndpoint())) return; | ||||||
|             public void onClick(View view) { |  | ||||||
|             try { |             try { | ||||||
|                 final AppCompatActivity activity = (AppCompatActivity) itemBuilder.getContext(); |                 final AppCompatActivity activity = (AppCompatActivity) itemBuilder.getContext(); | ||||||
|                 NavigationHelper.openChannelFragment( |                 NavigationHelper.openChannelFragment( | ||||||
| @@ -86,19 +86,17 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder { | |||||||
|             } catch (Exception e) { |             } catch (Exception e) { | ||||||
|                 ErrorActivity.reportUiError((AppCompatActivity) itemBuilder.getContext(), e); |                 ErrorActivity.reportUiError((AppCompatActivity) itemBuilder.getContext(), e); | ||||||
|             } |             } | ||||||
|             } |  | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         streamUrl = item.getUrl(); |         streamUrl = item.getUrl(); | ||||||
|  |  | ||||||
|         itemContentView.setMaxLines(commentDefaultLines); |         itemContentView.setLines(commentDefaultLines); | ||||||
|         commentText = item.getCommentText(); |         commentText = item.getCommentText(); | ||||||
|         itemContentView.setText(commentText); |         itemContentView.setText(commentText); | ||||||
|         linkify(); |  | ||||||
|         itemContentView.setOnTouchListener(CommentTextOnTouchListener.INSTANCE); |         itemContentView.setOnTouchListener(CommentTextOnTouchListener.INSTANCE); | ||||||
|  |  | ||||||
|         if (itemContentView.getLineCount() == 0) { |         if (itemContentView.getLineCount() == 0) { | ||||||
|             itemContentView.post(() -> ellipsize()); |             itemContentView.post(this::ellipsize); | ||||||
|         } else { |         } else { | ||||||
|             ellipsize(); |             ellipsize(); | ||||||
|         } |         } | ||||||
| @@ -119,15 +117,17 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder { | |||||||
|     private void ellipsize() { |     private void ellipsize() { | ||||||
|         if (itemContentView.getLineCount() > commentDefaultLines){ |         if (itemContentView.getLineCount() > commentDefaultLines){ | ||||||
|             int endOfLastLine = itemContentView.getLayout().getLineEnd(commentDefaultLines - 1); |             int endOfLastLine = itemContentView.getLayout().getLineEnd(commentDefaultLines - 1); | ||||||
|             String newVal = itemContentView.getText().subSequence(0, endOfLastLine - 3) + "..."; |             int end = itemContentView.getText().toString().lastIndexOf(' ', endOfLastLine -2); | ||||||
|  |             if(end == -1) end = Math.max(endOfLastLine -2, 0); | ||||||
|  |             String newVal = itemContentView.getText().subSequence(0, end) + " …"; | ||||||
|             itemContentView.setText(newVal); |             itemContentView.setText(newVal); | ||||||
|             linkify(); |  | ||||||
|         } |         } | ||||||
|  |         linkify(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private void toggleEllipsize() { |     private void toggleEllipsize() { | ||||||
|         if (itemContentView.getText().toString().equals(commentText)) { |         if (itemContentView.getText().toString().equals(commentText)) { | ||||||
|             ellipsize(); |             if (itemContentView.getLineCount() > commentDefaultLines) ellipsize(); | ||||||
|         } else { |         } else { | ||||||
|             expand(); |             expand(); | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -1,11 +1,12 @@ | |||||||
| package org.schabi.newpipe.info_list.holder; | package org.schabi.newpipe.info_list.holder; | ||||||
|  |  | ||||||
| import android.support.v7.widget.RecyclerView; | import androidx.recyclerview.widget.RecyclerView; | ||||||
| import android.view.LayoutInflater; | import android.view.LayoutInflater; | ||||||
| import android.view.ViewGroup; | import android.view.ViewGroup; | ||||||
|  |  | ||||||
| import org.schabi.newpipe.extractor.InfoItem; | import org.schabi.newpipe.extractor.InfoItem; | ||||||
| import org.schabi.newpipe.info_list.InfoItemBuilder; | import org.schabi.newpipe.info_list.InfoItemBuilder; | ||||||
|  | import org.schabi.newpipe.local.history.HistoryRecordManager; | ||||||
|  |  | ||||||
| /* | /* | ||||||
|  * Created by Christian Schabesberger on 12.02.17. |  * Created by Christian Schabesberger on 12.02.17. | ||||||
| @@ -35,5 +36,8 @@ public abstract class InfoItemHolder extends RecyclerView.ViewHolder { | |||||||
|         this.itemBuilder = infoItemBuilder; |         this.itemBuilder = infoItemBuilder; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public abstract void updateFromItem(final InfoItem infoItem); |     public abstract void updateFromItem(final InfoItem infoItem, final HistoryRecordManager historyRecordManager); | ||||||
|  |  | ||||||
|  |     public void updateState(final InfoItem infoItem, final HistoryRecordManager historyRecordManager) { | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -8,6 +8,7 @@ import org.schabi.newpipe.R; | |||||||
| import org.schabi.newpipe.extractor.InfoItem; | import org.schabi.newpipe.extractor.InfoItem; | ||||||
| import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem; | import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem; | ||||||
| import org.schabi.newpipe.info_list.InfoItemBuilder; | import org.schabi.newpipe.info_list.InfoItemBuilder; | ||||||
|  | import org.schabi.newpipe.local.history.HistoryRecordManager; | ||||||
| import org.schabi.newpipe.util.ImageDisplayConstants; | import org.schabi.newpipe.util.ImageDisplayConstants; | ||||||
|  |  | ||||||
| public class PlaylistMiniInfoItemHolder extends InfoItemHolder { | public class PlaylistMiniInfoItemHolder extends InfoItemHolder { | ||||||
| @@ -30,7 +31,7 @@ public class PlaylistMiniInfoItemHolder extends InfoItemHolder { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public void updateFromItem(final InfoItem infoItem) { |     public void updateFromItem(final InfoItem infoItem, final HistoryRecordManager historyRecordManager) { | ||||||
|         if (!(infoItem instanceof PlaylistInfoItem)) return; |         if (!(infoItem instanceof PlaylistInfoItem)) return; | ||||||
|         final PlaylistInfoItem item = (PlaylistInfoItem) infoItem; |         final PlaylistInfoItem item = (PlaylistInfoItem) infoItem; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -8,6 +8,7 @@ import org.schabi.newpipe.R; | |||||||
| import org.schabi.newpipe.extractor.InfoItem; | import org.schabi.newpipe.extractor.InfoItem; | ||||||
| import org.schabi.newpipe.extractor.stream.StreamInfoItem; | import org.schabi.newpipe.extractor.stream.StreamInfoItem; | ||||||
| import org.schabi.newpipe.info_list.InfoItemBuilder; | import org.schabi.newpipe.info_list.InfoItemBuilder; | ||||||
|  | import org.schabi.newpipe.local.history.HistoryRecordManager; | ||||||
| import org.schabi.newpipe.util.Localization; | import org.schabi.newpipe.util.Localization; | ||||||
|  |  | ||||||
| /* | /* | ||||||
| @@ -40,8 +41,8 @@ public class StreamInfoItemHolder extends StreamMiniInfoItemHolder { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public void updateFromItem(final InfoItem infoItem) { |     public void updateFromItem(final InfoItem infoItem, final HistoryRecordManager historyRecordManager) { | ||||||
|         super.updateFromItem(infoItem); |         super.updateFromItem(infoItem, historyRecordManager); | ||||||
|  |  | ||||||
|         if (!(infoItem instanceof StreamInfoItem)) return; |         if (!(infoItem instanceof StreamInfoItem)) return; | ||||||
|         final StreamInfoItem item = (StreamInfoItem) infoItem; |         final StreamInfoItem item = (StreamInfoItem) infoItem; | ||||||
|   | |||||||
| @@ -1,18 +1,24 @@ | |||||||
| package org.schabi.newpipe.info_list.holder; | package org.schabi.newpipe.info_list.holder; | ||||||
|  |  | ||||||
| import android.support.v4.content.ContextCompat; | import androidx.core.content.ContextCompat; | ||||||
| import android.view.View; | import android.view.View; | ||||||
| import android.view.ViewGroup; | import android.view.ViewGroup; | ||||||
| import android.widget.ImageView; | import android.widget.ImageView; | ||||||
| import android.widget.TextView; | import android.widget.TextView; | ||||||
|  |  | ||||||
| import org.schabi.newpipe.R; | import org.schabi.newpipe.R; | ||||||
|  | import org.schabi.newpipe.database.stream.model.StreamStateEntity; | ||||||
| import org.schabi.newpipe.extractor.InfoItem; | import org.schabi.newpipe.extractor.InfoItem; | ||||||
| import org.schabi.newpipe.extractor.stream.StreamInfoItem; | import org.schabi.newpipe.extractor.stream.StreamInfoItem; | ||||||
| import org.schabi.newpipe.extractor.stream.StreamType; | import org.schabi.newpipe.extractor.stream.StreamType; | ||||||
| import org.schabi.newpipe.info_list.InfoItemBuilder; | import org.schabi.newpipe.info_list.InfoItemBuilder; | ||||||
|  | import org.schabi.newpipe.local.history.HistoryRecordManager; | ||||||
|  | import org.schabi.newpipe.util.AnimationUtils; | ||||||
| import org.schabi.newpipe.util.ImageDisplayConstants; | import org.schabi.newpipe.util.ImageDisplayConstants; | ||||||
| import org.schabi.newpipe.util.Localization; | import org.schabi.newpipe.util.Localization; | ||||||
|  | import org.schabi.newpipe.views.AnimatedProgressBar; | ||||||
|  |  | ||||||
|  | import java.util.concurrent.TimeUnit; | ||||||
|  |  | ||||||
| public class StreamMiniInfoItemHolder extends InfoItemHolder { | public class StreamMiniInfoItemHolder extends InfoItemHolder { | ||||||
|  |  | ||||||
| @@ -20,6 +26,7 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder { | |||||||
|     public final TextView itemVideoTitleView; |     public final TextView itemVideoTitleView; | ||||||
|     public final TextView itemUploaderView; |     public final TextView itemUploaderView; | ||||||
|     public final TextView itemDurationView; |     public final TextView itemDurationView; | ||||||
|  |     public final AnimatedProgressBar itemProgressView; | ||||||
|  |  | ||||||
|     StreamMiniInfoItemHolder(InfoItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) { |     StreamMiniInfoItemHolder(InfoItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) { | ||||||
|         super(infoItemBuilder, layoutId, parent); |         super(infoItemBuilder, layoutId, parent); | ||||||
| @@ -28,6 +35,7 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder { | |||||||
|         itemVideoTitleView = itemView.findViewById(R.id.itemVideoTitleView); |         itemVideoTitleView = itemView.findViewById(R.id.itemVideoTitleView); | ||||||
|         itemUploaderView = itemView.findViewById(R.id.itemUploaderView); |         itemUploaderView = itemView.findViewById(R.id.itemUploaderView); | ||||||
|         itemDurationView = itemView.findViewById(R.id.itemDurationView); |         itemDurationView = itemView.findViewById(R.id.itemDurationView); | ||||||
|  |         itemProgressView = itemView.findViewById(R.id.itemProgressView); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public StreamMiniInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) { |     public StreamMiniInfoItemHolder(InfoItemBuilder infoItemBuilder, ViewGroup parent) { | ||||||
| @@ -35,7 +43,7 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public void updateFromItem(final InfoItem infoItem) { |     public void updateFromItem(final InfoItem infoItem, final HistoryRecordManager historyRecordManager) { | ||||||
|         if (!(infoItem instanceof StreamInfoItem)) return; |         if (!(infoItem instanceof StreamInfoItem)) return; | ||||||
|         final StreamInfoItem item = (StreamInfoItem) infoItem; |         final StreamInfoItem item = (StreamInfoItem) infoItem; | ||||||
|  |  | ||||||
| @@ -47,13 +55,24 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder { | |||||||
|             itemDurationView.setBackgroundColor(ContextCompat.getColor(itemBuilder.getContext(), |             itemDurationView.setBackgroundColor(ContextCompat.getColor(itemBuilder.getContext(), | ||||||
|                     R.color.duration_background_color)); |                     R.color.duration_background_color)); | ||||||
|             itemDurationView.setVisibility(View.VISIBLE); |             itemDurationView.setVisibility(View.VISIBLE); | ||||||
|  |  | ||||||
|  |             StreamStateEntity state2 = historyRecordManager.loadStreamState(infoItem).blockingGet()[0]; | ||||||
|  |             if (state2 != null) { | ||||||
|  |                 itemProgressView.setVisibility(View.VISIBLE); | ||||||
|  |                 itemProgressView.setMax((int) item.getDuration()); | ||||||
|  |                 itemProgressView.setProgress((int) TimeUnit.MILLISECONDS.toSeconds(state2.getProgressTime())); | ||||||
|  |             } else { | ||||||
|  |                 itemProgressView.setVisibility(View.GONE); | ||||||
|  |             } | ||||||
|         } else if (item.getStreamType() == StreamType.LIVE_STREAM) { |         } else if (item.getStreamType() == StreamType.LIVE_STREAM) { | ||||||
|             itemDurationView.setText(R.string.duration_live); |             itemDurationView.setText(R.string.duration_live); | ||||||
|             itemDurationView.setBackgroundColor(ContextCompat.getColor(itemBuilder.getContext(), |             itemDurationView.setBackgroundColor(ContextCompat.getColor(itemBuilder.getContext(), | ||||||
|                     R.color.live_duration_background_color)); |                     R.color.live_duration_background_color)); | ||||||
|             itemDurationView.setVisibility(View.VISIBLE); |             itemDurationView.setVisibility(View.VISIBLE); | ||||||
|  |             itemProgressView.setVisibility(View.GONE); | ||||||
|         } else { |         } else { | ||||||
|             itemDurationView.setVisibility(View.GONE); |             itemDurationView.setVisibility(View.GONE); | ||||||
|  |             itemProgressView.setVisibility(View.GONE); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // Default thumbnail is shown on error, while loading and if the url is empty |         // Default thumbnail is shown on error, while loading and if the url is empty | ||||||
| @@ -83,6 +102,24 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void updateState(final InfoItem infoItem, final HistoryRecordManager historyRecordManager) { | ||||||
|  |         final StreamInfoItem item = (StreamInfoItem) infoItem; | ||||||
|  |  | ||||||
|  |         StreamStateEntity state = historyRecordManager.loadStreamState(infoItem).blockingGet()[0]; | ||||||
|  |         if (state != null && item.getDuration() > 0 && item.getStreamType() != StreamType.LIVE_STREAM) { | ||||||
|  |             itemProgressView.setMax((int) item.getDuration()); | ||||||
|  |             if (itemProgressView.getVisibility() == View.VISIBLE) { | ||||||
|  |                 itemProgressView.setProgressAnimated((int) TimeUnit.MILLISECONDS.toSeconds(state.getProgressTime())); | ||||||
|  |             } else { | ||||||
|  |                 itemProgressView.setProgress((int) TimeUnit.MILLISECONDS.toSeconds(state.getProgressTime())); | ||||||
|  |                 AnimationUtils.animateView(itemProgressView, true, 500); | ||||||
|  |             } | ||||||
|  |         } else if (itemProgressView.getVisibility() == View.VISIBLE) { | ||||||
|  |             AnimationUtils.animateView(itemProgressView, false, 500); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     private void enableLongClick(final StreamInfoItem item) { |     private void enableLongClick(final StreamInfoItem item) { | ||||||
|         itemView.setLongClickable(true); |         itemView.setLongClickable(true); | ||||||
|         itemView.setOnLongClickListener(view -> { |         itemView.setOnLongClickListener(view -> { | ||||||
|   | |||||||
| @@ -5,11 +5,11 @@ import android.content.res.Configuration; | |||||||
| import android.content.res.Resources; | import android.content.res.Resources; | ||||||
| import android.os.Bundle; | import android.os.Bundle; | ||||||
| import android.preference.PreferenceManager; | import android.preference.PreferenceManager; | ||||||
| import android.support.v4.app.Fragment; | import androidx.fragment.app.Fragment; | ||||||
| import android.support.v7.app.ActionBar; | import androidx.appcompat.app.ActionBar; | ||||||
| import android.support.v7.widget.GridLayoutManager; | import androidx.recyclerview.widget.GridLayoutManager; | ||||||
| import android.support.v7.widget.LinearLayoutManager; | import androidx.recyclerview.widget.LinearLayoutManager; | ||||||
| import android.support.v7.widget.RecyclerView; | import androidx.recyclerview.widget.RecyclerView; | ||||||
| import android.util.Log; | import android.util.Log; | ||||||
| import android.view.Menu; | import android.view.Menu; | ||||||
| import android.view.MenuInflater; | import android.view.MenuInflater; | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| package org.schabi.newpipe.local; | package org.schabi.newpipe.local; | ||||||
|  |  | ||||||
| import android.support.v7.widget.RecyclerView; | import androidx.recyclerview.widget.RecyclerView; | ||||||
| import android.view.View; | import android.view.View; | ||||||
|  |  | ||||||
| public class HeaderFooterHolder extends RecyclerView.ViewHolder { | public class HeaderFooterHolder extends RecyclerView.ViewHolder { | ||||||
|   | |||||||
| @@ -1,13 +1,17 @@ | |||||||
| package org.schabi.newpipe.local; | package org.schabi.newpipe.local; | ||||||
|  |  | ||||||
| import android.app.Activity; | import android.content.Context; | ||||||
| import android.support.v7.widget.GridLayoutManager; | import androidx.annotation.NonNull; | ||||||
| import android.support.v7.widget.RecyclerView; | import androidx.annotation.Nullable; | ||||||
|  | import androidx.recyclerview.widget.GridLayoutManager; | ||||||
|  | import androidx.recyclerview.widget.RecyclerView; | ||||||
| import android.util.Log; | import android.util.Log; | ||||||
| import android.view.View; | import android.view.View; | ||||||
| import android.view.ViewGroup; | import android.view.ViewGroup; | ||||||
|  |  | ||||||
| import org.schabi.newpipe.database.LocalItem; | import org.schabi.newpipe.database.LocalItem; | ||||||
|  | import org.schabi.newpipe.database.stream.model.StreamStateEntity; | ||||||
|  | import org.schabi.newpipe.local.history.HistoryRecordManager; | ||||||
| import org.schabi.newpipe.local.holder.LocalItemHolder; | import org.schabi.newpipe.local.holder.LocalItemHolder; | ||||||
| import org.schabi.newpipe.local.holder.LocalPlaylistGridItemHolder; | import org.schabi.newpipe.local.holder.LocalPlaylistGridItemHolder; | ||||||
| import org.schabi.newpipe.local.holder.LocalPlaylistItemHolder; | import org.schabi.newpipe.local.holder.LocalPlaylistItemHolder; | ||||||
| @@ -64,6 +68,7 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View | |||||||
|  |  | ||||||
|     private final LocalItemBuilder localItemBuilder; |     private final LocalItemBuilder localItemBuilder; | ||||||
|     private final ArrayList<LocalItem> localItems; |     private final ArrayList<LocalItem> localItems; | ||||||
|  |     private final HistoryRecordManager recordManager; | ||||||
|     private final DateFormat dateFormat; |     private final DateFormat dateFormat; | ||||||
|  |  | ||||||
|     private boolean showFooter = false; |     private boolean showFooter = false; | ||||||
| @@ -71,11 +76,12 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View | |||||||
|     private View header = null; |     private View header = null; | ||||||
|     private View footer = null; |     private View footer = null; | ||||||
|  |  | ||||||
|     public LocalItemListAdapter(Activity activity) { |     public LocalItemListAdapter(Context context) { | ||||||
|         localItemBuilder = new LocalItemBuilder(activity); |         recordManager = new HistoryRecordManager(context); | ||||||
|  |         localItemBuilder = new LocalItemBuilder(context); | ||||||
|         localItems = new ArrayList<>(); |         localItems = new ArrayList<>(); | ||||||
|         dateFormat = DateFormat.getDateInstance(DateFormat.SHORT, |         dateFormat = DateFormat.getDateInstance(DateFormat.SHORT, | ||||||
|                 Localization.getPreferredLocale(activity)); |                 Localization.getPreferredLocale(context)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public void setSelectedListener(OnClickGesture<LocalItem> listener) { |     public void setSelectedListener(OnClickGesture<LocalItem> listener) { | ||||||
| @@ -86,23 +92,20 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View | |||||||
|         localItemBuilder.setOnItemSelectedListener(null); |         localItemBuilder.setOnItemSelectedListener(null); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public void addItems(List<? extends LocalItem> data) { |     public void addItems(@Nullable List<? extends LocalItem> data) { | ||||||
|         if (data != null) { |         if (data == null) { | ||||||
|             if (DEBUG) { |             return; | ||||||
|                 Log.d(TAG, "addItems() before > localItems.size() = " + |  | ||||||
|                         localItems.size() + ", data.size() = " + data.size()); |  | ||||||
|         } |         } | ||||||
|  |         if (DEBUG) Log.d(TAG, "addItems() before > localItems.size() = " + | ||||||
|  |                 localItems.size() + ", data.size() = " + data.size()); | ||||||
|  |  | ||||||
|         int offsetStart = sizeConsideringHeader(); |         int offsetStart = sizeConsideringHeader(); | ||||||
|         localItems.addAll(data); |         localItems.addAll(data); | ||||||
|  |  | ||||||
|             if (DEBUG) { |         if (DEBUG) Log.d(TAG, "addItems() after > offsetStart = " + offsetStart + | ||||||
|                 Log.d(TAG, "addItems() after > offsetStart = " + offsetStart + |  | ||||||
|                 ", localItems.size() = " + localItems.size() + |                 ", localItems.size() = " + localItems.size() + | ||||||
|                 ", header = " + header + ", footer = " + footer + |                 ", header = " + header + ", footer = " + footer + | ||||||
|                 ", showFooter = " + showFooter); |                 ", showFooter = " + showFooter); | ||||||
|             } |  | ||||||
|  |  | ||||||
|         notifyItemRangeInserted(offsetStart, data.size()); |         notifyItemRangeInserted(offsetStart, data.size()); | ||||||
|  |  | ||||||
|         if (footer != null && showFooter) { |         if (footer != null && showFooter) { | ||||||
| @@ -113,11 +116,9 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View | |||||||
|                     " to " + footerNow); |                     " to " + footerNow); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public void removeItem(final LocalItem data) { |     public void removeItem(final LocalItem data) { | ||||||
|         final int index = localItems.indexOf(data); |         final int index = localItems.indexOf(data); | ||||||
|  |  | ||||||
|         localItems.remove(index); |         localItems.remove(index); | ||||||
|         notifyItemRemoved(index + (header != null ? 1 : 0)); |         notifyItemRemoved(index + (header != null ? 1 : 0)); | ||||||
|     } |     } | ||||||
| @@ -219,8 +220,9 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @NonNull | ||||||
|     @Override |     @Override | ||||||
|     public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int type) { |     public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int type) { | ||||||
|         if (DEBUG) Log.d(TAG, "onCreateViewHolder() called with: parent = [" + |         if (DEBUG) Log.d(TAG, "onCreateViewHolder() called with: parent = [" + | ||||||
|                 parent + "], type = [" + type + "]"); |                 parent + "], type = [" + type + "]"); | ||||||
|         switch (type) { |         switch (type) { | ||||||
| @@ -251,7 +253,7 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { |     public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { | ||||||
|         if (DEBUG) Log.d(TAG, "onBindViewHolder() called with: holder = [" + |         if (DEBUG) Log.d(TAG, "onBindViewHolder() called with: holder = [" + | ||||||
|                 holder.getClass().getSimpleName() + "], position = [" + position + "]"); |                 holder.getClass().getSimpleName() + "], position = [" + position + "]"); | ||||||
|  |  | ||||||
| @@ -259,7 +261,7 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View | |||||||
|             // If header isn't null, offset the items by -1 |             // If header isn't null, offset the items by -1 | ||||||
|             if (header != null) position--; |             if (header != null) position--; | ||||||
|  |  | ||||||
|             ((LocalItemHolder) holder).updateFromItem(localItems.get(position), dateFormat); |             ((LocalItemHolder) holder).updateFromItem(localItems.get(position), recordManager, dateFormat); | ||||||
|         } else if (holder instanceof HeaderFooterHolder && position == 0 && header != null) { |         } else if (holder instanceof HeaderFooterHolder && position == 0 && header != null) { | ||||||
|             ((HeaderFooterHolder) holder).view = header; |             ((HeaderFooterHolder) holder).view = header; | ||||||
|         } else if (holder instanceof HeaderFooterHolder && position == sizeConsideringHeader() |         } else if (holder instanceof HeaderFooterHolder && position == sizeConsideringHeader() | ||||||
| @@ -268,6 +270,21 @@ public class LocalItemListAdapter extends RecyclerView.Adapter<RecyclerView.View | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position, @NonNull List<Object> payloads) { | ||||||
|  |         if (!payloads.isEmpty() && holder instanceof LocalItemHolder) { | ||||||
|  |             for (Object payload : payloads) { | ||||||
|  |                 if (payload instanceof StreamStateEntity) { | ||||||
|  |                     ((LocalItemHolder) holder).updateState(localItems.get(header == null ? position : position - 1), recordManager); | ||||||
|  |                 } else if (payload instanceof Boolean) { | ||||||
|  |                     ((LocalItemHolder) holder).updateState(localItems.get(header == null ? position : position - 1), recordManager); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } else { | ||||||
|  |             onBindViewHolder(holder, position); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     public GridLayoutManager.SpanSizeLookup getSpanSizeLookup(final int spanCount) { |     public GridLayoutManager.SpanSizeLookup getSpanSizeLookup(final int spanCount) { | ||||||
|         return new GridLayoutManager.SpanSizeLookup() { |         return new GridLayoutManager.SpanSizeLookup() { | ||||||
|             @Override |             @Override | ||||||
|   | |||||||
| @@ -3,9 +3,9 @@ package org.schabi.newpipe.local.bookmark; | |||||||
| import android.app.AlertDialog; | import android.app.AlertDialog; | ||||||
| import android.os.Bundle; | import android.os.Bundle; | ||||||
| import android.os.Parcelable; | import android.os.Parcelable; | ||||||
| import android.support.annotation.NonNull; | import androidx.annotation.NonNull; | ||||||
| import android.support.annotation.Nullable; | import androidx.annotation.Nullable; | ||||||
| import android.support.v4.app.FragmentManager; | import androidx.fragment.app.FragmentManager; | ||||||
| import android.view.LayoutInflater; | import android.view.LayoutInflater; | ||||||
| import android.view.View; | import android.view.View; | ||||||
| import android.view.ViewGroup; | import android.view.ViewGroup; | ||||||
|   | |||||||
| @@ -1,11 +1,10 @@ | |||||||
| package org.schabi.newpipe.local.dialog; | package org.schabi.newpipe.local.dialog; | ||||||
|  |  | ||||||
| import android.annotation.SuppressLint; |  | ||||||
| import android.os.Bundle; | import android.os.Bundle; | ||||||
| import android.support.annotation.NonNull; | import androidx.annotation.NonNull; | ||||||
| import android.support.annotation.Nullable; | import androidx.annotation.Nullable; | ||||||
| import android.support.v7.widget.LinearLayoutManager; | import androidx.recyclerview.widget.LinearLayoutManager; | ||||||
| import android.support.v7.widget.RecyclerView; | import androidx.recyclerview.widget.RecyclerView; | ||||||
| import android.view.LayoutInflater; | import android.view.LayoutInflater; | ||||||
| import android.view.View; | import android.view.View; | ||||||
| import android.view.ViewGroup; | import android.view.ViewGroup; | ||||||
| @@ -28,7 +27,7 @@ import java.util.Collections; | |||||||
| import java.util.List; | import java.util.List; | ||||||
|  |  | ||||||
| import io.reactivex.android.schedulers.AndroidSchedulers; | import io.reactivex.android.schedulers.AndroidSchedulers; | ||||||
| import io.reactivex.disposables.Disposable; | import io.reactivex.disposables.CompositeDisposable; | ||||||
|  |  | ||||||
| public final class PlaylistAppendDialog extends PlaylistDialog { | public final class PlaylistAppendDialog extends PlaylistDialog { | ||||||
|     private static final String TAG = PlaylistAppendDialog.class.getCanonicalName(); |     private static final String TAG = PlaylistAppendDialog.class.getCanonicalName(); | ||||||
| @@ -36,7 +35,7 @@ public final class PlaylistAppendDialog extends PlaylistDialog { | |||||||
|     private RecyclerView playlistRecyclerView; |     private RecyclerView playlistRecyclerView; | ||||||
|     private LocalItemListAdapter playlistAdapter; |     private LocalItemListAdapter playlistAdapter; | ||||||
|  |  | ||||||
|     private Disposable playlistReactor; |     private CompositeDisposable playlistDisposables = new CompositeDisposable(); | ||||||
|  |  | ||||||
|     public static PlaylistAppendDialog fromStreamInfo(final StreamInfo info) { |     public static PlaylistAppendDialog fromStreamInfo(final StreamInfo info) { | ||||||
|         PlaylistAppendDialog dialog = new PlaylistAppendDialog(); |         PlaylistAppendDialog dialog = new PlaylistAppendDialog(); | ||||||
| @@ -99,9 +98,9 @@ public final class PlaylistAppendDialog extends PlaylistDialog { | |||||||
|         final View newPlaylistButton = view.findViewById(R.id.newPlaylist); |         final View newPlaylistButton = view.findViewById(R.id.newPlaylist); | ||||||
|         newPlaylistButton.setOnClickListener(ignored -> openCreatePlaylistDialog()); |         newPlaylistButton.setOnClickListener(ignored -> openCreatePlaylistDialog()); | ||||||
|  |  | ||||||
|         playlistReactor = playlistManager.getPlaylists() |         playlistDisposables.add(playlistManager.getPlaylists() | ||||||
|                 .observeOn(AndroidSchedulers.mainThread()) |                 .observeOn(AndroidSchedulers.mainThread()) | ||||||
|                 .subscribe(this::onPlaylistsReceived); |                 .subscribe(this::onPlaylistsReceived)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /*////////////////////////////////////////////////////////////////////////// |     /*////////////////////////////////////////////////////////////////////////// | ||||||
| @@ -111,10 +110,12 @@ public final class PlaylistAppendDialog extends PlaylistDialog { | |||||||
|     @Override |     @Override | ||||||
|     public void onDestroyView() { |     public void onDestroyView() { | ||||||
|         super.onDestroyView(); |         super.onDestroyView(); | ||||||
|         if (playlistReactor != null) playlistReactor.dispose(); |         playlistDisposables.dispose(); | ||||||
|         if (playlistAdapter != null) playlistAdapter.unsetSelectedListener(); |         if (playlistAdapter != null) { | ||||||
|  |             playlistAdapter.unsetSelectedListener(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         playlistReactor = null; |         playlistDisposables.clear(); | ||||||
|         playlistRecyclerView = null; |         playlistRecyclerView = null; | ||||||
|         playlistAdapter = null; |         playlistAdapter = null; | ||||||
|     } |     } | ||||||
| @@ -148,13 +149,12 @@ public final class PlaylistAppendDialog extends PlaylistDialog { | |||||||
|                                     @NonNull List<StreamEntity> streams) { |                                     @NonNull List<StreamEntity> streams) { | ||||||
|         if (getStreams() == null) return; |         if (getStreams() == null) return; | ||||||
|  |  | ||||||
|         @SuppressLint("ShowToast") |  | ||||||
|         final Toast successToast = Toast.makeText(getContext(), |         final Toast successToast = Toast.makeText(getContext(), | ||||||
|                 R.string.playlist_add_stream_success, Toast.LENGTH_SHORT); |                 R.string.playlist_add_stream_success, Toast.LENGTH_SHORT); | ||||||
|  |  | ||||||
|         manager.appendToPlaylist(playlist.uid, streams) |         playlistDisposables.add(manager.appendToPlaylist(playlist.uid, streams) | ||||||
|                 .observeOn(AndroidSchedulers.mainThread()) |                 .observeOn(AndroidSchedulers.mainThread()) | ||||||
|                 .subscribe(ignored -> successToast.show()); |                 .subscribe(ignored -> successToast.show())); | ||||||
|  |  | ||||||
|         getDialog().dismiss(); |         getDialog().dismiss(); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -3,8 +3,8 @@ package org.schabi.newpipe.local.dialog; | |||||||
| import android.app.AlertDialog; | import android.app.AlertDialog; | ||||||
| import android.app.Dialog; | import android.app.Dialog; | ||||||
| import android.os.Bundle; | import android.os.Bundle; | ||||||
| import android.support.annotation.NonNull; | import androidx.annotation.NonNull; | ||||||
| import android.support.annotation.Nullable; | import androidx.annotation.Nullable; | ||||||
| import android.view.View; | import android.view.View; | ||||||
| import android.widget.EditText; | import android.widget.EditText; | ||||||
| import android.widget.Toast; | import android.widget.Toast; | ||||||
|   | |||||||
| @@ -2,9 +2,9 @@ package org.schabi.newpipe.local.dialog; | |||||||
|  |  | ||||||
| import android.app.Dialog; | import android.app.Dialog; | ||||||
| import android.os.Bundle; | import android.os.Bundle; | ||||||
| import android.support.annotation.NonNull; | import androidx.annotation.NonNull; | ||||||
| import android.support.annotation.Nullable; | import androidx.annotation.Nullable; | ||||||
| import android.support.v4.app.DialogFragment; | import androidx.fragment.app.DialogFragment; | ||||||
| import android.view.Window; | import android.view.Window; | ||||||
|  |  | ||||||
| import org.schabi.newpipe.database.stream.model.StreamEntity; | import org.schabi.newpipe.database.stream.model.StreamEntity; | ||||||
|   | |||||||
| @@ -2,9 +2,9 @@ package org.schabi.newpipe.local.feed; | |||||||
|  |  | ||||||
| import android.os.Bundle; | import android.os.Bundle; | ||||||
| import android.os.Handler; | import android.os.Handler; | ||||||
| import android.support.annotation.NonNull; | import androidx.annotation.NonNull; | ||||||
| import android.support.annotation.Nullable; | import androidx.annotation.Nullable; | ||||||
| import android.support.v7.app.ActionBar; | import androidx.appcompat.app.ActionBar; | ||||||
| import android.util.Log; | import android.util.Log; | ||||||
| import android.view.LayoutInflater; | import android.view.LayoutInflater; | ||||||
| import android.view.Menu; | import android.view.Menu; | ||||||
| @@ -21,8 +21,8 @@ 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.exceptions.ExtractionException; | import org.schabi.newpipe.extractor.exceptions.ExtractionException; | ||||||
| import org.schabi.newpipe.fragments.list.BaseListFragment; | import org.schabi.newpipe.fragments.list.BaseListFragment; | ||||||
| import org.schabi.newpipe.report.UserAction; |  | ||||||
| import org.schabi.newpipe.local.subscription.SubscriptionService; | import org.schabi.newpipe.local.subscription.SubscriptionService; | ||||||
|  | import org.schabi.newpipe.report.UserAction; | ||||||
|  |  | ||||||
| import java.util.Collections; | import java.util.Collections; | ||||||
| import java.util.HashSet; | import java.util.HashSet; | ||||||
| @@ -183,7 +183,7 @@ public class FeedFragment extends BaseListFragment<List<SubscriptionEntity>, Voi | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public void handleResult(@android.support.annotation.NonNull List<SubscriptionEntity> result) { |     public void handleResult(@androidx.annotation.NonNull List<SubscriptionEntity> result) { | ||||||
|         super.handleResult(result); |         super.handleResult(result); | ||||||
|  |  | ||||||
|         if (result.isEmpty()) { |         if (result.isEmpty()) { | ||||||
| @@ -262,7 +262,7 @@ public class FeedFragment extends BaseListFragment<List<SubscriptionEntity>, Voi | |||||||
|      * If chosen feed already displayed, then we request another feed from another |      * If chosen feed already displayed, then we request another feed from another | ||||||
|      * subscription, until the subscription table runs out of new items. |      * subscription, until the subscription table runs out of new items. | ||||||
|      * <p> |      * <p> | ||||||
|      * This Observer is self-contained and will dispose itself when complete. However, this |      * This Observer is self-contained and will close itself when complete. However, this | ||||||
|      * does not obey the fragment lifecycle and may continue running in the background |      * does not obey the fragment lifecycle and may continue running in the background | ||||||
|      * until it is complete. This is done due to RxJava2 no longer propagate errors once |      * until it is complete. This is done due to RxJava2 no longer propagate errors once | ||||||
|      * an observer is unsubscribed while the thread process is still running. |      * an observer is unsubscribed while the thread process is still running. | ||||||
|   | |||||||
| @@ -1,9 +1,9 @@ | |||||||
| package org.schabi.newpipe.local.history; | package org.schabi.newpipe.local.history; | ||||||
|  |  | ||||||
| import android.content.Context; | import android.content.Context; | ||||||
| import android.support.annotation.NonNull; | import androidx.annotation.NonNull; | ||||||
| import android.support.annotation.Nullable; | import androidx.annotation.Nullable; | ||||||
| import android.support.v7.widget.RecyclerView; | import androidx.recyclerview.widget.RecyclerView; | ||||||
|  |  | ||||||
| import org.schabi.newpipe.util.Localization; | import org.schabi.newpipe.util.Localization; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| package org.schabi.newpipe.local.history; | package org.schabi.newpipe.local.history; | ||||||
|  |  | ||||||
| import android.support.annotation.Nullable; | import androidx.annotation.Nullable; | ||||||
|  |  | ||||||
| import org.schabi.newpipe.extractor.stream.AudioStream; | import org.schabi.newpipe.extractor.stream.AudioStream; | ||||||
| import org.schabi.newpipe.extractor.stream.StreamInfo; | import org.schabi.newpipe.extractor.stream.StreamInfo; | ||||||
|   | |||||||
| @@ -21,28 +21,34 @@ package org.schabi.newpipe.local.history; | |||||||
| import android.content.Context; | import android.content.Context; | ||||||
| import android.content.SharedPreferences; | import android.content.SharedPreferences; | ||||||
| import android.preference.PreferenceManager; | import android.preference.PreferenceManager; | ||||||
| import android.support.annotation.NonNull; | import androidx.annotation.NonNull; | ||||||
|  |  | ||||||
| import org.schabi.newpipe.NewPipeDatabase; | import org.schabi.newpipe.NewPipeDatabase; | ||||||
| import org.schabi.newpipe.R; | import org.schabi.newpipe.R; | ||||||
| import org.schabi.newpipe.database.AppDatabase; | import org.schabi.newpipe.database.AppDatabase; | ||||||
|  | import org.schabi.newpipe.database.LocalItem; | ||||||
| import org.schabi.newpipe.database.history.dao.SearchHistoryDAO; | import org.schabi.newpipe.database.history.dao.SearchHistoryDAO; | ||||||
| import org.schabi.newpipe.database.history.dao.StreamHistoryDAO; | import org.schabi.newpipe.database.history.dao.StreamHistoryDAO; | ||||||
| import org.schabi.newpipe.database.history.model.SearchHistoryEntry; | import org.schabi.newpipe.database.history.model.SearchHistoryEntry; | ||||||
| import org.schabi.newpipe.database.history.model.StreamHistoryEntity; | import org.schabi.newpipe.database.history.model.StreamHistoryEntity; | ||||||
| import org.schabi.newpipe.database.history.model.StreamHistoryEntry; | import org.schabi.newpipe.database.history.model.StreamHistoryEntry; | ||||||
|  | import org.schabi.newpipe.database.playlist.PlaylistStreamEntry; | ||||||
|  | import org.schabi.newpipe.database.playlist.model.PlaylistStreamEntity; | ||||||
| import org.schabi.newpipe.database.stream.StreamStatisticsEntry; | import org.schabi.newpipe.database.stream.StreamStatisticsEntry; | ||||||
| import org.schabi.newpipe.database.stream.dao.StreamDAO; | import org.schabi.newpipe.database.stream.dao.StreamDAO; | ||||||
| import org.schabi.newpipe.database.stream.dao.StreamStateDAO; | import org.schabi.newpipe.database.stream.dao.StreamStateDAO; | ||||||
| import org.schabi.newpipe.database.stream.model.StreamEntity; | import org.schabi.newpipe.database.stream.model.StreamEntity; | ||||||
| import org.schabi.newpipe.database.stream.model.StreamStateEntity; | import org.schabi.newpipe.database.stream.model.StreamStateEntity; | ||||||
|  | import org.schabi.newpipe.extractor.InfoItem; | ||||||
| import org.schabi.newpipe.extractor.stream.StreamInfo; | import org.schabi.newpipe.extractor.stream.StreamInfo; | ||||||
|  | import org.schabi.newpipe.player.playqueue.PlayQueueItem; | ||||||
|  |  | ||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
| import java.util.Collection; | import java.util.Collection; | ||||||
| import java.util.Date; | import java.util.Date; | ||||||
| import java.util.List; | import java.util.List; | ||||||
|  |  | ||||||
|  | import io.reactivex.Completable; | ||||||
| import io.reactivex.Flowable; | import io.reactivex.Flowable; | ||||||
| import io.reactivex.Maybe; | import io.reactivex.Maybe; | ||||||
| import io.reactivex.Single; | import io.reactivex.Single; | ||||||
| @@ -80,9 +86,9 @@ public class HistoryRecordManager { | |||||||
|         final Date currentTime = new Date(); |         final Date currentTime = new Date(); | ||||||
|         return Maybe.fromCallable(() -> database.runInTransaction(() -> { |         return Maybe.fromCallable(() -> database.runInTransaction(() -> { | ||||||
|             final long streamId = streamTable.upsert(new StreamEntity(info)); |             final long streamId = streamTable.upsert(new StreamEntity(info)); | ||||||
|             StreamHistoryEntity latestEntry = streamHistoryTable.getLatestEntry(); |             StreamHistoryEntity latestEntry = streamHistoryTable.getLatestEntry(streamId); | ||||||
|  |  | ||||||
|             if (latestEntry != null && latestEntry.getStreamUid() == streamId) { |             if (latestEntry != null) { | ||||||
|                 streamHistoryTable.delete(latestEntry); |                 streamHistoryTable.delete(latestEntry); | ||||||
|                 latestEntry.setAccessDate(currentTime); |                 latestEntry.setAccessDate(currentTime); | ||||||
|                 latestEntry.setRepeatCount(latestEntry.getRepeatCount() + 1); |                 latestEntry.setRepeatCount(latestEntry.getRepeatCount() + 1); | ||||||
| @@ -99,7 +105,12 @@ public class HistoryRecordManager { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     public Single<Integer> deleteWholeStreamHistory() { |     public Single<Integer> deleteWholeStreamHistory() { | ||||||
|         return Single.fromCallable(() -> streamHistoryTable.deleteAll()) |         return Single.fromCallable(streamHistoryTable::deleteAll) | ||||||
|  |                 .subscribeOn(Schedulers.io()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public Single<Integer> deleteCompelteStreamStateHistory() { | ||||||
|  |         return Single.fromCallable(streamStateTable::deleteAll) | ||||||
|                 .subscribeOn(Schedulers.io()); |                 .subscribeOn(Schedulers.io()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -159,8 +170,8 @@ public class HistoryRecordManager { | |||||||
|                 .subscribeOn(Schedulers.io()); |                 .subscribeOn(Schedulers.io()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public Single<Integer> deleteWholeSearchHistory() { |     public Single<Integer> deleteCompleteSearchHistory() { | ||||||
|         return Single.fromCallable(() -> searchHistoryTable.deleteAll()) |         return Single.fromCallable(searchHistoryTable::deleteAll) | ||||||
|                 .subscribeOn(Schedulers.io()); |                 .subscribeOn(Schedulers.io()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -180,21 +191,104 @@ public class HistoryRecordManager { | |||||||
|     // Stream State History |     // Stream State History | ||||||
|     /////////////////////////////////////////////////////// |     /////////////////////////////////////////////////////// | ||||||
|  |  | ||||||
|     @SuppressWarnings("unused") |     public Maybe<StreamHistoryEntity> getStreamHistory(final StreamInfo info) { | ||||||
|     public Maybe<StreamStateEntity> loadStreamState(final StreamInfo info) { |         return Maybe.fromCallable(() -> { | ||||||
|         return Maybe.fromCallable(() -> streamTable.upsert(new StreamEntity(info))) |             final long streamId = streamTable.upsert(new StreamEntity(info)); | ||||||
|                 .flatMap(streamId -> streamStateTable.getState(streamId).firstElement()) |             return streamHistoryTable.getLatestEntry(streamId); | ||||||
|                 .flatMap(states -> states.isEmpty() ? Maybe.empty() : Maybe.just(states.get(0))) |         }).subscribeOn(Schedulers.io()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public Maybe<StreamStateEntity> loadStreamState(final PlayQueueItem queueItem) { | ||||||
|  |         return queueItem.getStream() | ||||||
|  |                 .map((info) -> streamTable.upsert(new StreamEntity(info))) | ||||||
|  |                 .flatMapPublisher(streamStateTable::getState) | ||||||
|  |                 .firstElement() | ||||||
|  |                 .flatMap(list -> list.isEmpty() ? Maybe.empty() : Maybe.just(list.get(0))) | ||||||
|  |                 .filter(state -> state.isValid((int) queueItem.getDuration())) | ||||||
|                 .subscribeOn(Schedulers.io()); |                 .subscribeOn(Schedulers.io()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public Maybe<Long> saveStreamState(@NonNull final StreamInfo info, final long progressTime) { |     public Maybe<StreamStateEntity> loadStreamState(final StreamInfo info) { | ||||||
|         return Maybe.fromCallable(() -> database.runInTransaction(() -> { |         return Single.fromCallable(() -> streamTable.upsert(new StreamEntity(info))) | ||||||
|  |                 .flatMapPublisher(streamStateTable::getState) | ||||||
|  |                 .firstElement() | ||||||
|  |                 .flatMap(list -> list.isEmpty() ? Maybe.empty() : Maybe.just(list.get(0))) | ||||||
|  |                 .filter(state -> state.isValid((int) info.getDuration())) | ||||||
|  |                 .subscribeOn(Schedulers.io()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public Completable saveStreamState(@NonNull final StreamInfo info, final long progressTime) { | ||||||
|  |         return Completable.fromAction(() -> database.runInTransaction(() -> { | ||||||
|             final long streamId = streamTable.upsert(new StreamEntity(info)); |             final long streamId = streamTable.upsert(new StreamEntity(info)); | ||||||
|             return streamStateTable.upsert(new StreamStateEntity(streamId, progressTime)); |             final StreamStateEntity state = new StreamStateEntity(streamId, progressTime); | ||||||
|  |             if (state.isValid((int) info.getDuration())) { | ||||||
|  |                 streamStateTable.upsert(state); | ||||||
|  |             } else { | ||||||
|  |                 streamStateTable.deleteState(streamId); | ||||||
|  |             } | ||||||
|         })).subscribeOn(Schedulers.io()); |         })).subscribeOn(Schedulers.io()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     public Single<StreamStateEntity[]> loadStreamState(final InfoItem info) { | ||||||
|  |         return Single.fromCallable(() -> { | ||||||
|  |             final List<StreamEntity> entities = streamTable.getStream(info.getServiceId(), info.getUrl()).blockingFirst(); | ||||||
|  |             if (entities.isEmpty()) { | ||||||
|  |                 return new StreamStateEntity[]{null}; | ||||||
|  |             } | ||||||
|  |             final List<StreamStateEntity> states = streamStateTable.getState(entities.get(0).getUid()).blockingFirst(); | ||||||
|  |             if (states.isEmpty()) { | ||||||
|  |                 return new StreamStateEntity[]{null}; | ||||||
|  |             } | ||||||
|  |             return new StreamStateEntity[]{states.get(0)}; | ||||||
|  |         }).subscribeOn(Schedulers.io()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public Single<List<StreamStateEntity>> loadStreamStateBatch(final List<InfoItem> infos) { | ||||||
|  |         return Single.fromCallable(() -> { | ||||||
|  |             final List<StreamStateEntity> result = new ArrayList<>(infos.size()); | ||||||
|  |             for (InfoItem info : infos) { | ||||||
|  |                 final List<StreamEntity> entities = streamTable.getStream(info.getServiceId(), info.getUrl()).blockingFirst(); | ||||||
|  |                 if (entities.isEmpty()) { | ||||||
|  |                     result.add(null); | ||||||
|  |                     continue; | ||||||
|  |                 } | ||||||
|  |                 final List<StreamStateEntity> states = streamStateTable.getState(entities.get(0).getUid()).blockingFirst(); | ||||||
|  |                 if (states.isEmpty()) { | ||||||
|  |                     result.add(null); | ||||||
|  |                     continue; | ||||||
|  |                 } | ||||||
|  |                 result.add(states.get(0)); | ||||||
|  |             } | ||||||
|  |             return result; | ||||||
|  |         }).subscribeOn(Schedulers.io()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public Single<List<StreamStateEntity>> loadLocalStreamStateBatch(final List<? extends LocalItem> items) { | ||||||
|  |         return Single.fromCallable(() -> { | ||||||
|  |             final List<StreamStateEntity> result = new ArrayList<>(items.size()); | ||||||
|  |             for (LocalItem item : items) { | ||||||
|  |                 long streamId; | ||||||
|  |                 if (item instanceof StreamStatisticsEntry) { | ||||||
|  |                     streamId = ((StreamStatisticsEntry) item).streamId; | ||||||
|  |                 } else if (item instanceof PlaylistStreamEntity) { | ||||||
|  |                     streamId = ((PlaylistStreamEntity) item).getStreamUid(); | ||||||
|  |                 } else if (item instanceof PlaylistStreamEntry) { | ||||||
|  |                     streamId = ((PlaylistStreamEntry) item).streamId; | ||||||
|  |                 } else { | ||||||
|  |                     result.add(null); | ||||||
|  |                     continue; | ||||||
|  |                 } | ||||||
|  |                 final List<StreamStateEntity> states = streamStateTable.getState(streamId).blockingFirst(); | ||||||
|  |                 if (states.isEmpty()) { | ||||||
|  |                     result.add(null); | ||||||
|  |                     continue; | ||||||
|  |                 } | ||||||
|  |                 result.add(states.get(0)); | ||||||
|  |             } | ||||||
|  |             return result; | ||||||
|  |         }).subscribeOn(Schedulers.io()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     /////////////////////////////////////////////////////// |     /////////////////////////////////////////////////////// | ||||||
|     // Utility |     // Utility | ||||||
|     /////////////////////////////////////////////////////// |     /////////////////////////////////////////////////////// | ||||||
| @@ -202,4 +296,5 @@ public class HistoryRecordManager { | |||||||
|     public Single<Integer> removeOrphanedRecords() { |     public Single<Integer> removeOrphanedRecords() { | ||||||
|         return Single.fromCallable(streamTable::deleteOrphans).subscribeOn(Schedulers.io()); |         return Single.fromCallable(streamTable::deleteOrphans).subscribeOn(Schedulers.io()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -2,13 +2,12 @@ package org.schabi.newpipe.local.history; | |||||||
|  |  | ||||||
| import android.app.Activity; | import android.app.Activity; | ||||||
| import android.content.Context; | import android.content.Context; | ||||||
| import android.content.DialogInterface; |  | ||||||
| import android.os.Bundle; | import android.os.Bundle; | ||||||
| import android.os.Parcelable; | import android.os.Parcelable; | ||||||
| import android.support.annotation.NonNull; | import androidx.annotation.NonNull; | ||||||
| import android.support.annotation.Nullable; | import androidx.annotation.Nullable; | ||||||
| import android.support.design.widget.Snackbar; | import com.google.android.material.snackbar.Snackbar; | ||||||
| import android.support.v7.app.AlertDialog; | import androidx.appcompat.app.AlertDialog; | ||||||
| import android.view.LayoutInflater; | import android.view.LayoutInflater; | ||||||
| import android.view.Menu; | import android.view.Menu; | ||||||
| import android.view.MenuInflater; | import android.view.MenuInflater; | ||||||
| @@ -25,6 +24,7 @@ import org.schabi.newpipe.R; | |||||||
| import org.schabi.newpipe.database.LocalItem; | import org.schabi.newpipe.database.LocalItem; | ||||||
| import org.schabi.newpipe.database.stream.StreamStatisticsEntry; | import org.schabi.newpipe.database.stream.StreamStatisticsEntry; | ||||||
| import org.schabi.newpipe.extractor.stream.StreamInfoItem; | import org.schabi.newpipe.extractor.stream.StreamInfoItem; | ||||||
|  | import org.schabi.newpipe.extractor.stream.StreamType; | ||||||
| import org.schabi.newpipe.info_list.InfoItemDialog; | import org.schabi.newpipe.info_list.InfoItemDialog; | ||||||
| import org.schabi.newpipe.local.BaseLocalListFragment; | import org.schabi.newpipe.local.BaseLocalListFragment; | ||||||
| import org.schabi.newpipe.player.playqueue.PlayQueue; | import org.schabi.newpipe.player.playqueue.PlayQueue; | ||||||
| @@ -34,6 +34,7 @@ import org.schabi.newpipe.report.UserAction; | |||||||
| import org.schabi.newpipe.settings.SettingsActivity; | import org.schabi.newpipe.settings.SettingsActivity; | ||||||
| import org.schabi.newpipe.util.NavigationHelper; | import org.schabi.newpipe.util.NavigationHelper; | ||||||
| import org.schabi.newpipe.util.OnClickGesture; | import org.schabi.newpipe.util.OnClickGesture; | ||||||
|  | import org.schabi.newpipe.util.StreamDialogEntry; | ||||||
| import org.schabi.newpipe.util.ThemeHelper; | import org.schabi.newpipe.util.ThemeHelper; | ||||||
|  |  | ||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
| @@ -179,7 +180,7 @@ public class StatisticsPlaylistFragment | |||||||
|                                     .observeOn(AndroidSchedulers.mainThread()) |                                     .observeOn(AndroidSchedulers.mainThread()) | ||||||
|                                     .subscribe( |                                     .subscribe( | ||||||
|                                             howManyDeleted -> Toast.makeText(getContext(), |                                             howManyDeleted -> Toast.makeText(getContext(), | ||||||
|                                                     R.string.view_history_deleted, |                                                     R.string.watch_history_deleted, | ||||||
|                                                     Toast.LENGTH_SHORT).show(), |                                                     Toast.LENGTH_SHORT).show(), | ||||||
|                                             throwable -> ErrorActivity.reportError(getContext(), |                                             throwable -> ErrorActivity.reportError(getContext(), | ||||||
|                                                     throwable, |                                                     throwable, | ||||||
| @@ -309,11 +310,11 @@ public class StatisticsPlaylistFragment | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         headerPlayAllButton.setOnClickListener(view -> |         headerPlayAllButton.setOnClickListener(view -> | ||||||
|                 NavigationHelper.playOnMainPlayer(activity, getPlayQueue())); |                 NavigationHelper.playOnMainPlayer(activity, getPlayQueue(), false)); | ||||||
|         headerPopupButton.setOnClickListener(view -> |         headerPopupButton.setOnClickListener(view -> | ||||||
|                 NavigationHelper.playOnPopupPlayer(activity, getPlayQueue())); |                 NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(), false)); | ||||||
|         headerBackgroundButton.setOnClickListener(view -> |         headerBackgroundButton.setOnClickListener(view -> | ||||||
|                 NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue())); |                 NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue(), false)); | ||||||
|         sortButton.setOnClickListener(view -> toggleSortMode()); |         sortButton.setOnClickListener(view -> toggleSortMode()); | ||||||
|  |  | ||||||
|         hideLoading(); |         hideLoading(); | ||||||
| @@ -356,52 +357,44 @@ public class StatisticsPlaylistFragment | |||||||
|         startLoading(true); |         startLoading(true); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     private PlayQueue getPlayQueueStartingAt(StreamStatisticsEntry infoItem) { | ||||||
|  |         return getPlayQueue(Math.max(itemListAdapter.getItemsList().indexOf(infoItem), 0)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     private void showStreamDialog(final StreamStatisticsEntry item) { |     private void showStreamDialog(final StreamStatisticsEntry item) { | ||||||
|         final Context context = getContext(); |         final Context context = getContext(); | ||||||
|         final Activity activity = getActivity(); |         final Activity activity = getActivity(); | ||||||
|         if (context == null || context.getResources() == null || getActivity() == null) return; |         if (context == null || context.getResources() == null || activity == null) return; | ||||||
|         final StreamInfoItem infoItem = item.toStreamInfoItem(); |         final StreamInfoItem infoItem = item.toStreamInfoItem(); | ||||||
|  |  | ||||||
|         final String[] commands = new String[]{ |         if (infoItem.getStreamType() == StreamType.AUDIO_STREAM) { | ||||||
|                 context.getResources().getString(R.string.enqueue_on_background), |             StreamDialogEntry.setEnabledEntries( | ||||||
|                 context.getResources().getString(R.string.enqueue_on_popup), |                     StreamDialogEntry.enqueue_on_background, | ||||||
|                 context.getResources().getString(R.string.start_here_on_main), |                     StreamDialogEntry.start_here_on_background, | ||||||
|                 context.getResources().getString(R.string.start_here_on_background), |                     StreamDialogEntry.delete, | ||||||
|                 context.getResources().getString(R.string.start_here_on_popup), |                     StreamDialogEntry.append_playlist, | ||||||
|                 context.getResources().getString(R.string.delete), |                     StreamDialogEntry.share); | ||||||
|                 context.getResources().getString(R.string.share) |         } else { | ||||||
|         }; |             StreamDialogEntry.setEnabledEntries( | ||||||
|  |                     StreamDialogEntry.enqueue_on_background, | ||||||
|  |                     StreamDialogEntry.enqueue_on_popup, | ||||||
|  |                     StreamDialogEntry.start_here_on_background, | ||||||
|  |                     StreamDialogEntry.start_here_on_popup, | ||||||
|  |                     StreamDialogEntry.delete, | ||||||
|  |                     StreamDialogEntry.append_playlist, | ||||||
|  |                     StreamDialogEntry.share); | ||||||
|  |  | ||||||
|         final DialogInterface.OnClickListener actions = (dialogInterface, i) -> { |             StreamDialogEntry.start_here_on_popup.setCustomAction( | ||||||
|             final int index = Math.max(itemListAdapter.getItemsList().indexOf(item), 0); |                     (fragment, infoItemDuplicate) -> NavigationHelper.playOnPopupPlayer(context, getPlayQueueStartingAt(item), true)); | ||||||
|             switch (i) { |  | ||||||
|                 case 0: |  | ||||||
|                     NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(infoItem)); |  | ||||||
|                     break; |  | ||||||
|                 case 1: |  | ||||||
|                     NavigationHelper.enqueueOnPopupPlayer(activity, new SinglePlayQueue(infoItem)); |  | ||||||
|                     break; |  | ||||||
|                 case 2: |  | ||||||
|                     NavigationHelper.playOnMainPlayer(context, getPlayQueue(index)); |  | ||||||
|                     break; |  | ||||||
|                 case 3: |  | ||||||
|                     NavigationHelper.playOnBackgroundPlayer(context, getPlayQueue(index)); |  | ||||||
|                     break; |  | ||||||
|                 case 4: |  | ||||||
|                     NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(index)); |  | ||||||
|                     break; |  | ||||||
|                 case 5: |  | ||||||
|                     deleteEntry(index); |  | ||||||
|                     break; |  | ||||||
|                 case 6: |  | ||||||
|                     shareUrl(item.toStreamInfoItem().getName(), item.toStreamInfoItem().getUrl()); |  | ||||||
|                     break; |  | ||||||
|                 default: |  | ||||||
|                     break; |  | ||||||
|         } |         } | ||||||
|         }; |  | ||||||
|  |  | ||||||
|         new InfoItemDialog(getActivity(), infoItem, commands, actions).show(); |         StreamDialogEntry.start_here_on_background.setCustomAction( | ||||||
|  |                 (fragment, infoItemDuplicate) -> NavigationHelper.playOnBackgroundPlayer(context, getPlayQueueStartingAt(item), true)); | ||||||
|  |         StreamDialogEntry.delete.setCustomAction((fragment, infoItemDuplicate) -> | ||||||
|  |             deleteEntry(Math.max(itemListAdapter.getItemsList().indexOf(item), 0))); | ||||||
|  |  | ||||||
|  |         new InfoItemDialog(activity, infoItem, StreamDialogEntry.getCommands(context), (dialog, which) -> | ||||||
|  |                 StreamDialogEntry.clickOn(which, this, infoItem)).show(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private void deleteEntry(final int index) { |     private void deleteEntry(final int index) { | ||||||
|   | |||||||
| @@ -1,11 +1,12 @@ | |||||||
| package org.schabi.newpipe.local.holder; | package org.schabi.newpipe.local.holder; | ||||||
|  |  | ||||||
| import android.support.v7.widget.RecyclerView; | import androidx.recyclerview.widget.RecyclerView; | ||||||
| import android.view.LayoutInflater; | import android.view.LayoutInflater; | ||||||
| import android.view.ViewGroup; | import android.view.ViewGroup; | ||||||
|  |  | ||||||
| import org.schabi.newpipe.database.LocalItem; | import org.schabi.newpipe.database.LocalItem; | ||||||
| import org.schabi.newpipe.local.LocalItemBuilder; | import org.schabi.newpipe.local.LocalItemBuilder; | ||||||
|  | import org.schabi.newpipe.local.history.HistoryRecordManager; | ||||||
|  |  | ||||||
| import java.text.DateFormat; | import java.text.DateFormat; | ||||||
|  |  | ||||||
| @@ -38,5 +39,8 @@ public abstract class LocalItemHolder extends RecyclerView.ViewHolder { | |||||||
|         this.itemBuilder = itemBuilder; |         this.itemBuilder = itemBuilder; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public abstract void updateFromItem(final LocalItem item, final DateFormat dateFormat); |     public abstract void updateFromItem(final LocalItem item, HistoryRecordManager historyRecordManager, final DateFormat dateFormat); | ||||||
|  |  | ||||||
|  |     public void updateState(final LocalItem localItem, HistoryRecordManager historyRecordManager) { | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -6,6 +6,7 @@ import android.view.ViewGroup; | |||||||
| import org.schabi.newpipe.database.LocalItem; | import org.schabi.newpipe.database.LocalItem; | ||||||
| import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry; | import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry; | ||||||
| import org.schabi.newpipe.local.LocalItemBuilder; | import org.schabi.newpipe.local.LocalItemBuilder; | ||||||
|  | import org.schabi.newpipe.local.history.HistoryRecordManager; | ||||||
| import org.schabi.newpipe.util.ImageDisplayConstants; | import org.schabi.newpipe.util.ImageDisplayConstants; | ||||||
|  |  | ||||||
| import java.text.DateFormat; | import java.text.DateFormat; | ||||||
| @@ -21,7 +22,7 @@ public class LocalPlaylistItemHolder extends PlaylistItemHolder { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public void updateFromItem(final LocalItem localItem, final DateFormat dateFormat) { |     public void updateFromItem(final LocalItem localItem, HistoryRecordManager historyRecordManager, final DateFormat dateFormat) { | ||||||
|         if (!(localItem instanceof PlaylistMetadataEntry)) return; |         if (!(localItem instanceof PlaylistMetadataEntry)) return; | ||||||
|         final PlaylistMetadataEntry item = (PlaylistMetadataEntry) localItem; |         final PlaylistMetadataEntry item = (PlaylistMetadataEntry) localItem; | ||||||
|  |  | ||||||
| @@ -32,6 +33,6 @@ public class LocalPlaylistItemHolder extends PlaylistItemHolder { | |||||||
|         itemBuilder.displayImage(item.thumbnailUrl, itemThumbnailView, |         itemBuilder.displayImage(item.thumbnailUrl, itemThumbnailView, | ||||||
|                 ImageDisplayConstants.DISPLAY_PLAYLIST_OPTIONS); |                 ImageDisplayConstants.DISPLAY_PLAYLIST_OPTIONS); | ||||||
|  |  | ||||||
|         super.updateFromItem(localItem, dateFormat); |         super.updateFromItem(localItem, historyRecordManager, dateFormat); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| package org.schabi.newpipe.local.holder; | package org.schabi.newpipe.local.holder; | ||||||
|  |  | ||||||
| import android.support.v4.content.ContextCompat; | import androidx.core.content.ContextCompat; | ||||||
| import android.view.MotionEvent; | import android.view.MotionEvent; | ||||||
| import android.view.View; | import android.view.View; | ||||||
| import android.view.ViewGroup; | import android.view.ViewGroup; | ||||||
| @@ -10,12 +10,18 @@ import android.widget.TextView; | |||||||
| import org.schabi.newpipe.R; | import org.schabi.newpipe.R; | ||||||
| import org.schabi.newpipe.database.LocalItem; | import org.schabi.newpipe.database.LocalItem; | ||||||
| import org.schabi.newpipe.database.playlist.PlaylistStreamEntry; | import org.schabi.newpipe.database.playlist.PlaylistStreamEntry; | ||||||
|  | import org.schabi.newpipe.database.stream.model.StreamStateEntity; | ||||||
| import org.schabi.newpipe.extractor.NewPipe; | import org.schabi.newpipe.extractor.NewPipe; | ||||||
| import org.schabi.newpipe.local.LocalItemBuilder; | import org.schabi.newpipe.local.LocalItemBuilder; | ||||||
|  | import org.schabi.newpipe.local.history.HistoryRecordManager; | ||||||
|  | import org.schabi.newpipe.util.AnimationUtils; | ||||||
| import org.schabi.newpipe.util.ImageDisplayConstants; | import org.schabi.newpipe.util.ImageDisplayConstants; | ||||||
| import org.schabi.newpipe.util.Localization; | import org.schabi.newpipe.util.Localization; | ||||||
|  | import org.schabi.newpipe.views.AnimatedProgressBar; | ||||||
|  |  | ||||||
| import java.text.DateFormat; | import java.text.DateFormat; | ||||||
|  | import java.util.ArrayList; | ||||||
|  | import java.util.concurrent.TimeUnit; | ||||||
|  |  | ||||||
| public class LocalPlaylistStreamItemHolder extends LocalItemHolder { | public class LocalPlaylistStreamItemHolder extends LocalItemHolder { | ||||||
|  |  | ||||||
| @@ -24,6 +30,7 @@ public class LocalPlaylistStreamItemHolder extends LocalItemHolder { | |||||||
|     public final TextView itemAdditionalDetailsView; |     public final TextView itemAdditionalDetailsView; | ||||||
|     public final TextView itemDurationView; |     public final TextView itemDurationView; | ||||||
|     public final View itemHandleView; |     public final View itemHandleView; | ||||||
|  |     public final AnimatedProgressBar itemProgressView; | ||||||
|  |  | ||||||
|     LocalPlaylistStreamItemHolder(LocalItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) { |     LocalPlaylistStreamItemHolder(LocalItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) { | ||||||
|         super(infoItemBuilder, layoutId, parent); |         super(infoItemBuilder, layoutId, parent); | ||||||
| @@ -33,6 +40,7 @@ public class LocalPlaylistStreamItemHolder extends LocalItemHolder { | |||||||
|         itemAdditionalDetailsView = itemView.findViewById(R.id.itemAdditionalDetails); |         itemAdditionalDetailsView = itemView.findViewById(R.id.itemAdditionalDetails); | ||||||
|         itemDurationView = itemView.findViewById(R.id.itemDurationView); |         itemDurationView = itemView.findViewById(R.id.itemDurationView); | ||||||
|         itemHandleView = itemView.findViewById(R.id.itemHandle); |         itemHandleView = itemView.findViewById(R.id.itemHandle); | ||||||
|  |         itemProgressView = itemView.findViewById(R.id.itemProgressView); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public LocalPlaylistStreamItemHolder(LocalItemBuilder infoItemBuilder, ViewGroup parent) { |     public LocalPlaylistStreamItemHolder(LocalItemBuilder infoItemBuilder, ViewGroup parent) { | ||||||
| @@ -40,7 +48,7 @@ public class LocalPlaylistStreamItemHolder extends LocalItemHolder { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public void updateFromItem(final LocalItem localItem, final DateFormat dateFormat) { |     public void updateFromItem(final LocalItem localItem, HistoryRecordManager historyRecordManager, final DateFormat dateFormat) { | ||||||
|         if (!(localItem instanceof PlaylistStreamEntry)) return; |         if (!(localItem instanceof PlaylistStreamEntry)) return; | ||||||
|         final PlaylistStreamEntry item = (PlaylistStreamEntry) localItem; |         final PlaylistStreamEntry item = (PlaylistStreamEntry) localItem; | ||||||
|  |  | ||||||
| @@ -53,6 +61,15 @@ public class LocalPlaylistStreamItemHolder extends LocalItemHolder { | |||||||
|             itemDurationView.setBackgroundColor(ContextCompat.getColor(itemBuilder.getContext(), |             itemDurationView.setBackgroundColor(ContextCompat.getColor(itemBuilder.getContext(), | ||||||
|                     R.color.duration_background_color)); |                     R.color.duration_background_color)); | ||||||
|             itemDurationView.setVisibility(View.VISIBLE); |             itemDurationView.setVisibility(View.VISIBLE); | ||||||
|  |  | ||||||
|  |             StreamStateEntity state = historyRecordManager.loadLocalStreamStateBatch(new ArrayList<LocalItem>() {{ add(localItem); }}).blockingGet().get(0); | ||||||
|  |             if (state != null) { | ||||||
|  |                 itemProgressView.setVisibility(View.VISIBLE); | ||||||
|  |                 itemProgressView.setMax((int) item.duration); | ||||||
|  |                 itemProgressView.setProgress((int) TimeUnit.MILLISECONDS.toSeconds(state.getProgressTime())); | ||||||
|  |             } else { | ||||||
|  |                 itemProgressView.setVisibility(View.GONE); | ||||||
|  |             } | ||||||
|         } else { |         } else { | ||||||
|             itemDurationView.setVisibility(View.GONE); |             itemDurationView.setVisibility(View.GONE); | ||||||
|         } |         } | ||||||
| @@ -79,6 +96,25 @@ public class LocalPlaylistStreamItemHolder extends LocalItemHolder { | |||||||
|         itemHandleView.setOnTouchListener(getOnTouchListener(item)); |         itemHandleView.setOnTouchListener(getOnTouchListener(item)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void updateState(LocalItem localItem, HistoryRecordManager historyRecordManager) { | ||||||
|  |         if (!(localItem instanceof PlaylistStreamEntry)) return; | ||||||
|  |         final PlaylistStreamEntry item = (PlaylistStreamEntry) localItem; | ||||||
|  |  | ||||||
|  |         StreamStateEntity state = historyRecordManager.loadLocalStreamStateBatch(new ArrayList<LocalItem>() {{ add(localItem); }}).blockingGet().get(0); | ||||||
|  |         if (state != null && item.duration > 0) { | ||||||
|  |             itemProgressView.setMax((int) item.duration); | ||||||
|  |             if (itemProgressView.getVisibility() == View.VISIBLE) { | ||||||
|  |                 itemProgressView.setProgressAnimated((int) TimeUnit.MILLISECONDS.toSeconds(state.getProgressTime())); | ||||||
|  |             } else { | ||||||
|  |                 itemProgressView.setProgress((int) TimeUnit.MILLISECONDS.toSeconds(state.getProgressTime())); | ||||||
|  |                 AnimationUtils.animateView(itemProgressView, true, 500); | ||||||
|  |             } | ||||||
|  |         } else if (itemProgressView.getVisibility() == View.VISIBLE) { | ||||||
|  |             AnimationUtils.animateView(itemProgressView, false, 500); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     private View.OnTouchListener getOnTouchListener(final PlaylistStreamEntry item) { |     private View.OnTouchListener getOnTouchListener(final PlaylistStreamEntry item) { | ||||||
|         return (view, motionEvent) -> { |         return (view, motionEvent) -> { | ||||||
|             view.performClick(); |             view.performClick(); | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| package org.schabi.newpipe.local.holder; | package org.schabi.newpipe.local.holder; | ||||||
|  |  | ||||||
| import android.support.annotation.Nullable; | import androidx.annotation.Nullable; | ||||||
| import android.support.v4.content.ContextCompat; | import androidx.core.content.ContextCompat; | ||||||
| import android.view.View; | import android.view.View; | ||||||
| import android.view.ViewGroup; | import android.view.ViewGroup; | ||||||
| import android.widget.ImageView; | import android.widget.ImageView; | ||||||
| @@ -10,12 +10,18 @@ import android.widget.TextView; | |||||||
| import org.schabi.newpipe.R; | import org.schabi.newpipe.R; | ||||||
| import org.schabi.newpipe.database.LocalItem; | import org.schabi.newpipe.database.LocalItem; | ||||||
| import org.schabi.newpipe.database.stream.StreamStatisticsEntry; | import org.schabi.newpipe.database.stream.StreamStatisticsEntry; | ||||||
|  | import org.schabi.newpipe.database.stream.model.StreamStateEntity; | ||||||
| import org.schabi.newpipe.extractor.NewPipe; | import org.schabi.newpipe.extractor.NewPipe; | ||||||
| import org.schabi.newpipe.local.LocalItemBuilder; | import org.schabi.newpipe.local.LocalItemBuilder; | ||||||
|  | import org.schabi.newpipe.local.history.HistoryRecordManager; | ||||||
|  | import org.schabi.newpipe.util.AnimationUtils; | ||||||
| import org.schabi.newpipe.util.ImageDisplayConstants; | import org.schabi.newpipe.util.ImageDisplayConstants; | ||||||
| import org.schabi.newpipe.util.Localization; | import org.schabi.newpipe.util.Localization; | ||||||
|  | import org.schabi.newpipe.views.AnimatedProgressBar; | ||||||
|  |  | ||||||
| import java.text.DateFormat; | import java.text.DateFormat; | ||||||
|  | import java.util.ArrayList; | ||||||
|  | import java.util.concurrent.TimeUnit; | ||||||
|  |  | ||||||
| /* | /* | ||||||
|  * Created by Christian Schabesberger on 01.08.16. |  * Created by Christian Schabesberger on 01.08.16. | ||||||
| @@ -45,6 +51,7 @@ public class LocalStatisticStreamItemHolder extends LocalItemHolder { | |||||||
|     public final TextView itemDurationView; |     public final TextView itemDurationView; | ||||||
|     @Nullable |     @Nullable | ||||||
|     public final TextView itemAdditionalDetails; |     public final TextView itemAdditionalDetails; | ||||||
|  |     public final AnimatedProgressBar itemProgressView; | ||||||
|  |  | ||||||
|     public LocalStatisticStreamItemHolder(LocalItemBuilder itemBuilder, ViewGroup parent) { |     public LocalStatisticStreamItemHolder(LocalItemBuilder itemBuilder, ViewGroup parent) { | ||||||
|         this(itemBuilder, R.layout.list_stream_item, parent); |         this(itemBuilder, R.layout.list_stream_item, parent); | ||||||
| @@ -58,6 +65,7 @@ public class LocalStatisticStreamItemHolder extends LocalItemHolder { | |||||||
|         itemUploaderView = itemView.findViewById(R.id.itemUploaderView); |         itemUploaderView = itemView.findViewById(R.id.itemUploaderView); | ||||||
|         itemDurationView = itemView.findViewById(R.id.itemDurationView); |         itemDurationView = itemView.findViewById(R.id.itemDurationView); | ||||||
|         itemAdditionalDetails = itemView.findViewById(R.id.itemAdditionalDetails); |         itemAdditionalDetails = itemView.findViewById(R.id.itemAdditionalDetails); | ||||||
|  |         itemProgressView = itemView.findViewById(R.id.itemProgressView); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private String getStreamInfoDetailLine(final StreamStatisticsEntry entry, |     private String getStreamInfoDetailLine(final StreamStatisticsEntry entry, | ||||||
| @@ -70,7 +78,7 @@ public class LocalStatisticStreamItemHolder extends LocalItemHolder { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public void updateFromItem(final LocalItem localItem, final DateFormat dateFormat) { |     public void updateFromItem(final LocalItem localItem, HistoryRecordManager historyRecordManager, final DateFormat dateFormat) { | ||||||
|         if (!(localItem instanceof StreamStatisticsEntry)) return; |         if (!(localItem instanceof StreamStatisticsEntry)) return; | ||||||
|         final StreamStatisticsEntry item = (StreamStatisticsEntry) localItem; |         final StreamStatisticsEntry item = (StreamStatisticsEntry) localItem; | ||||||
|  |  | ||||||
| @@ -82,8 +90,18 @@ public class LocalStatisticStreamItemHolder extends LocalItemHolder { | |||||||
|             itemDurationView.setBackgroundColor(ContextCompat.getColor(itemBuilder.getContext(), |             itemDurationView.setBackgroundColor(ContextCompat.getColor(itemBuilder.getContext(), | ||||||
|                     R.color.duration_background_color)); |                     R.color.duration_background_color)); | ||||||
|             itemDurationView.setVisibility(View.VISIBLE); |             itemDurationView.setVisibility(View.VISIBLE); | ||||||
|  |  | ||||||
|  |             StreamStateEntity state = historyRecordManager.loadLocalStreamStateBatch(new ArrayList<LocalItem>() {{ add(localItem); }}).blockingGet().get(0); | ||||||
|  |             if (state != null) { | ||||||
|  |                 itemProgressView.setVisibility(View.VISIBLE); | ||||||
|  |                 itemProgressView.setMax((int) item.duration); | ||||||
|  |                 itemProgressView.setProgress((int) TimeUnit.MILLISECONDS.toSeconds(state.getProgressTime())); | ||||||
|  |             } else { | ||||||
|  |                 itemProgressView.setVisibility(View.GONE); | ||||||
|  |             } | ||||||
|         } else { |         } else { | ||||||
|             itemDurationView.setVisibility(View.GONE); |             itemDurationView.setVisibility(View.GONE); | ||||||
|  |             itemProgressView.setVisibility(View.GONE); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (itemAdditionalDetails != null) { |         if (itemAdditionalDetails != null) { | ||||||
| @@ -108,4 +126,23 @@ public class LocalStatisticStreamItemHolder extends LocalItemHolder { | |||||||
|             return true; |             return true; | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void updateState(LocalItem localItem, HistoryRecordManager historyRecordManager) { | ||||||
|  |         if (!(localItem instanceof StreamStatisticsEntry)) return; | ||||||
|  |         final StreamStatisticsEntry item = (StreamStatisticsEntry) localItem; | ||||||
|  |  | ||||||
|  |         StreamStateEntity state = historyRecordManager.loadLocalStreamStateBatch(new ArrayList<LocalItem>() {{ add(localItem); }}).blockingGet().get(0); | ||||||
|  |         if (state != null && item.duration > 0) { | ||||||
|  |             itemProgressView.setMax((int) item.duration); | ||||||
|  |             if (itemProgressView.getVisibility() == View.VISIBLE) { | ||||||
|  |                 itemProgressView.setProgressAnimated((int) TimeUnit.MILLISECONDS.toSeconds(state.getProgressTime())); | ||||||
|  |             } else { | ||||||
|  |                 itemProgressView.setProgress((int) TimeUnit.MILLISECONDS.toSeconds(state.getProgressTime())); | ||||||
|  |                 AnimationUtils.animateView(itemProgressView, true, 500); | ||||||
|  |             } | ||||||
|  |         } else if (itemProgressView.getVisibility() == View.VISIBLE) { | ||||||
|  |             AnimationUtils.animateView(itemProgressView, false, 500); | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -7,6 +7,7 @@ import android.widget.TextView; | |||||||
| import org.schabi.newpipe.R; | import org.schabi.newpipe.R; | ||||||
| import org.schabi.newpipe.database.LocalItem; | import org.schabi.newpipe.database.LocalItem; | ||||||
| import org.schabi.newpipe.local.LocalItemBuilder; | import org.schabi.newpipe.local.LocalItemBuilder; | ||||||
|  | import org.schabi.newpipe.local.history.HistoryRecordManager; | ||||||
|  |  | ||||||
| import java.text.DateFormat; | import java.text.DateFormat; | ||||||
|  |  | ||||||
| @@ -31,7 +32,7 @@ public abstract class PlaylistItemHolder extends LocalItemHolder { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public void updateFromItem(final LocalItem localItem, final DateFormat dateFormat) { |     public void updateFromItem(final LocalItem localItem, HistoryRecordManager historyRecordManager, final DateFormat dateFormat) { | ||||||
|         itemView.setOnClickListener(view -> { |         itemView.setOnClickListener(view -> { | ||||||
|             if (itemBuilder.getOnItemSelectedListener() != null) { |             if (itemBuilder.getOnItemSelectedListener() != null) { | ||||||
|                 itemBuilder.getOnItemSelectedListener().selected(localItem); |                 itemBuilder.getOnItemSelectedListener().selected(localItem); | ||||||
|   | |||||||
| @@ -6,6 +6,7 @@ import org.schabi.newpipe.database.LocalItem; | |||||||
| import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity; | import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity; | ||||||
| import org.schabi.newpipe.extractor.NewPipe; | import org.schabi.newpipe.extractor.NewPipe; | ||||||
| import org.schabi.newpipe.local.LocalItemBuilder; | import org.schabi.newpipe.local.LocalItemBuilder; | ||||||
|  | import org.schabi.newpipe.local.history.HistoryRecordManager; | ||||||
| import org.schabi.newpipe.util.ImageDisplayConstants; | import org.schabi.newpipe.util.ImageDisplayConstants; | ||||||
| import org.schabi.newpipe.util.Localization; | import org.schabi.newpipe.util.Localization; | ||||||
|  |  | ||||||
| @@ -21,7 +22,7 @@ public class RemotePlaylistItemHolder extends PlaylistItemHolder { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public void updateFromItem(final LocalItem localItem, final DateFormat dateFormat) { |     public void updateFromItem(final LocalItem localItem, HistoryRecordManager historyRecordManager, final DateFormat dateFormat) { | ||||||
|         if (!(localItem instanceof PlaylistRemoteEntity)) return; |         if (!(localItem instanceof PlaylistRemoteEntity)) return; | ||||||
|         final PlaylistRemoteEntity item = (PlaylistRemoteEntity) localItem; |         final PlaylistRemoteEntity item = (PlaylistRemoteEntity) localItem; | ||||||
|  |  | ||||||
| @@ -33,6 +34,6 @@ public class RemotePlaylistItemHolder extends PlaylistItemHolder { | |||||||
|         itemBuilder.displayImage(item.getThumbnailUrl(), itemThumbnailView, |         itemBuilder.displayImage(item.getThumbnailUrl(), itemThumbnailView, | ||||||
|                 ImageDisplayConstants.DISPLAY_PLAYLIST_OPTIONS); |                 ImageDisplayConstants.DISPLAY_PLAYLIST_OPTIONS); | ||||||
|  |  | ||||||
|         super.updateFromItem(localItem, dateFormat); |         super.updateFromItem(localItem, historyRecordManager, dateFormat); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -2,14 +2,13 @@ package org.schabi.newpipe.local.playlist; | |||||||
|  |  | ||||||
| import android.app.Activity; | import android.app.Activity; | ||||||
| import android.content.Context; | import android.content.Context; | ||||||
| import android.content.DialogInterface; |  | ||||||
| import android.os.Bundle; | import android.os.Bundle; | ||||||
| import android.os.Parcelable; | import android.os.Parcelable; | ||||||
| import android.support.annotation.NonNull; | import androidx.annotation.NonNull; | ||||||
| import android.support.annotation.Nullable; | import androidx.annotation.Nullable; | ||||||
| import android.support.v7.app.AlertDialog; | import androidx.appcompat.app.AlertDialog; | ||||||
| import android.support.v7.widget.RecyclerView; | import androidx.recyclerview.widget.RecyclerView; | ||||||
| import android.support.v7.widget.helper.ItemTouchHelper; | import androidx.recyclerview.widget.ItemTouchHelper; | ||||||
| import android.text.TextUtils; | import android.text.TextUtils; | ||||||
| import android.util.Log; | import android.util.Log; | ||||||
| import android.view.LayoutInflater; | import android.view.LayoutInflater; | ||||||
| @@ -26,14 +25,16 @@ import org.schabi.newpipe.R; | |||||||
| import org.schabi.newpipe.database.LocalItem; | import org.schabi.newpipe.database.LocalItem; | ||||||
| import org.schabi.newpipe.database.playlist.PlaylistStreamEntry; | import org.schabi.newpipe.database.playlist.PlaylistStreamEntry; | ||||||
| import org.schabi.newpipe.extractor.stream.StreamInfoItem; | import org.schabi.newpipe.extractor.stream.StreamInfoItem; | ||||||
| import org.schabi.newpipe.local.BaseLocalListFragment; | import org.schabi.newpipe.extractor.stream.StreamType; | ||||||
| import org.schabi.newpipe.info_list.InfoItemDialog; | import org.schabi.newpipe.info_list.InfoItemDialog; | ||||||
|  | import org.schabi.newpipe.local.BaseLocalListFragment; | ||||||
| import org.schabi.newpipe.player.playqueue.PlayQueue; | import org.schabi.newpipe.player.playqueue.PlayQueue; | ||||||
| import org.schabi.newpipe.player.playqueue.SinglePlayQueue; | import org.schabi.newpipe.player.playqueue.SinglePlayQueue; | ||||||
| import org.schabi.newpipe.report.UserAction; | import org.schabi.newpipe.report.UserAction; | ||||||
| import org.schabi.newpipe.util.Localization; | import org.schabi.newpipe.util.Localization; | ||||||
| import org.schabi.newpipe.util.NavigationHelper; | import org.schabi.newpipe.util.NavigationHelper; | ||||||
| import org.schabi.newpipe.util.OnClickGesture; | import org.schabi.newpipe.util.OnClickGesture; | ||||||
|  | import org.schabi.newpipe.util.StreamDialogEntry; | ||||||
|  |  | ||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
| import java.util.Collections; | import java.util.Collections; | ||||||
| @@ -318,11 +319,11 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt | |||||||
|         setVideoCount(itemListAdapter.getItemsList().size()); |         setVideoCount(itemListAdapter.getItemsList().size()); | ||||||
|  |  | ||||||
|         headerPlayAllButton.setOnClickListener(view -> |         headerPlayAllButton.setOnClickListener(view -> | ||||||
|                 NavigationHelper.playOnMainPlayer(activity, getPlayQueue())); |                 NavigationHelper.playOnMainPlayer(activity, getPlayQueue(), false)); | ||||||
|         headerPopupButton.setOnClickListener(view -> |         headerPopupButton.setOnClickListener(view -> | ||||||
|                 NavigationHelper.playOnPopupPlayer(activity, getPlayQueue())); |                 NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(), false)); | ||||||
|         headerBackgroundButton.setOnClickListener(view -> |         headerBackgroundButton.setOnClickListener(view -> | ||||||
|                 NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue())); |                 NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue(), false)); | ||||||
|  |  | ||||||
|         hideLoading(); |         hideLoading(); | ||||||
|     } |     } | ||||||
| @@ -510,59 +511,48 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt | |||||||
|     // Utils |     // Utils | ||||||
|     //////////////////////////////////////////////////////////////////////////*/ |     //////////////////////////////////////////////////////////////////////////*/ | ||||||
|  |  | ||||||
|  |     private PlayQueue getPlayQueueStartingAt(PlaylistStreamEntry infoItem) { | ||||||
|  |         return getPlayQueue(Math.max(itemListAdapter.getItemsList().indexOf(infoItem), 0)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     protected void showStreamItemDialog(final PlaylistStreamEntry item) { |     protected void showStreamItemDialog(final PlaylistStreamEntry item) { | ||||||
|         final Context context = getContext(); |         final Context context = getContext(); | ||||||
|         final Activity activity = getActivity(); |         final Activity activity = getActivity(); | ||||||
|         if (context == null || context.getResources() == null || getActivity() == null) return; |         if (context == null || context.getResources() == null || activity == null) return; | ||||||
|  |  | ||||||
|         final StreamInfoItem infoItem = item.toStreamInfoItem(); |         final StreamInfoItem infoItem = item.toStreamInfoItem(); | ||||||
|  |  | ||||||
|         final String[] commands = new String[]{ |         if (infoItem.getStreamType() == StreamType.AUDIO_STREAM) { | ||||||
|                 context.getResources().getString(R.string.enqueue_on_background), |             StreamDialogEntry.setEnabledEntries( | ||||||
|                 context.getResources().getString(R.string.enqueue_on_popup), |                     StreamDialogEntry.enqueue_on_background, | ||||||
|                 context.getResources().getString(R.string.start_here_on_main), |                     StreamDialogEntry.start_here_on_background, | ||||||
|                 context.getResources().getString(R.string.start_here_on_background), |                     StreamDialogEntry.set_as_playlist_thumbnail, | ||||||
|                 context.getResources().getString(R.string.start_here_on_popup), |                     StreamDialogEntry.delete, | ||||||
|                 context.getResources().getString(R.string.set_as_playlist_thumbnail), |                     StreamDialogEntry.append_playlist, | ||||||
|                 context.getResources().getString(R.string.delete), |                     StreamDialogEntry.share); | ||||||
|                 context.getResources().getString(R.string.share) |         } else { | ||||||
|         }; |             StreamDialogEntry.setEnabledEntries( | ||||||
|  |                     StreamDialogEntry.enqueue_on_background, | ||||||
|  |                     StreamDialogEntry.enqueue_on_popup, | ||||||
|  |                     StreamDialogEntry.start_here_on_background, | ||||||
|  |                     StreamDialogEntry.start_here_on_popup, | ||||||
|  |                     StreamDialogEntry.set_as_playlist_thumbnail, | ||||||
|  |                     StreamDialogEntry.delete, | ||||||
|  |                     StreamDialogEntry.append_playlist, | ||||||
|  |                     StreamDialogEntry.share); | ||||||
|  |  | ||||||
|         final DialogInterface.OnClickListener actions = (dialogInterface, i) -> { |             StreamDialogEntry.start_here_on_popup.setCustomAction( | ||||||
|             final int index = Math.max(itemListAdapter.getItemsList().indexOf(item), 0); |                     (fragment, infoItemDuplicate) -> NavigationHelper.playOnPopupPlayer(context, getPlayQueueStartingAt(item), true)); | ||||||
|             switch (i) { |  | ||||||
|                 case 0: |  | ||||||
|                     NavigationHelper.enqueueOnBackgroundPlayer(context, |  | ||||||
|                             new SinglePlayQueue(infoItem)); |  | ||||||
|                     break; |  | ||||||
|                 case 1: |  | ||||||
|                     NavigationHelper.enqueueOnPopupPlayer(activity, new |  | ||||||
|                             SinglePlayQueue(infoItem)); |  | ||||||
|                     break; |  | ||||||
|                 case 2: |  | ||||||
|                     NavigationHelper.playOnMainPlayer(context, getPlayQueue(index)); |  | ||||||
|                     break; |  | ||||||
|                 case 3: |  | ||||||
|                     NavigationHelper.playOnBackgroundPlayer(context, getPlayQueue(index)); |  | ||||||
|                     break; |  | ||||||
|                 case 4: |  | ||||||
|                     NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(index)); |  | ||||||
|                     break; |  | ||||||
|                 case 5: |  | ||||||
|                     changeThumbnailUrl(item.thumbnailUrl); |  | ||||||
|                     break; |  | ||||||
|                 case 6: |  | ||||||
|                     deleteItem(item); |  | ||||||
|                     break; |  | ||||||
|                 case 7: |  | ||||||
|                     shareUrl(item.toStreamInfoItem().getName(), item.toStreamInfoItem().getUrl()); |  | ||||||
|                     break; |  | ||||||
|                 default: |  | ||||||
|                     break; |  | ||||||
|         } |         } | ||||||
|         }; |  | ||||||
|  |  | ||||||
|         new InfoItemDialog(getActivity(), infoItem, commands, actions).show(); |         StreamDialogEntry.start_here_on_background.setCustomAction( | ||||||
|  |                 (fragment, infoItemDuplicate) -> NavigationHelper.playOnBackgroundPlayer(context, getPlayQueueStartingAt(item), true)); | ||||||
|  |         StreamDialogEntry.set_as_playlist_thumbnail.setCustomAction( | ||||||
|  |                 (fragment, infoItemDuplicate) -> changeThumbnailUrl(item.thumbnailUrl)); | ||||||
|  |         StreamDialogEntry.delete.setCustomAction( | ||||||
|  |                 (fragment, infoItemDuplicate) -> deleteItem(item)); | ||||||
|  |  | ||||||
|  |         new InfoItemDialog(activity, infoItem, StreamDialogEntry.getCommands(context), (dialog, which) -> | ||||||
|  |                 StreamDialogEntry.clickOn(which, this, infoItem)).show(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private void setInitialData(long playlistId, String name) { |     private void setInitialData(long playlistId, String name) { | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| package org.schabi.newpipe.local.playlist; | package org.schabi.newpipe.local.playlist; | ||||||
|  |  | ||||||
| import android.support.annotation.Nullable; | import androidx.annotation.Nullable; | ||||||
|  |  | ||||||
| import org.schabi.newpipe.database.AppDatabase; | import org.schabi.newpipe.database.AppDatabase; | ||||||
| import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry; | import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry; | ||||||
|   | |||||||
| @@ -4,10 +4,10 @@ import android.app.AlertDialog; | |||||||
| import android.app.Dialog; | import android.app.Dialog; | ||||||
| import android.content.Intent; | import android.content.Intent; | ||||||
| import android.os.Bundle; | import android.os.Bundle; | ||||||
| import android.support.annotation.NonNull; | import androidx.annotation.NonNull; | ||||||
| import android.support.annotation.Nullable; | import androidx.annotation.Nullable; | ||||||
| import android.support.v4.app.DialogFragment; | import androidx.fragment.app.DialogFragment; | ||||||
| import android.support.v4.app.Fragment; | import androidx.fragment.app.Fragment; | ||||||
|  |  | ||||||
| import org.schabi.newpipe.R; | import org.schabi.newpipe.R; | ||||||
| import org.schabi.newpipe.util.ThemeHelper; | import org.schabi.newpipe.util.ThemeHelper; | ||||||
|   | |||||||
| @@ -19,7 +19,7 @@ | |||||||
|  |  | ||||||
| package org.schabi.newpipe.local.subscription; | package org.schabi.newpipe.local.subscription; | ||||||
|  |  | ||||||
| import android.support.annotation.Nullable; | import androidx.annotation.Nullable; | ||||||
|  |  | ||||||
| import com.grack.nanojson.JsonAppendableWriter; | import com.grack.nanojson.JsonAppendableWriter; | ||||||
| import com.grack.nanojson.JsonArray; | import com.grack.nanojson.JsonArray; | ||||||
|   | |||||||
| @@ -17,16 +17,15 @@ import android.os.Bundle; | |||||||
| import android.os.Environment; | import android.os.Environment; | ||||||
| import android.os.Parcelable; | import android.os.Parcelable; | ||||||
| import android.preference.PreferenceManager; | import android.preference.PreferenceManager; | ||||||
| import android.support.annotation.DrawableRes; | import androidx.annotation.DrawableRes; | ||||||
| import android.support.annotation.NonNull; | import androidx.annotation.NonNull; | ||||||
| import android.support.annotation.Nullable; | import androidx.annotation.Nullable; | ||||||
| import android.support.v4.app.FragmentManager; | import androidx.fragment.app.FragmentManager; | ||||||
| import android.support.v4.content.LocalBroadcastManager; | import androidx.localbroadcastmanager.content.LocalBroadcastManager; | ||||||
| import android.support.v7.app.ActionBar; | import androidx.appcompat.app.ActionBar; | ||||||
| import android.support.v7.app.AppCompatActivity; | import androidx.recyclerview.widget.GridLayoutManager; | ||||||
| import android.support.v7.widget.GridLayoutManager; | import androidx.recyclerview.widget.LinearLayoutManager; | ||||||
| import android.support.v7.widget.LinearLayoutManager; | import androidx.recyclerview.widget.RecyclerView; | ||||||
| import android.support.v7.widget.RecyclerView; |  | ||||||
| import android.view.LayoutInflater; | import android.view.LayoutInflater; | ||||||
| import android.view.Menu; | import android.view.Menu; | ||||||
| import android.view.MenuInflater; | import android.view.MenuInflater; | ||||||
| @@ -48,15 +47,14 @@ import org.schabi.newpipe.extractor.exceptions.ExtractionException; | |||||||
| import org.schabi.newpipe.extractor.subscription.SubscriptionExtractor; | import org.schabi.newpipe.extractor.subscription.SubscriptionExtractor; | ||||||
| import org.schabi.newpipe.fragments.BaseStateFragment; | import org.schabi.newpipe.fragments.BaseStateFragment; | ||||||
| import org.schabi.newpipe.info_list.InfoListAdapter; | import org.schabi.newpipe.info_list.InfoListAdapter; | ||||||
| import org.schabi.newpipe.report.UserAction; |  | ||||||
| import org.schabi.newpipe.local.subscription.services.SubscriptionsExportService; | import org.schabi.newpipe.local.subscription.services.SubscriptionsExportService; | ||||||
| import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService; | import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService; | ||||||
| import org.schabi.newpipe.report.ErrorActivity; |  | ||||||
| import org.schabi.newpipe.report.UserAction; | import org.schabi.newpipe.report.UserAction; | ||||||
| import org.schabi.newpipe.util.FilePickerActivityHelper; | import org.schabi.newpipe.util.FilePickerActivityHelper; | ||||||
| import org.schabi.newpipe.util.NavigationHelper; | import org.schabi.newpipe.util.NavigationHelper; | ||||||
| import org.schabi.newpipe.util.OnClickGesture; | import org.schabi.newpipe.util.OnClickGesture; | ||||||
| import org.schabi.newpipe.util.ServiceHelper; | import org.schabi.newpipe.util.ServiceHelper; | ||||||
|  | import org.schabi.newpipe.util.ShareUtils; | ||||||
| import org.schabi.newpipe.util.ThemeHelper; | import org.schabi.newpipe.util.ThemeHelper; | ||||||
| import org.schabi.newpipe.views.CollapsibleView; | import org.schabi.newpipe.views.CollapsibleView; | ||||||
|  |  | ||||||
| @@ -130,6 +128,11 @@ public class SubscriptionFragment extends BaseStateFragment<List<SubscriptionEnt | |||||||
|         subscriptionService = SubscriptionService.getInstance(activity); |         subscriptionService = SubscriptionService.getInstance(activity); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void onDetach() { | ||||||
|  |         super.onDetach(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     @Nullable |     @Nullable | ||||||
|     @Override |     @Override | ||||||
|     public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) { |     public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) { | ||||||
| @@ -376,7 +379,6 @@ public class SubscriptionFragment extends BaseStateFragment<List<SubscriptionEnt | |||||||
|  |  | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         //noinspection ConstantConditions |  | ||||||
|         whatsNewItemListHeader.setOnClickListener(v -> { |         whatsNewItemListHeader.setOnClickListener(v -> { | ||||||
|             FragmentManager fragmentManager = getFM(); |             FragmentManager fragmentManager = getFM(); | ||||||
|             NavigationHelper.openWhatsNewFragment(fragmentManager); |             NavigationHelper.openWhatsNewFragment(fragmentManager); | ||||||
| @@ -390,17 +392,17 @@ public class SubscriptionFragment extends BaseStateFragment<List<SubscriptionEnt | |||||||
|         if (context == null || context.getResources() == null || getActivity() == null) return; |         if (context == null || context.getResources() == null || getActivity() == null) return; | ||||||
|  |  | ||||||
|         final String[] commands = new String[]{ |         final String[] commands = new String[]{ | ||||||
|                 context.getResources().getString(R.string.share), |                 context.getResources().getString(R.string.unsubscribe), | ||||||
|                 context.getResources().getString(R.string.unsubscribe) |                 context.getResources().getString(R.string.share) | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|         final DialogInterface.OnClickListener actions = (dialogInterface, i) -> { |         final DialogInterface.OnClickListener actions = (dialogInterface, i) -> { | ||||||
|             switch (i) { |             switch (i) { | ||||||
|                 case 0: |                 case 0: | ||||||
|                     shareChannel(selectedItem); |                     deleteChannel(selectedItem); | ||||||
|                     break; |                     break; | ||||||
|                 case 1: |                 case 1: | ||||||
|                     deleteChannel(selectedItem); |                     shareChannel(selectedItem); | ||||||
|                     break; |                     break; | ||||||
|                 default: |                 default: | ||||||
|                     break; |                     break; | ||||||
| @@ -425,7 +427,7 @@ public class SubscriptionFragment extends BaseStateFragment<List<SubscriptionEnt | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     private void shareChannel(ChannelInfoItem selectedItem) { |     private void shareChannel(ChannelInfoItem selectedItem) { | ||||||
|         shareUrl(selectedItem.getName(), selectedItem.getUrl()); |         ShareUtils.shareUrl(getContext(), selectedItem.getName(), selectedItem.getUrl()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @SuppressLint("CheckResult") |     @SuppressLint("CheckResult") | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| package org.schabi.newpipe.local.subscription; | package org.schabi.newpipe.local.subscription; | ||||||
|  |  | ||||||
| import android.content.Context; | import android.content.Context; | ||||||
| import android.support.annotation.NonNull; | import androidx.annotation.NonNull; | ||||||
| import android.util.Log; | import android.util.Log; | ||||||
|  |  | ||||||
| import org.schabi.newpipe.MainActivity; | import org.schabi.newpipe.MainActivity; | ||||||
| @@ -93,7 +93,7 @@ public class SubscriptionService { | |||||||
|      * in the cooldown interval, only the latest changes are emitted to the subscribers. |      * in the cooldown interval, only the latest changes are emitted to the subscribers. | ||||||
|      * This reduces the amount of observations caused by frequent updates to the database. |      * This reduces the amount of observations caused by frequent updates to the database. | ||||||
|      */ |      */ | ||||||
|     @android.support.annotation.NonNull |     @androidx.annotation.NonNull | ||||||
|     public Flowable<List<SubscriptionEntity>> getSubscription() { |     public Flowable<List<SubscriptionEntity>> getSubscription() { | ||||||
|         return subscription; |         return subscription; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -3,11 +3,11 @@ package org.schabi.newpipe.local.subscription; | |||||||
| import android.app.Activity; | import android.app.Activity; | ||||||
| import android.content.Intent; | import android.content.Intent; | ||||||
| import android.os.Bundle; | import android.os.Bundle; | ||||||
| import android.support.annotation.NonNull; | import androidx.annotation.NonNull; | ||||||
| import android.support.annotation.Nullable; | import androidx.annotation.Nullable; | ||||||
| import android.support.annotation.StringRes; | import androidx.annotation.StringRes; | ||||||
| import android.support.v4.text.util.LinkifyCompat; | import androidx.core.text.util.LinkifyCompat; | ||||||
| import android.support.v7.app.ActionBar; | import androidx.appcompat.app.ActionBar; | ||||||
| import android.text.TextUtils; | import android.text.TextUtils; | ||||||
| import android.text.util.Linkify; | import android.text.util.Linkify; | ||||||
| import android.view.LayoutInflater; | import android.view.LayoutInflater; | ||||||
|   | |||||||
| @@ -23,11 +23,11 @@ import android.app.Service; | |||||||
| import android.content.Intent; | import android.content.Intent; | ||||||
| import android.os.Build; | import android.os.Build; | ||||||
| import android.os.IBinder; | import android.os.IBinder; | ||||||
| import android.support.annotation.NonNull; | import androidx.annotation.NonNull; | ||||||
| import android.support.annotation.Nullable; | import androidx.annotation.Nullable; | ||||||
| import android.support.annotation.StringRes; | import androidx.annotation.StringRes; | ||||||
| import android.support.v4.app.NotificationCompat; | import androidx.core.app.NotificationCompat; | ||||||
| import android.support.v4.app.NotificationManagerCompat; | import androidx.core.app.NotificationManagerCompat; | ||||||
| import android.text.TextUtils; | import android.text.TextUtils; | ||||||
| import android.widget.Toast; | import android.widget.Toast; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -20,7 +20,7 @@ | |||||||
| package org.schabi.newpipe.local.subscription.services; | package org.schabi.newpipe.local.subscription.services; | ||||||
|  |  | ||||||
| import android.content.Intent; | import android.content.Intent; | ||||||
| import android.support.v4.content.LocalBroadcastManager; | import androidx.localbroadcastmanager.content.LocalBroadcastManager; | ||||||
| import android.text.TextUtils; | import android.text.TextUtils; | ||||||
| import android.util.Log; | import android.util.Log; | ||||||
|  |  | ||||||
|   | |||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user
	 yausername
					yausername