merged upstream/dev

This commit is contained in:
yausername 2019-11-16 04:37:14 +05:30
commit b6be586766
361 changed files with 16088 additions and 11403 deletions

1
.github/FUNDING.yml vendored Normal file
View file

@ -0,0 +1 @@
liberapay: TeamNewPipe

1
.gitignore vendored
View file

@ -7,7 +7,6 @@
/app/app.iml /app/app.iml
/.idea /.idea
/*.iml /*.iml
gradle.properties
*~ *~
.weblate .weblate
*.class *.class

View file

@ -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.

View file

@ -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}"

View file

@ -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;

View file

@ -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;

View file

@ -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"/>

View file

@ -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();
}
}

View file

@ -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);
}
}

View file

@ -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;

View file

@ -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;

View file

@ -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));
} }
} }
} }

View file

@ -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));
}
} }

View file

@ -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 + "]");

View file

@ -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;

View file

@ -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 {

View file

@ -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);
} }
} }
}; };

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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");
}
} }
}; };
} }

View file

@ -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;

View file

@ -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);

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;
}
} }

View file

@ -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;

View file

@ -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;

View file

@ -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);
}
} }

View file

@ -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();
} }
} }

View file

@ -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)));
}
} }

View file

@ -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;

View file

@ -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;

View file

@ -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);
} }

View file

@ -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)}

View file

@ -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();
} }

View file

@ -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);
} }
} }

View file

@ -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();
} }
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////

View file

@ -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

View file

@ -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() {

View file

@ -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);

View file

@ -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(),

View file

@ -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;
}); });
} }

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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());
} }
} }

View file

@ -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;

View file

@ -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

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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();
} }

View file

@ -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) {
}
} }

View file

@ -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;

View file

@ -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;

View file

@ -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 -> {

View file

@ -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;

View file

@ -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 {

View file

@ -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

View file

@ -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;

View file

@ -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();
} }

View file

@ -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;

View file

@ -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;

View file

@ -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.

View file

@ -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;

View file

@ -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;

View file

@ -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());
} }
} }

View file

@ -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) {

View file

@ -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) {
}
} }

View file

@ -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);
} }
} }

View file

@ -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();

View file

@ -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);
}
}
} }

View file

@ -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);

View file

@ -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);
} }
} }

View file

@ -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) {

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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")

View file

@ -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;
} }

View file

@ -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;

View file

@ -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;

View file

@ -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