mirror of
https://github.com/TeamNewPipe/NewPipe
synced 2025-09-25 19:40:53 +02:00
Compare commits
156 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
3750561b4d | ||
![]() |
6b026557d4 | ||
![]() |
1ee137bbda | ||
![]() |
2c88e9d068 | ||
![]() |
4825a0a35f | ||
![]() |
122b0b0de4 | ||
![]() |
7dc85af5fb | ||
![]() |
c7daf32904 | ||
![]() |
4c8dca5300 | ||
![]() |
ef91214085 | ||
![]() |
dc09a4621b | ||
![]() |
2f99a217c3 | ||
![]() |
6992b2c308 | ||
![]() |
0d51eefbb9 | ||
![]() |
aa28a85747 | ||
![]() |
f18ee8e83d | ||
![]() |
fb58967766 | ||
![]() |
c3f1478fde | ||
![]() |
e5c00a7ef4 | ||
![]() |
769791af7a | ||
![]() |
e632fab4d0 | ||
![]() |
6cd25d7e55 | ||
![]() |
c9488eb042 | ||
![]() |
c8516a04dc | ||
![]() |
02d1b98b1c | ||
![]() |
d8236bbedd | ||
![]() |
1de21fb0c2 | ||
![]() |
13cac07b8d | ||
![]() |
bd9dcfb28a | ||
![]() |
d5199eac3e | ||
![]() |
7638d229c0 | ||
![]() |
a641c5bb58 | ||
![]() |
1e0c9f46ad | ||
![]() |
4eb02f584e | ||
![]() |
700c1b4b25 | ||
![]() |
4b4337e078 | ||
![]() |
38ce800685 | ||
![]() |
2310e8c1d6 | ||
![]() |
1b2b3a4f88 | ||
![]() |
d11129a76b | ||
![]() |
02789122a0 | ||
![]() |
676bc02d52 | ||
![]() |
8b807b0706 | ||
![]() |
72dfe974ab | ||
![]() |
316db0e4c6 | ||
![]() |
010c607e40 | ||
![]() |
3e099fb2a3 | ||
![]() |
9c9730b152 | ||
![]() |
9e44053e22 | ||
![]() |
dee32c3dc5 | ||
![]() |
344fbff59a | ||
![]() |
e39a816bdc | ||
![]() |
605b8fac5e | ||
![]() |
dfba10f8ae | ||
![]() |
48a1ab64b0 | ||
![]() |
dd2cde3c1a | ||
![]() |
1b9c2b37c5 | ||
![]() |
eae1f8b597 | ||
![]() |
18ce86c2ed | ||
![]() |
d5f25e05d9 | ||
![]() |
53303ac5d3 | ||
![]() |
90cc8e2144 | ||
![]() |
adf9badbf6 | ||
![]() |
c35fe4f3f1 | ||
![]() |
63291f8101 | ||
![]() |
62efb588ef | ||
![]() |
203ca9afc6 | ||
![]() |
a23f941ac8 | ||
![]() |
b0a10f0542 | ||
![]() |
478ad42977 | ||
![]() |
0764983ac6 | ||
![]() |
2b2f1ee8f5 | ||
![]() |
28f167fd99 | ||
![]() |
272be36dd9 | ||
![]() |
f933db8117 | ||
![]() |
cddb9bccb9 | ||
![]() |
b5ad24eb47 | ||
![]() |
ad8f791f71 | ||
![]() |
2e862b4ccc | ||
![]() |
ecac897e7b | ||
![]() |
702adb53a7 | ||
![]() |
4ea962f523 | ||
![]() |
acaf92d671 | ||
![]() |
c673cb6157 | ||
![]() |
c0f7b123a3 | ||
![]() |
e9e2afa61a | ||
![]() |
403154b2e1 | ||
![]() |
e5fd24b0d1 | ||
![]() |
8dc34274a1 | ||
![]() |
467bd21de2 | ||
![]() |
5c9705d94e | ||
![]() |
85fb5827aa | ||
![]() |
0bcc9bd3ba | ||
![]() |
25e120bec1 | ||
![]() |
7067deb328 | ||
![]() |
f6efd302dc | ||
![]() |
61972141ae | ||
![]() |
af936bc646 | ||
![]() |
d66f933c69 | ||
![]() |
cf81c37683 | ||
![]() |
d2306b0fd7 | ||
![]() |
94dfabf3dc | ||
![]() |
5522dc10b8 | ||
![]() |
0ae04b8ead | ||
![]() |
44cad27d0a | ||
![]() |
5d59025b3c | ||
![]() |
768bb0bbcd | ||
![]() |
ac071b383f | ||
![]() |
e0b1a6b88b | ||
![]() |
ed86b1c572 | ||
![]() |
b6c2bade73 | ||
![]() |
b6b19b474e | ||
![]() |
231b7492fb | ||
![]() |
b4950fcb2e | ||
![]() |
b79ea7b51b | ||
![]() |
28c72e7f63 | ||
![]() |
5fcc3b4dab | ||
![]() |
51837ce36f | ||
![]() |
ddaafb68c8 | ||
![]() |
a744775fe7 | ||
![]() |
50b85a7734 | ||
![]() |
aab09c0c65 | ||
![]() |
3ded6feddb | ||
![]() |
c8802fe5d0 | ||
![]() |
411b3129f9 | ||
![]() |
a55acd38df | ||
![]() |
e7773d8807 | ||
![]() |
7edef8d5a2 | ||
![]() |
03d2ca9f9f | ||
![]() |
2271ea4281 | ||
![]() |
afc8db8f81 | ||
![]() |
4af49ee5a6 | ||
![]() |
d7b29aae5c | ||
![]() |
9f7a8407ca | ||
![]() |
7eb13a9b93 | ||
![]() |
7c9896beaf | ||
![]() |
54d3bff26d | ||
![]() |
a2050a5211 | ||
![]() |
048743c062 | ||
![]() |
e9bd2934c3 | ||
![]() |
50634eb2b3 | ||
![]() |
08489b81fb | ||
![]() |
a2ff770afc | ||
![]() |
658d988254 | ||
![]() |
9d7e9289bb | ||
![]() |
12aac09c7b | ||
![]() |
d7d87691cb | ||
![]() |
731640997e | ||
![]() |
64d7432852 | ||
![]() |
1c9f68bcae | ||
![]() |
4fde62ff89 | ||
![]() |
ceb55d0ede | ||
![]() |
87c958b2e7 | ||
![]() |
d844e0aba6 | ||
![]() |
a953aab9b4 | ||
![]() |
108af48b76 |
16
.gitignore
vendored
16
.gitignore
vendored
@@ -1,15 +1,15 @@
|
|||||||
.gitignore
|
.gradle/
|
||||||
.gradle
|
local.properties
|
||||||
/local.properties
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
/build
|
build/
|
||||||
/captures
|
captures/
|
||||||
/app/app.iml
|
.idea/
|
||||||
/.idea
|
*.iml
|
||||||
/*.iml
|
|
||||||
*~
|
*~
|
||||||
.weblate
|
.weblate
|
||||||
*.class
|
*.class
|
||||||
|
**/debug/
|
||||||
|
**/release/
|
||||||
|
|
||||||
# vscode / eclipse files
|
# vscode / eclipse files
|
||||||
*.classpath
|
*.classpath
|
||||||
|
3
app/.gitignore
vendored
3
app/.gitignore
vendored
@@ -1,3 +0,0 @@
|
|||||||
.gitignore
|
|
||||||
/build
|
|
||||||
*.iml
|
|
@@ -17,8 +17,8 @@ android {
|
|||||||
resValue "string", "app_name", "NewPipe"
|
resValue "string", "app_name", "NewPipe"
|
||||||
minSdkVersion 19
|
minSdkVersion 19
|
||||||
targetSdkVersion 29
|
targetSdkVersion 29
|
||||||
versionCode 977
|
versionCode 980
|
||||||
versionName "0.21.11"
|
versionName "0.21.14"
|
||||||
|
|
||||||
multiDexEnabled true
|
multiDexEnabled true
|
||||||
|
|
||||||
@@ -105,9 +105,9 @@ ext {
|
|||||||
androidxRoomVersion = '2.3.0'
|
androidxRoomVersion = '2.3.0'
|
||||||
|
|
||||||
icepickVersion = '3.2.0'
|
icepickVersion = '3.2.0'
|
||||||
exoPlayerVersion = '2.12.3'
|
exoPlayerVersion = '2.14.2'
|
||||||
googleAutoServiceVersion = '1.0'
|
googleAutoServiceVersion = '1.0'
|
||||||
groupieVersion = '2.9.0'
|
groupieVersion = '2.10.0'
|
||||||
markwonVersion = '4.6.2'
|
markwonVersion = '4.6.2'
|
||||||
|
|
||||||
leakCanaryVersion = '2.5'
|
leakCanaryVersion = '2.5'
|
||||||
@@ -189,7 +189,7 @@ dependencies {
|
|||||||
// name and the commit hash with the commit hash of the (pushed) commit you want to test
|
// name and the commit hash with the commit hash of the (pushed) commit you want to test
|
||||||
// This works thanks to JitPack: https://jitpack.io/
|
// This works thanks to JitPack: https://jitpack.io/
|
||||||
implementation 'com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751'
|
implementation 'com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751'
|
||||||
implementation 'com.github.TeamNewPipe:NewPipeExtractor:v0.21.11'
|
implementation 'com.github.TeamNewPipe:NewPipeExtractor:v0.21.12'
|
||||||
|
|
||||||
/** Checkstyle **/
|
/** Checkstyle **/
|
||||||
checkstyle "com.puppycrawl.tools:checkstyle:${checkstyleVersion}"
|
checkstyle "com.puppycrawl.tools:checkstyle:${checkstyleVersion}"
|
||||||
@@ -208,14 +208,17 @@ dependencies {
|
|||||||
implementation "androidx.lifecycle:lifecycle-livedata:${androidxLifecycleVersion}"
|
implementation "androidx.lifecycle:lifecycle-livedata:${androidxLifecycleVersion}"
|
||||||
implementation "androidx.lifecycle:lifecycle-viewmodel:${androidxLifecycleVersion}"
|
implementation "androidx.lifecycle:lifecycle-viewmodel:${androidxLifecycleVersion}"
|
||||||
implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0'
|
implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0'
|
||||||
implementation 'androidx.media:media:1.3.1'
|
implementation 'androidx.media:media:1.4.3'
|
||||||
implementation 'androidx.multidex:multidex:2.0.1'
|
implementation 'androidx.multidex:multidex:2.0.1'
|
||||||
implementation 'androidx.preference:preference:1.1.1'
|
implementation 'androidx.preference:preference:1.1.1'
|
||||||
implementation 'androidx.recyclerview:recyclerview:1.1.0'
|
implementation 'androidx.recyclerview:recyclerview:1.2.1'
|
||||||
implementation "androidx.room:room-runtime:${androidxRoomVersion}"
|
implementation "androidx.room:room-runtime:${androidxRoomVersion}"
|
||||||
implementation "androidx.room:room-rxjava3:${androidxRoomVersion}"
|
implementation "androidx.room:room-rxjava3:${androidxRoomVersion}"
|
||||||
kapt "androidx.room:room-compiler:${androidxRoomVersion}"
|
kapt "androidx.room:room-compiler:${androidxRoomVersion}"
|
||||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
||||||
|
// Newer version specified to prevent accessibility regressions with RecyclerView, see:
|
||||||
|
// https://developer.android.com/jetpack/androidx/releases/viewpager2#1.1.0-alpha01
|
||||||
|
implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01'
|
||||||
implementation 'androidx.webkit:webkit:1.4.0'
|
implementation 'androidx.webkit:webkit:1.4.0'
|
||||||
implementation 'com.google.android.material:material:1.2.1'
|
implementation 'com.google.android.material:material:1.2.1'
|
||||||
|
|
||||||
|
@@ -256,6 +256,21 @@
|
|||||||
<data android:pathPrefix="/" />
|
<data android:pathPrefix="/" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
|
<!-- y2u.be filter -->
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
|
||||||
|
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
|
<data android:scheme="http" />
|
||||||
|
<data android:scheme="https" />
|
||||||
|
<data android:host="y2u.be" />
|
||||||
|
<data android:pathPrefix="/" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
<!-- Soundcloud filter -->
|
<!-- Soundcloud filter -->
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
@@ -325,8 +340,12 @@
|
|||||||
<data android:host="skeptikon.fr" />
|
<data android:host="skeptikon.fr" />
|
||||||
|
|
||||||
<data android:pathPrefix="/videos/" /> <!-- it contains playlists -->
|
<data android:pathPrefix="/videos/" /> <!-- it contains playlists -->
|
||||||
|
<data android:pathPrefix="/w/" /> <!-- short video URLs -->
|
||||||
|
<data android:pathPrefix="/w/p/" /> <!-- short playlist URLs -->
|
||||||
<data android:pathPrefix="/accounts/" />
|
<data android:pathPrefix="/accounts/" />
|
||||||
|
<data android:pathPrefix="/a/" /> <!-- short account URLs -->
|
||||||
<data android:pathPrefix="/video-channels/" />
|
<data android:pathPrefix="/video-channels/" />
|
||||||
|
<data android:pathPrefix="/c/" /> <!-- short video-channels URLs -->
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
<!-- Bandcamp filter for tracks, albums and playlists -->
|
<!-- Bandcamp filter for tracks, albums and playlists -->
|
||||||
|
@@ -51,8 +51,12 @@ import java.util.ArrayList;
|
|||||||
* <li>{@link #saveState()}</li>
|
* <li>{@link #saveState()}</li>
|
||||||
* <li>{@link #restoreState(Parcelable, ClassLoader)}</li>
|
* <li>{@link #restoreState(Parcelable, ClassLoader)}</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
|
*
|
||||||
|
* @deprecated Switch to {@link androidx.viewpager2.widget.ViewPager2} and use
|
||||||
|
* {@link androidx.viewpager2.adapter.FragmentStateAdapter} instead.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
|
@Deprecated
|
||||||
public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapter {
|
public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapter {
|
||||||
private static final String TAG = "FragmentStatePagerAdapt";
|
private static final String TAG = "FragmentStatePagerAdapt";
|
||||||
private static final boolean DEBUG = false;
|
private static final boolean DEBUG = false;
|
||||||
@@ -86,9 +90,10 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
|
|||||||
private final int mBehavior;
|
private final int mBehavior;
|
||||||
private FragmentTransaction mCurTransaction = null;
|
private FragmentTransaction mCurTransaction = null;
|
||||||
|
|
||||||
private final ArrayList<Fragment.SavedState> mSavedState = new ArrayList<Fragment.SavedState>();
|
private final ArrayList<Fragment.SavedState> mSavedState = new ArrayList<>();
|
||||||
private final ArrayList<Fragment> mFragments = new ArrayList<Fragment>();
|
private final ArrayList<Fragment> mFragments = new ArrayList<>();
|
||||||
private Fragment mCurrentPrimaryItem = null;
|
private Fragment mCurrentPrimaryItem = null;
|
||||||
|
private boolean mExecutingFinishUpdate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor for {@link FragmentStatePagerAdapterMenuWorkaround}
|
* Constructor for {@link FragmentStatePagerAdapterMenuWorkaround}
|
||||||
@@ -208,7 +213,7 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
|
|||||||
mFragments.set(position, null);
|
mFragments.set(position, null);
|
||||||
|
|
||||||
mCurTransaction.remove(fragment);
|
mCurTransaction.remove(fragment);
|
||||||
if (fragment == mCurrentPrimaryItem) {
|
if (fragment.equals(mCurrentPrimaryItem)) {
|
||||||
mCurrentPrimaryItem = null;
|
mCurrentPrimaryItem = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -247,7 +252,19 @@ public abstract class FragmentStatePagerAdapterMenuWorkaround extends PagerAdapt
|
|||||||
@Override
|
@Override
|
||||||
public void finishUpdate(@NonNull final ViewGroup container) {
|
public void finishUpdate(@NonNull final ViewGroup container) {
|
||||||
if (mCurTransaction != null) {
|
if (mCurTransaction != null) {
|
||||||
|
// We drop any transactions that attempt to be committed
|
||||||
|
// from a re-entrant call to finishUpdate(). We need to
|
||||||
|
// do this as a workaround for Robolectric running measure/layout
|
||||||
|
// calls inline rather than allowing them to be posted
|
||||||
|
// as they would on a real device.
|
||||||
|
if (!mExecutingFinishUpdate) {
|
||||||
|
try {
|
||||||
|
mExecutingFinishUpdate = true;
|
||||||
mCurTransaction.commitNowAllowingStateLoss();
|
mCurTransaction.commitNowAllowingStateLoss();
|
||||||
|
} finally {
|
||||||
|
mExecutingFinishUpdate = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
mCurTransaction = null;
|
mCurTransaction = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -43,8 +43,6 @@ import io.reactivex.rxjava3.exceptions.UndeliverableException;
|
|||||||
import io.reactivex.rxjava3.functions.Consumer;
|
import io.reactivex.rxjava3.functions.Consumer;
|
||||||
import io.reactivex.rxjava3.plugins.RxJavaPlugins;
|
import io.reactivex.rxjava3.plugins.RxJavaPlugins;
|
||||||
|
|
||||||
import static org.schabi.newpipe.CheckForNewAppVersion.startNewVersionCheckService;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (C) Hans-Christoph Steiner 2016 <hans@eds.org>
|
* Copyright (C) Hans-Christoph Steiner 2016 <hans@eds.org>
|
||||||
* App.java is part of NewPipe.
|
* App.java is part of NewPipe.
|
||||||
@@ -113,9 +111,6 @@ public class App extends MultiDexApplication {
|
|||||||
&& prefs.getBoolean(getString(R.string.show_image_indicators_key), false));
|
&& prefs.getBoolean(getString(R.string.show_image_indicators_key), false));
|
||||||
|
|
||||||
configureRxJavaErrorHandler();
|
configureRxJavaErrorHandler();
|
||||||
|
|
||||||
// Check for new version
|
|
||||||
startNewVersionCheckService();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -259,4 +254,5 @@ public class App extends MultiDexApplication {
|
|||||||
protected boolean isDisposedRxExceptionsReported() {
|
protected boolean isDisposedRxExceptionsReported() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -21,7 +21,6 @@ public abstract class BaseFragment extends Fragment {
|
|||||||
//These values are used for controlling fragments when they are part of the frontpage
|
//These values are used for controlling fragments when they are part of the frontpage
|
||||||
@State
|
@State
|
||||||
protected boolean useAsFrontPage = false;
|
protected boolean useAsFrontPage = false;
|
||||||
private boolean mIsVisibleToUser = false;
|
|
||||||
|
|
||||||
public void useAsFrontPage(final boolean value) {
|
public void useAsFrontPage(final boolean value) {
|
||||||
useAsFrontPage = value;
|
useAsFrontPage = value;
|
||||||
@@ -85,12 +84,6 @@ public abstract class BaseFragment extends Fragment {
|
|||||||
AppWatcher.INSTANCE.getObjectWatcher().watch(this);
|
AppWatcher.INSTANCE.getObjectWatcher().watch(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setUserVisibleHint(final boolean isVisibleToUser) {
|
|
||||||
super.setUserVisibleHint(isVisibleToUser);
|
|
||||||
mIsVisibleToUser = isVisibleToUser;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Init
|
// Init
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
@@ -109,8 +102,7 @@ public abstract class BaseFragment extends Fragment {
|
|||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "setTitle() called with: title = [" + title + "]");
|
Log.d(TAG, "setTitle() called with: title = [" + title + "]");
|
||||||
}
|
}
|
||||||
if ((!useAsFrontPage || mIsVisibleToUser)
|
if (!useAsFrontPage && activity != null && activity.getSupportActionBar() != null) {
|
||||||
&& (activity != null && activity.getSupportActionBar() != null)) {
|
|
||||||
activity.getSupportActionBar().setDisplayShowTitleEnabled(true);
|
activity.getSupportActionBar().setDisplayShowTitleEnabled(true);
|
||||||
activity.getSupportActionBar().setTitle(title);
|
activity.getSupportActionBar().setTitle(title);
|
||||||
}
|
}
|
||||||
|
@@ -7,7 +7,6 @@ import android.content.Intent;
|
|||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.content.pm.Signature;
|
import android.content.pm.Signature;
|
||||||
import android.net.ConnectivityManager;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
@@ -15,7 +14,6 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.core.app.NotificationCompat;
|
import androidx.core.app.NotificationCompat;
|
||||||
import androidx.core.app.NotificationManagerCompat;
|
import androidx.core.app.NotificationManagerCompat;
|
||||||
import androidx.core.content.ContextCompat;
|
|
||||||
import androidx.core.content.pm.PackageInfoCompat;
|
import androidx.core.content.pm.PackageInfoCompat;
|
||||||
import androidx.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
|
|
||||||
@@ -48,7 +46,8 @@ public final class CheckForNewAppVersion extends IntentService {
|
|||||||
private static final boolean DEBUG = MainActivity.DEBUG;
|
private static final boolean DEBUG = MainActivity.DEBUG;
|
||||||
private static final String TAG = CheckForNewAppVersion.class.getSimpleName();
|
private static final String TAG = CheckForNewAppVersion.class.getSimpleName();
|
||||||
|
|
||||||
private static final String GITHUB_APK_SHA1
|
// Public key of the certificate that is used in NewPipe release versions
|
||||||
|
private static final String RELEASE_CERT_PUBLIC_KEY_SHA1
|
||||||
= "B0:2E:90:7C:1C:D6:FC:57:C3:35:F0:88:D0:8F:50:5F:94:E4:D2:15";
|
= "B0:2E:90:7C:1C:D6:FC:57:C3:35:F0:88:D0:8F:50:5F:94:E4:D2:15";
|
||||||
private static final String NEWPIPE_API_URL = "https://newpipe.net/api/data.json";
|
private static final String NEWPIPE_API_URL = "https://newpipe.net/api/data.json";
|
||||||
|
|
||||||
@@ -129,9 +128,10 @@ public final class CheckForNewAppVersion extends IntentService {
|
|||||||
final String versionName,
|
final String versionName,
|
||||||
final String apkLocationUrl,
|
final String apkLocationUrl,
|
||||||
final int versionCode) {
|
final int versionCode) {
|
||||||
final int notificationId = 2000;
|
if (BuildConfig.VERSION_CODE >= versionCode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (BuildConfig.VERSION_CODE < versionCode) {
|
|
||||||
// A pending intent to open the apk location url in the browser.
|
// A pending intent to open the apk location url in the browser.
|
||||||
final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(apkLocationUrl));
|
final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(apkLocationUrl));
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
@@ -154,19 +154,11 @@ public final class CheckForNewAppVersion extends IntentService {
|
|||||||
|
|
||||||
final NotificationManagerCompat notificationManager
|
final NotificationManagerCompat notificationManager
|
||||||
= NotificationManagerCompat.from(application);
|
= NotificationManagerCompat.from(application);
|
||||||
notificationManager.notify(notificationId, notificationBuilder.build());
|
notificationManager.notify(2000, notificationBuilder.build());
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isConnected(@NonNull final App app) {
|
public static boolean isReleaseApk(@NonNull final App app) {
|
||||||
final ConnectivityManager connectivityManager =
|
return getCertificateSHA1Fingerprint(app).equals(RELEASE_CERT_PUBLIC_KEY_SHA1);
|
||||||
ContextCompat.getSystemService(app, ConnectivityManager.class);
|
|
||||||
return connectivityManager != null && connectivityManager.getActiveNetworkInfo() != null
|
|
||||||
&& connectivityManager.getActiveNetworkInfo().isConnected();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isGithubApk(@NonNull final App app) {
|
|
||||||
return getCertificateSHA1Fingerprint(app).equals(GITHUB_APK_SHA1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkNewVersion() throws IOException, ReCaptchaException {
|
private void checkNewVersion() throws IOException, ReCaptchaException {
|
||||||
@@ -175,9 +167,8 @@ public final class CheckForNewAppVersion extends IntentService {
|
|||||||
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(app);
|
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(app);
|
||||||
final NewVersionManager manager = new NewVersionManager();
|
final NewVersionManager manager = new NewVersionManager();
|
||||||
|
|
||||||
// Check if user has enabled/disabled update checking
|
// Check if the current apk is a github one or not.
|
||||||
// and if the current apk is a github one or not.
|
if (!isReleaseApk(app)) {
|
||||||
if (!prefs.getBoolean(app.getString(R.string.update_app_key), true) || !isGithubApk(app)) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,6 +204,7 @@ public final class CheckForNewAppVersion extends IntentService {
|
|||||||
|
|
||||||
// Parse the json from the response.
|
// Parse the json from the response.
|
||||||
try {
|
try {
|
||||||
|
|
||||||
final JsonObject githubStableObject = JsonParser.object()
|
final JsonObject githubStableObject = JsonParser.object()
|
||||||
.from(response.responseBody()).getObject("flavors")
|
.from(response.responseBody()).getObject("flavors")
|
||||||
.getObject("github").getObject("stable");
|
.getObject("github").getObject("stable");
|
||||||
@@ -235,6 +227,23 @@ public final class CheckForNewAppVersion extends IntentService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start a new service which
|
||||||
|
* checks if all conditions for performing a version check are met,
|
||||||
|
* fetches the API endpoint {@link #NEWPIPE_API_URL} containing info
|
||||||
|
* about the latest NewPipe version
|
||||||
|
* and displays a notification about ana available update.
|
||||||
|
* <br>
|
||||||
|
* Following conditions need to be met, before data is request from the server:
|
||||||
|
* <ul>
|
||||||
|
* <li> The app is signed with the correct signing key (by TeamNewPipe / schabi).
|
||||||
|
* If the signing key differs from the one used upstream, the update cannot be installed.</li>
|
||||||
|
* <li>The user enabled searching for and notifying about updates in the settings.</li>
|
||||||
|
* <li>The app did not recently check for updates.
|
||||||
|
* We do not want to make unnecessary connections and DOS our servers.</li>
|
||||||
|
* </ul>
|
||||||
|
* <b>Must not be executed</b> when the app is in background.
|
||||||
|
*/
|
||||||
public static void startNewVersionCheckService() {
|
public static void startNewVersionCheckService() {
|
||||||
final Intent intent = new Intent(App.getApp().getApplicationContext(),
|
final Intent intent = new Intent(App.getApp().getApplicationContext(),
|
||||||
CheckForNewAppVersion.class);
|
CheckForNewAppVersion.class);
|
||||||
|
@@ -20,6 +20,9 @@
|
|||||||
|
|
||||||
package org.schabi.newpipe;
|
package org.schabi.newpipe;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.CheckForNewAppVersion.startNewVersionCheckService;
|
||||||
|
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
|
||||||
|
|
||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
@@ -91,8 +94,6 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
|
|
||||||
|
|
||||||
public class MainActivity extends AppCompatActivity {
|
public class MainActivity extends AppCompatActivity {
|
||||||
private static final String TAG = "MainActivity";
|
private static final String TAG = "MainActivity";
|
||||||
@SuppressWarnings("ConstantConditions")
|
@SuppressWarnings("ConstantConditions")
|
||||||
@@ -165,7 +166,57 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
openMiniPlayerUponPlayerStarted();
|
openMiniPlayerUponPlayerStarted();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupDrawer() throws Exception {
|
@Override
|
||||||
|
protected void onPostCreate(final Bundle savedInstanceState) {
|
||||||
|
super.onPostCreate(savedInstanceState);
|
||||||
|
|
||||||
|
final App app = App.getApp();
|
||||||
|
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(app);
|
||||||
|
|
||||||
|
if (prefs.getBoolean(app.getString(R.string.update_app_key), true)) {
|
||||||
|
// Start the service which is checking all conditions
|
||||||
|
// and eventually searching for a new version.
|
||||||
|
// The service searching for a new NewPipe version must not be started in background.
|
||||||
|
startNewVersionCheckService();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupDrawer() throws ExtractionException {
|
||||||
|
addDrawerMenuForCurrentService();
|
||||||
|
|
||||||
|
toggle = new ActionBarDrawerToggle(this, mainBinding.getRoot(),
|
||||||
|
toolbarLayoutBinding.toolbar, R.string.drawer_open, R.string.drawer_close);
|
||||||
|
toggle.syncState();
|
||||||
|
mainBinding.getRoot().addDrawerListener(toggle);
|
||||||
|
mainBinding.getRoot().addDrawerListener(new DrawerLayout.SimpleDrawerListener() {
|
||||||
|
private int lastService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDrawerOpened(final View drawerView) {
|
||||||
|
lastService = ServiceHelper.getSelectedServiceId(MainActivity.this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDrawerClosed(final View drawerView) {
|
||||||
|
if (servicesShown) {
|
||||||
|
toggleServices();
|
||||||
|
}
|
||||||
|
if (lastService != ServiceHelper.getSelectedServiceId(MainActivity.this)) {
|
||||||
|
ActivityCompat.recreate(MainActivity.this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
drawerLayoutBinding.navigation.setNavigationItemSelectedListener(this::drawerItemSelected);
|
||||||
|
setupDrawerHeader();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the drawer menu for the current service.
|
||||||
|
*
|
||||||
|
* @throws ExtractionException
|
||||||
|
*/
|
||||||
|
private void addDrawerMenuForCurrentService() throws ExtractionException {
|
||||||
//Tabs
|
//Tabs
|
||||||
final int currentServiceId = ServiceHelper.getSelectedServiceId(this);
|
final int currentServiceId = ServiceHelper.getSelectedServiceId(this);
|
||||||
final StreamingService service = NewPipe.getService(currentServiceId);
|
final StreamingService service = NewPipe.getService(currentServiceId);
|
||||||
@@ -204,32 +255,6 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
drawerLayoutBinding.navigation.getMenu()
|
drawerLayoutBinding.navigation.getMenu()
|
||||||
.add(R.id.menu_options_about_group, ITEM_ID_ABOUT, ORDER, R.string.tab_about)
|
.add(R.id.menu_options_about_group, ITEM_ID_ABOUT, ORDER, R.string.tab_about)
|
||||||
.setIcon(R.drawable.ic_info_outline);
|
.setIcon(R.drawable.ic_info_outline);
|
||||||
|
|
||||||
toggle = new ActionBarDrawerToggle(this, mainBinding.getRoot(),
|
|
||||||
toolbarLayoutBinding.toolbar, R.string.drawer_open, R.string.drawer_close);
|
|
||||||
toggle.syncState();
|
|
||||||
mainBinding.getRoot().addDrawerListener(toggle);
|
|
||||||
mainBinding.getRoot().addDrawerListener(new DrawerLayout.SimpleDrawerListener() {
|
|
||||||
private int lastService;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDrawerOpened(final View drawerView) {
|
|
||||||
lastService = ServiceHelper.getSelectedServiceId(MainActivity.this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDrawerClosed(final View drawerView) {
|
|
||||||
if (servicesShown) {
|
|
||||||
toggleServices();
|
|
||||||
}
|
|
||||||
if (lastService != ServiceHelper.getSelectedServiceId(MainActivity.this)) {
|
|
||||||
ActivityCompat.recreate(MainActivity.this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
drawerLayoutBinding.navigation.setNavigationItemSelectedListener(this::drawerItemSelected);
|
|
||||||
setupDrawerHeader();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean drawerItemSelected(final MenuItem item) {
|
private boolean drawerItemSelected(final MenuItem item) {
|
||||||
@@ -337,11 +362,15 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
drawerLayoutBinding.navigation.getMenu().removeGroup(R.id.menu_tabs_group);
|
drawerLayoutBinding.navigation.getMenu().removeGroup(R.id.menu_tabs_group);
|
||||||
drawerLayoutBinding.navigation.getMenu().removeGroup(R.id.menu_options_about_group);
|
drawerLayoutBinding.navigation.getMenu().removeGroup(R.id.menu_options_about_group);
|
||||||
|
|
||||||
|
// Show up or down arrow
|
||||||
|
drawerHeaderBinding.drawerArrow.setImageResource(
|
||||||
|
servicesShown ? R.drawable.ic_arrow_drop_up : R.drawable.ic_arrow_drop_down);
|
||||||
|
|
||||||
if (servicesShown) {
|
if (servicesShown) {
|
||||||
showServices();
|
showServices();
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
showTabs();
|
addDrawerMenuForCurrentService();
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
ErrorActivity.reportUiErrorInSnackbar(this, "Showing main page tabs", e);
|
ErrorActivity.reportUiErrorInSnackbar(this, "Showing main page tabs", e);
|
||||||
}
|
}
|
||||||
@@ -349,8 +378,6 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void showServices() {
|
private void showServices() {
|
||||||
drawerHeaderBinding.drawerArrow.setImageResource(R.drawable.ic_arrow_drop_up);
|
|
||||||
|
|
||||||
for (final StreamingService s : NewPipe.getServices()) {
|
for (final StreamingService s : NewPipe.getServices()) {
|
||||||
final String title = s.getServiceInfo().getName()
|
final String title = s.getServiceInfo().getName()
|
||||||
+ (ServiceHelper.isBeta(s) ? " (beta)" : "");
|
+ (ServiceHelper.isBeta(s) ? " (beta)" : "");
|
||||||
@@ -414,48 +441,6 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
menuItem.setActionView(spinner);
|
menuItem.setActionView(spinner);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showTabs() throws ExtractionException {
|
|
||||||
drawerHeaderBinding.drawerArrow.setImageResource(R.drawable.ic_arrow_drop_down);
|
|
||||||
|
|
||||||
//Tabs
|
|
||||||
final int currentServiceId = ServiceHelper.getSelectedServiceId(this);
|
|
||||||
final StreamingService service = NewPipe.getService(currentServiceId);
|
|
||||||
|
|
||||||
int kioskId = 0;
|
|
||||||
|
|
||||||
for (final String ks : service.getKioskList().getAvailableKiosks()) {
|
|
||||||
drawerLayoutBinding.navigation.getMenu()
|
|
||||||
.add(R.id.menu_tabs_group, kioskId, ORDER,
|
|
||||||
KioskTranslator.getTranslatedKioskName(ks, this))
|
|
||||||
.setIcon(KioskTranslator.getKioskIcon(ks, this));
|
|
||||||
kioskId++;
|
|
||||||
}
|
|
||||||
|
|
||||||
drawerLayoutBinding.navigation.getMenu()
|
|
||||||
.add(R.id.menu_tabs_group, ITEM_ID_SUBSCRIPTIONS, ORDER, R.string.tab_subscriptions)
|
|
||||||
.setIcon(R.drawable.ic_tv);
|
|
||||||
drawerLayoutBinding.navigation.getMenu()
|
|
||||||
.add(R.id.menu_tabs_group, ITEM_ID_FEED, ORDER, R.string.fragment_feed_title)
|
|
||||||
.setIcon(R.drawable.ic_rss_feed);
|
|
||||||
drawerLayoutBinding.navigation.getMenu()
|
|
||||||
.add(R.id.menu_tabs_group, ITEM_ID_BOOKMARKS, ORDER, R.string.tab_bookmarks)
|
|
||||||
.setIcon(R.drawable.ic_bookmark);
|
|
||||||
drawerLayoutBinding.navigation.getMenu()
|
|
||||||
.add(R.id.menu_tabs_group, ITEM_ID_DOWNLOADS, ORDER, R.string.downloads)
|
|
||||||
.setIcon(R.drawable.ic_file_download);
|
|
||||||
drawerLayoutBinding.navigation.getMenu()
|
|
||||||
.add(R.id.menu_tabs_group, ITEM_ID_HISTORY, ORDER, R.string.action_history)
|
|
||||||
.setIcon(R.drawable.ic_history);
|
|
||||||
|
|
||||||
//Settings and About
|
|
||||||
drawerLayoutBinding.navigation.getMenu()
|
|
||||||
.add(R.id.menu_options_about_group, ITEM_ID_SETTINGS, ORDER, R.string.settings)
|
|
||||||
.setIcon(R.drawable.ic_settings);
|
|
||||||
drawerLayoutBinding.navigation.getMenu()
|
|
||||||
.add(R.id.menu_options_about_group, ITEM_ID_ABOUT, ORDER, R.string.tab_about)
|
|
||||||
.setIcon(R.drawable.ic_info_outline);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onDestroy() {
|
protected void onDestroy() {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
|
@@ -9,8 +9,8 @@ import android.widget.PopupMenu;
|
|||||||
|
|
||||||
import androidx.fragment.app.FragmentManager;
|
import androidx.fragment.app.FragmentManager;
|
||||||
|
|
||||||
import org.schabi.newpipe.local.dialog.PlaylistAppendDialog;
|
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
||||||
import org.schabi.newpipe.local.dialog.PlaylistCreationDialog;
|
import org.schabi.newpipe.local.dialog.PlaylistDialog;
|
||||||
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
||||||
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
||||||
import org.schabi.newpipe.util.NavigationHelper;
|
import org.schabi.newpipe.util.NavigationHelper;
|
||||||
@@ -18,6 +18,9 @@ import org.schabi.newpipe.util.NavigationHelper;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
|
||||||
public final class QueueItemMenuUtil {
|
public final class QueueItemMenuUtil {
|
||||||
|
private QueueItemMenuUtil() {
|
||||||
|
}
|
||||||
|
|
||||||
public static void openPopupMenu(final PlayQueue playQueue,
|
public static void openPopupMenu(final PlayQueue playQueue,
|
||||||
final PlayQueueItem item,
|
final PlayQueueItem item,
|
||||||
final View view,
|
final View view,
|
||||||
@@ -47,13 +50,22 @@ public final class QueueItemMenuUtil {
|
|||||||
false);
|
false);
|
||||||
return true;
|
return true;
|
||||||
case R.id.menu_item_append_playlist:
|
case R.id.menu_item_append_playlist:
|
||||||
final PlaylistAppendDialog d = PlaylistAppendDialog.fromPlayQueueItems(
|
PlaylistDialog.createCorrespondingDialog(
|
||||||
Collections.singletonList(item)
|
context,
|
||||||
|
Collections.singletonList(new StreamEntity(item)),
|
||||||
|
dialog -> dialog.show(
|
||||||
|
fragmentManager,
|
||||||
|
"QueueItemMenuUtil@append_playlist"
|
||||||
|
)
|
||||||
);
|
);
|
||||||
PlaylistAppendDialog.onPlaylistFound(context,
|
|
||||||
() -> d.show(fragmentManager, "QueueItemMenuUtil@append_playlist"),
|
return true;
|
||||||
() -> PlaylistCreationDialog.newInstance(d)
|
case R.id.menu_item_channel_details:
|
||||||
.show(fragmentManager, "QueueItemMenuUtil@append_playlist"));
|
// An intent must be used here.
|
||||||
|
// Opening with FragmentManager transactions is not working,
|
||||||
|
// as PlayQueueActivity doesn't use fragments.
|
||||||
|
NavigationHelper.openChannelFragmentUsingIntent(context, item.getServiceId(),
|
||||||
|
item.getUploaderUrl(), item.getUploader());
|
||||||
return true;
|
return true;
|
||||||
case R.id.menu_item_share:
|
case R.id.menu_item_share:
|
||||||
shareText(context, item.getTitle(), item.getUrl(),
|
shareText(context, item.getTitle(), item.getUrl(),
|
||||||
@@ -65,6 +77,4 @@ public final class QueueItemMenuUtil {
|
|||||||
|
|
||||||
popupMenu.show();
|
popupMenu.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
private QueueItemMenuUtil() { }
|
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,8 @@
|
|||||||
package org.schabi.newpipe;
|
package org.schabi.newpipe;
|
||||||
|
|
||||||
|
import static org.schabi.newpipe.extractor.StreamingService.ServiceInfo.MediaCapability.AUDIO;
|
||||||
|
import static org.schabi.newpipe.extractor.StreamingService.ServiceInfo.MediaCapability.VIDEO;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.app.IntentService;
|
import android.app.IntentService;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
@@ -30,6 +33,7 @@ import androidx.core.widget.TextViewCompat;
|
|||||||
import androidx.fragment.app.FragmentManager;
|
import androidx.fragment.app.FragmentManager;
|
||||||
import androidx.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
||||||
import org.schabi.newpipe.databinding.ListRadioIconItemBinding;
|
import org.schabi.newpipe.databinding.ListRadioIconItemBinding;
|
||||||
import org.schabi.newpipe.databinding.SingleChoiceDialogViewBinding;
|
import org.schabi.newpipe.databinding.SingleChoiceDialogViewBinding;
|
||||||
import org.schabi.newpipe.download.DownloadDialog;
|
import org.schabi.newpipe.download.DownloadDialog;
|
||||||
@@ -56,6 +60,7 @@ import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
|
|||||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||||
import org.schabi.newpipe.extractor.stream.VideoStream;
|
import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||||
import org.schabi.newpipe.ktx.ExceptionUtils;
|
import org.schabi.newpipe.ktx.ExceptionUtils;
|
||||||
|
import org.schabi.newpipe.local.dialog.PlaylistDialog;
|
||||||
import org.schabi.newpipe.player.MainPlayer;
|
import org.schabi.newpipe.player.MainPlayer;
|
||||||
import org.schabi.newpipe.player.helper.PlayerHelper;
|
import org.schabi.newpipe.player.helper.PlayerHelper;
|
||||||
import org.schabi.newpipe.player.helper.PlayerHolder;
|
import org.schabi.newpipe.player.helper.PlayerHolder;
|
||||||
@@ -69,14 +74,15 @@ import org.schabi.newpipe.util.ExtractorHelper;
|
|||||||
import org.schabi.newpipe.util.ListHelper;
|
import org.schabi.newpipe.util.ListHelper;
|
||||||
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.external_communication.ShareUtils;
|
|
||||||
import org.schabi.newpipe.util.ThemeHelper;
|
import org.schabi.newpipe.util.ThemeHelper;
|
||||||
|
import org.schabi.newpipe.util.external_communication.ShareUtils;
|
||||||
import org.schabi.newpipe.util.urlfinder.UrlFinder;
|
import org.schabi.newpipe.util.urlfinder.UrlFinder;
|
||||||
import org.schabi.newpipe.views.FocusOverlayView;
|
import org.schabi.newpipe.views.FocusOverlayView;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import icepick.Icepick;
|
import icepick.Icepick;
|
||||||
@@ -89,9 +95,6 @@ import io.reactivex.rxjava3.disposables.Disposable;
|
|||||||
import io.reactivex.rxjava3.functions.Consumer;
|
import io.reactivex.rxjava3.functions.Consumer;
|
||||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||||
|
|
||||||
import static org.schabi.newpipe.extractor.StreamingService.ServiceInfo.MediaCapability.AUDIO;
|
|
||||||
import static org.schabi.newpipe.extractor.StreamingService.ServiceInfo.MediaCapability.VIDEO;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the url from the intent and open it in the chosen preferred player.
|
* Get the url from the intent and open it in the chosen preferred player.
|
||||||
*/
|
*/
|
||||||
@@ -107,6 +110,7 @@ public class RouterActivity extends AppCompatActivity {
|
|||||||
protected String currentUrl;
|
protected String currentUrl;
|
||||||
private StreamingService currentService;
|
private StreamingService currentService;
|
||||||
private boolean selectionIsDownload = false;
|
private boolean selectionIsDownload = false;
|
||||||
|
private boolean selectionIsAddToPlaylist = false;
|
||||||
private AlertDialog alertDialogChoice = null;
|
private AlertDialog alertDialogChoice = null;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -350,7 +354,7 @@ public class RouterActivity extends AppCompatActivity {
|
|||||||
.setNegativeButton(R.string.just_once, dialogButtonsClickListener)
|
.setNegativeButton(R.string.just_once, dialogButtonsClickListener)
|
||||||
.setPositiveButton(R.string.always, dialogButtonsClickListener)
|
.setPositiveButton(R.string.always, dialogButtonsClickListener)
|
||||||
.setOnDismissListener((dialog) -> {
|
.setOnDismissListener((dialog) -> {
|
||||||
if (!selectionIsDownload) {
|
if (!selectionIsDownload && !selectionIsAddToPlaylist) {
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -446,6 +450,10 @@ public class RouterActivity extends AppCompatActivity {
|
|||||||
final AdapterChoiceItem backgroundPlayer = new AdapterChoiceItem(
|
final AdapterChoiceItem backgroundPlayer = new AdapterChoiceItem(
|
||||||
getString(R.string.background_player_key), getString(R.string.background_player),
|
getString(R.string.background_player_key), getString(R.string.background_player),
|
||||||
R.drawable.ic_headset);
|
R.drawable.ic_headset);
|
||||||
|
final AdapterChoiceItem addToPlaylist = new AdapterChoiceItem(
|
||||||
|
getString(R.string.add_to_playlist_key), getString(R.string.add_to_playlist),
|
||||||
|
R.drawable.ic_add);
|
||||||
|
|
||||||
|
|
||||||
if (linkType == LinkType.STREAM) {
|
if (linkType == LinkType.STREAM) {
|
||||||
if (isExtVideoEnabled) {
|
if (isExtVideoEnabled) {
|
||||||
@@ -482,6 +490,10 @@ public class RouterActivity extends AppCompatActivity {
|
|||||||
getString(R.string.download),
|
getString(R.string.download),
|
||||||
R.drawable.ic_file_download));
|
R.drawable.ic_file_download));
|
||||||
|
|
||||||
|
// Add to playlist is not necessary for CHANNEL and PLAYLIST linkType since those can
|
||||||
|
// not be added to a playlist
|
||||||
|
returnList.add(addToPlaylist);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
returnList.add(showInfo);
|
returnList.add(showInfo);
|
||||||
if (capabilities.contains(VIDEO) && !isExtVideoEnabled) {
|
if (capabilities.contains(VIDEO) && !isExtVideoEnabled) {
|
||||||
@@ -547,6 +559,12 @@ public class RouterActivity extends AppCompatActivity {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (selectedChoiceKey.equals(getString(R.string.add_to_playlist_key))) {
|
||||||
|
selectionIsAddToPlaylist = true;
|
||||||
|
openAddToPlaylistDialog();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// stop and bypass FetcherService if InfoScreen was selected since
|
// stop and bypass FetcherService if InfoScreen was selected since
|
||||||
// StreamDetailFragment can fetch data itself
|
// StreamDetailFragment can fetch data itself
|
||||||
if (selectedChoiceKey.equals(getString(R.string.show_info_key))) {
|
if (selectedChoiceKey.equals(getString(R.string.show_info_key))) {
|
||||||
@@ -572,6 +590,41 @@ public class RouterActivity extends AppCompatActivity {
|
|||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void openAddToPlaylistDialog() {
|
||||||
|
// Getting the stream info usually takes a moment
|
||||||
|
// Notifying the user here to ensure that no confusion arises
|
||||||
|
Toast.makeText(
|
||||||
|
getApplicationContext(),
|
||||||
|
getString(R.string.processing_may_take_a_moment),
|
||||||
|
Toast.LENGTH_SHORT)
|
||||||
|
.show();
|
||||||
|
|
||||||
|
disposables.add(ExtractorHelper.getStreamInfo(currentServiceId, currentUrl, false)
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(
|
||||||
|
info -> PlaylistDialog.createCorrespondingDialog(
|
||||||
|
getThemeWrapperContext(),
|
||||||
|
Collections.singletonList(new StreamEntity(info)),
|
||||||
|
playlistDialog -> {
|
||||||
|
playlistDialog.setOnDismissListener(dialog -> finish());
|
||||||
|
|
||||||
|
playlistDialog.show(
|
||||||
|
this.getSupportFragmentManager(),
|
||||||
|
"addToPlaylistDialog"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
),
|
||||||
|
throwable -> handleError(this, new ErrorInfo(
|
||||||
|
throwable,
|
||||||
|
UserAction.REQUESTED_STREAM,
|
||||||
|
"Tried to add " + currentUrl + " to a playlist",
|
||||||
|
currentService.getServiceId())
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressLint("CheckResult")
|
@SuppressLint("CheckResult")
|
||||||
private void openDownloadDialog() {
|
private void openDownloadDialog() {
|
||||||
disposables.add(ExtractorHelper.getStreamInfo(currentServiceId, currentUrl, true)
|
disposables.add(ExtractorHelper.getStreamInfo(currentServiceId, currentUrl, true)
|
||||||
|
@@ -7,6 +7,7 @@ import androidx.room.Query
|
|||||||
import androidx.room.Transaction
|
import androidx.room.Transaction
|
||||||
import androidx.room.Update
|
import androidx.room.Update
|
||||||
import io.reactivex.rxjava3.core.Flowable
|
import io.reactivex.rxjava3.core.Flowable
|
||||||
|
import io.reactivex.rxjava3.core.Maybe
|
||||||
import org.schabi.newpipe.database.feed.model.FeedEntity
|
import org.schabi.newpipe.database.feed.model.FeedEntity
|
||||||
import org.schabi.newpipe.database.feed.model.FeedLastUpdatedEntity
|
import org.schabi.newpipe.database.feed.model.FeedLastUpdatedEntity
|
||||||
import org.schabi.newpipe.database.stream.StreamWithState
|
import org.schabi.newpipe.database.stream.StreamWithState
|
||||||
@@ -37,7 +38,7 @@ abstract class FeedDAO {
|
|||||||
LIMIT 500
|
LIMIT 500
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
abstract fun getAllStreams(): Flowable<List<StreamWithState>>
|
abstract fun getAllStreams(): Maybe<List<StreamWithState>>
|
||||||
|
|
||||||
@Query(
|
@Query(
|
||||||
"""
|
"""
|
||||||
@@ -62,7 +63,7 @@ abstract class FeedDAO {
|
|||||||
LIMIT 500
|
LIMIT 500
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
abstract fun getAllStreamsForGroup(groupId: Long): Flowable<List<StreamWithState>>
|
abstract fun getAllStreamsForGroup(groupId: Long): Maybe<List<StreamWithState>>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see StreamStateEntity.isFinished()
|
* @see StreamStateEntity.isFinished()
|
||||||
@@ -97,7 +98,7 @@ abstract class FeedDAO {
|
|||||||
LIMIT 500
|
LIMIT 500
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
abstract fun getLiveOrNotPlayedStreams(): Flowable<List<StreamWithState>>
|
abstract fun getLiveOrNotPlayedStreams(): Maybe<List<StreamWithState>>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see StreamStateEntity.isFinished()
|
* @see StreamStateEntity.isFinished()
|
||||||
@@ -137,7 +138,7 @@ abstract class FeedDAO {
|
|||||||
LIMIT 500
|
LIMIT 500
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
abstract fun getLiveOrNotPlayedStreamsForGroup(groupId: Long): Flowable<List<StreamWithState>>
|
abstract fun getLiveOrNotPlayedStreamsForGroup(groupId: Long): Maybe<List<StreamWithState>>
|
||||||
|
|
||||||
@Query(
|
@Query(
|
||||||
"""
|
"""
|
||||||
|
@@ -0,0 +1,103 @@
|
|||||||
|
package org.schabi.newpipe.error;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.ObjectOutputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensures that a Exception is serializable.
|
||||||
|
* This is
|
||||||
|
*/
|
||||||
|
public final class EnsureExceptionSerializable {
|
||||||
|
private static final String TAG = "EnsureExSerializable";
|
||||||
|
|
||||||
|
private EnsureExceptionSerializable() {
|
||||||
|
// No instance
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensures that an exception is serializable.
|
||||||
|
* <br/>
|
||||||
|
* If that is not the case a {@link WorkaroundNotSerializableException} is created.
|
||||||
|
*
|
||||||
|
* @param exception
|
||||||
|
* @return if an exception is not serializable a new {@link WorkaroundNotSerializableException}
|
||||||
|
* otherwise the exception from the parameter
|
||||||
|
*/
|
||||||
|
public static Exception ensureSerializable(@NonNull final Exception exception) {
|
||||||
|
return checkIfSerializable(exception)
|
||||||
|
? exception
|
||||||
|
: WorkaroundNotSerializableException.create(exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean checkIfSerializable(@NonNull final Exception exception) {
|
||||||
|
try {
|
||||||
|
// Check by creating a new ObjectOutputStream which does the serialization
|
||||||
|
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
|
ObjectOutputStream oos = new ObjectOutputStream(bos)
|
||||||
|
) {
|
||||||
|
oos.writeObject(exception);
|
||||||
|
oos.flush();
|
||||||
|
|
||||||
|
bos.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (final IOException ex) {
|
||||||
|
Log.d(TAG, "Exception is not serializable", ex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class WorkaroundNotSerializableException extends Exception {
|
||||||
|
protected WorkaroundNotSerializableException(
|
||||||
|
final Throwable notSerializableException,
|
||||||
|
final Throwable cause) {
|
||||||
|
super(notSerializableException.toString(), cause);
|
||||||
|
setStackTrace(notSerializableException.getStackTrace());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected WorkaroundNotSerializableException(final Throwable notSerializableException) {
|
||||||
|
super(notSerializableException.toString());
|
||||||
|
setStackTrace(notSerializableException.getStackTrace());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static WorkaroundNotSerializableException create(
|
||||||
|
@NonNull final Exception notSerializableException
|
||||||
|
) {
|
||||||
|
// Build a list of the exception + all causes
|
||||||
|
final List<Throwable> throwableList = new ArrayList<>();
|
||||||
|
|
||||||
|
int pos = 0;
|
||||||
|
Throwable throwableToProcess = notSerializableException;
|
||||||
|
|
||||||
|
while (throwableToProcess != null) {
|
||||||
|
throwableList.add(throwableToProcess);
|
||||||
|
|
||||||
|
pos++;
|
||||||
|
throwableToProcess = throwableToProcess.getCause();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reverse list so that it starts with the last one
|
||||||
|
Collections.reverse(throwableList);
|
||||||
|
|
||||||
|
// Build exception stack
|
||||||
|
WorkaroundNotSerializableException cause = null;
|
||||||
|
for (final Throwable t : throwableList) {
|
||||||
|
cause = cause == null
|
||||||
|
? new WorkaroundNotSerializableException(t)
|
||||||
|
: new WorkaroundNotSerializableException(t, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
return cause;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@@ -77,6 +77,16 @@ public class ErrorActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
private ActivityErrorBinding activityErrorBinding;
|
private ActivityErrorBinding activityErrorBinding;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reports a new error by starting a new activity.
|
||||||
|
* <br/>
|
||||||
|
* Ensure that the data within errorInfo is serializable otherwise
|
||||||
|
* an exception will be thrown!<br/>
|
||||||
|
* {@link EnsureExceptionSerializable} might help.
|
||||||
|
*
|
||||||
|
* @param context
|
||||||
|
* @param errorInfo
|
||||||
|
*/
|
||||||
public static void reportError(final Context context, final ErrorInfo errorInfo) {
|
public static void reportError(final Context context, final ErrorInfo errorInfo) {
|
||||||
final Intent intent = new Intent(context, ErrorActivity.class);
|
final Intent intent = new Intent(context, ErrorActivity.class);
|
||||||
intent.putExtra(ERROR_INFO, errorInfo);
|
intent.putExtra(ERROR_INFO, errorInfo);
|
||||||
|
@@ -20,8 +20,8 @@ public class BlankFragment extends BaseFragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setUserVisibleHint(final boolean isVisibleToUser) {
|
public void onResume() {
|
||||||
super.setUserVisibleHint(isVisibleToUser);
|
super.onResume();
|
||||||
setTitle("NewPipe");
|
setTitle("NewPipe");
|
||||||
// leave this inline. Will make it harder for copy cats.
|
// leave this inline. Will make it harder for copy cats.
|
||||||
// If you are a Copy cat FUCK YOU.
|
// If you are a Copy cat FUCK YOU.
|
||||||
|
@@ -52,6 +52,7 @@ import com.squareup.picasso.Callback;
|
|||||||
|
|
||||||
import org.schabi.newpipe.App;
|
import org.schabi.newpipe.App;
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
|
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
||||||
import org.schabi.newpipe.databinding.FragmentVideoDetailBinding;
|
import org.schabi.newpipe.databinding.FragmentVideoDetailBinding;
|
||||||
import org.schabi.newpipe.download.DownloadDialog;
|
import org.schabi.newpipe.download.DownloadDialog;
|
||||||
import org.schabi.newpipe.error.ErrorActivity;
|
import org.schabi.newpipe.error.ErrorActivity;
|
||||||
@@ -73,8 +74,7 @@ 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.RelatedItemsFragment;
|
import org.schabi.newpipe.fragments.list.videos.RelatedItemsFragment;
|
||||||
import org.schabi.newpipe.ktx.AnimationType;
|
import org.schabi.newpipe.ktx.AnimationType;
|
||||||
import org.schabi.newpipe.local.dialog.PlaylistAppendDialog;
|
import org.schabi.newpipe.local.dialog.PlaylistDialog;
|
||||||
import org.schabi.newpipe.local.dialog.PlaylistCreationDialog;
|
|
||||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||||
import org.schabi.newpipe.player.MainPlayer;
|
import org.schabi.newpipe.player.MainPlayer;
|
||||||
import org.schabi.newpipe.player.MainPlayer.PlayerType;
|
import org.schabi.newpipe.player.MainPlayer.PlayerType;
|
||||||
@@ -99,6 +99,7 @@ import org.schabi.newpipe.util.external_communication.KoreUtils;
|
|||||||
import org.schabi.newpipe.util.external_communication.ShareUtils;
|
import org.schabi.newpipe.util.external_communication.ShareUtils;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -444,12 +445,11 @@ public final class VideoDetailFragment
|
|||||||
break;
|
break;
|
||||||
case R.id.detail_controls_playlist_append:
|
case R.id.detail_controls_playlist_append:
|
||||||
if (getFM() != null && currentInfo != null) {
|
if (getFM() != null && currentInfo != null) {
|
||||||
|
|
||||||
final PlaylistAppendDialog d = PlaylistAppendDialog.fromStreamInfo(currentInfo);
|
|
||||||
disposables.add(
|
disposables.add(
|
||||||
PlaylistAppendDialog.onPlaylistFound(getContext(),
|
PlaylistDialog.createCorrespondingDialog(
|
||||||
() -> d.show(getFM(), TAG),
|
getContext(),
|
||||||
() -> PlaylistCreationDialog.newInstance(d).show(getFM(), TAG)
|
Collections.singletonList(new StreamEntity(currentInfo)),
|
||||||
|
dialog -> dialog.show(getFM(), TAG)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -594,6 +594,11 @@ public final class VideoDetailFragment
|
|||||||
// Init
|
// Init
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(@NonNull final View rootView, final Bundle savedInstanceState) {
|
||||||
|
super.onViewCreated(rootView, savedInstanceState);
|
||||||
|
}
|
||||||
|
|
||||||
@Override // called from onViewCreated in {@link BaseFragment#onViewCreated}
|
@Override // called from onViewCreated in {@link BaseFragment#onViewCreated}
|
||||||
protected void initViews(final View rootView, final Bundle savedInstanceState) {
|
protected void initViews(final View rootView, final Bundle savedInstanceState) {
|
||||||
super.initViews(rootView, savedInstanceState);
|
super.initViews(rootView, savedInstanceState);
|
||||||
@@ -604,6 +609,18 @@ public final class VideoDetailFragment
|
|||||||
|
|
||||||
binding.detailThumbnailRootLayout.requestFocus();
|
binding.detailThumbnailRootLayout.requestFocus();
|
||||||
|
|
||||||
|
binding.detailControlsPlayWithKodi.setVisibility(
|
||||||
|
KoreUtils.shouldShowPlayWithKodi(requireContext(), serviceId)
|
||||||
|
? View.VISIBLE
|
||||||
|
: View.GONE
|
||||||
|
);
|
||||||
|
binding.detailControlsCrashThePlayer.setVisibility(
|
||||||
|
DEBUG && PreferenceManager.getDefaultSharedPreferences(getContext())
|
||||||
|
.getBoolean(getString(R.string.show_crash_the_player_key), false)
|
||||||
|
? View.VISIBLE
|
||||||
|
: View.GONE
|
||||||
|
);
|
||||||
|
|
||||||
if (DeviceUtils.isTv(getContext())) {
|
if (DeviceUtils.isTv(getContext())) {
|
||||||
// remove ripple effects from detail controls
|
// remove ripple effects from detail controls
|
||||||
final int transparent = ContextCompat.getColor(requireContext(),
|
final int transparent = ContextCompat.getColor(requireContext(),
|
||||||
@@ -638,8 +655,14 @@ public final class VideoDetailFragment
|
|||||||
binding.detailControlsShare.setOnClickListener(this);
|
binding.detailControlsShare.setOnClickListener(this);
|
||||||
binding.detailControlsOpenInBrowser.setOnClickListener(this);
|
binding.detailControlsOpenInBrowser.setOnClickListener(this);
|
||||||
binding.detailControlsPlayWithKodi.setOnClickListener(this);
|
binding.detailControlsPlayWithKodi.setOnClickListener(this);
|
||||||
binding.detailControlsPlayWithKodi.setVisibility(KoreUtils.shouldShowPlayWithKodi(
|
if (DEBUG) {
|
||||||
requireContext(), serviceId) ? View.VISIBLE : View.GONE);
|
binding.detailControlsCrashThePlayer.setOnClickListener(
|
||||||
|
v -> VideoDetailPlayerCrasher.onCrashThePlayer(
|
||||||
|
this.getContext(),
|
||||||
|
this.player,
|
||||||
|
getLayoutInflater())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
binding.overlayThumbnail.setOnClickListener(this);
|
binding.overlayThumbnail.setOnClickListener(this);
|
||||||
binding.overlayThumbnail.setOnLongClickListener(this);
|
binding.overlayThumbnail.setOnLongClickListener(this);
|
||||||
@@ -1181,7 +1204,7 @@ public final class VideoDetailFragment
|
|||||||
addVideoPlayerView();
|
addVideoPlayerView();
|
||||||
|
|
||||||
final Intent playerIntent = NavigationHelper.getPlayerIntent(requireContext(),
|
final Intent playerIntent = NavigationHelper.getPlayerIntent(requireContext(),
|
||||||
MainPlayer.class, queue, autoPlayEnabled);
|
MainPlayer.class, queue, true, autoPlayEnabled);
|
||||||
ContextCompat.startForegroundService(activity, playerIntent);
|
ContextCompat.startForegroundService(activity, playerIntent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -0,0 +1,159 @@
|
|||||||
|
package org.schabi.newpipe.fragments.detail;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.ContextThemeWrapper;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.RadioButton;
|
||||||
|
import android.widget.RadioGroup;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer2.C;
|
||||||
|
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.R;
|
||||||
|
import org.schabi.newpipe.databinding.ListRadioIconItemBinding;
|
||||||
|
import org.schabi.newpipe.databinding.SingleChoiceDialogViewBinding;
|
||||||
|
import org.schabi.newpipe.player.Player;
|
||||||
|
import org.schabi.newpipe.util.ThemeHelper;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Outsourced logic for crashing the player in the {@link VideoDetailFragment}.
|
||||||
|
*/
|
||||||
|
public final class VideoDetailPlayerCrasher {
|
||||||
|
|
||||||
|
// This has to be <= 23 chars on devices running Android 7 or lower (API <= 25)
|
||||||
|
// or it fails with an IllegalArgumentException
|
||||||
|
// https://stackoverflow.com/a/54744028
|
||||||
|
private static final String TAG = "VideoDetPlayerCrasher";
|
||||||
|
|
||||||
|
private static final Map<String, Supplier<ExoPlaybackException>> AVAILABLE_EXCEPTION_TYPES =
|
||||||
|
getExceptionTypes();
|
||||||
|
|
||||||
|
private VideoDetailPlayerCrasher() {
|
||||||
|
// No impls
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map<String, Supplier<ExoPlaybackException>> getExceptionTypes() {
|
||||||
|
final String defaultMsg = "Dummy";
|
||||||
|
final Map<String, Supplier<ExoPlaybackException>> exceptionTypes = new LinkedHashMap<>();
|
||||||
|
exceptionTypes.put(
|
||||||
|
"Source",
|
||||||
|
() -> ExoPlaybackException.createForSource(
|
||||||
|
new IOException(defaultMsg)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
exceptionTypes.put(
|
||||||
|
"Renderer",
|
||||||
|
() -> ExoPlaybackException.createForRenderer(
|
||||||
|
new Exception(defaultMsg),
|
||||||
|
"Dummy renderer",
|
||||||
|
0,
|
||||||
|
null,
|
||||||
|
C.FORMAT_HANDLED
|
||||||
|
)
|
||||||
|
);
|
||||||
|
exceptionTypes.put(
|
||||||
|
"Unexpected",
|
||||||
|
() -> ExoPlaybackException.createForUnexpected(
|
||||||
|
new RuntimeException(defaultMsg)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
exceptionTypes.put(
|
||||||
|
"Remote",
|
||||||
|
() -> ExoPlaybackException.createForRemote(defaultMsg)
|
||||||
|
);
|
||||||
|
|
||||||
|
return Collections.unmodifiableMap(exceptionTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Context getThemeWrapperContext(final Context context) {
|
||||||
|
return new ContextThemeWrapper(
|
||||||
|
context,
|
||||||
|
ThemeHelper.isLightThemeSelected(context)
|
||||||
|
? R.style.LightTheme
|
||||||
|
: R.style.DarkTheme);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void onCrashThePlayer(
|
||||||
|
@NonNull final Context context,
|
||||||
|
@Nullable final Player player,
|
||||||
|
@NonNull final LayoutInflater layoutInflater
|
||||||
|
) {
|
||||||
|
if (player == null) {
|
||||||
|
Log.d(TAG, "Player is not available");
|
||||||
|
Toast.makeText(context, "Player is not available", Toast.LENGTH_SHORT)
|
||||||
|
.show();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- Build the dialog/UI --
|
||||||
|
|
||||||
|
final Context themeWrapperContext = getThemeWrapperContext(context);
|
||||||
|
|
||||||
|
final LayoutInflater inflater = LayoutInflater.from(themeWrapperContext);
|
||||||
|
final RadioGroup radioGroup = SingleChoiceDialogViewBinding.inflate(layoutInflater)
|
||||||
|
.list;
|
||||||
|
|
||||||
|
final AlertDialog alertDialog = new AlertDialog.Builder(getThemeWrapperContext(context))
|
||||||
|
.setTitle("Choose an exception")
|
||||||
|
.setView(radioGroup)
|
||||||
|
.setCancelable(true)
|
||||||
|
.setNegativeButton(R.string.cancel, null)
|
||||||
|
.create();
|
||||||
|
|
||||||
|
for (final Map.Entry<String, Supplier<ExoPlaybackException>> entry
|
||||||
|
: AVAILABLE_EXCEPTION_TYPES.entrySet()) {
|
||||||
|
final RadioButton radioButton = ListRadioIconItemBinding.inflate(inflater).getRoot();
|
||||||
|
radioButton.setText(entry.getKey());
|
||||||
|
radioButton.setChecked(false);
|
||||||
|
radioButton.setLayoutParams(
|
||||||
|
new RadioGroup.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
|
)
|
||||||
|
);
|
||||||
|
radioButton.setOnClickListener(v -> {
|
||||||
|
tryCrashPlayerWith(player, entry.getValue().get());
|
||||||
|
if (alertDialog != null) {
|
||||||
|
alertDialog.cancel();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
radioGroup.addView(radioButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
alertDialog.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Note that this method does not crash the underlying exoplayer directly (it's not possible).
|
||||||
|
* It simply supplies a Exception to {@link Player#onPlayerError(ExoPlaybackException)}.
|
||||||
|
* @param player
|
||||||
|
* @param exception
|
||||||
|
*/
|
||||||
|
private static void tryCrashPlayerWith(
|
||||||
|
@NonNull final Player player,
|
||||||
|
@NonNull final ExoPlaybackException exception
|
||||||
|
) {
|
||||||
|
Log.d(TAG, "Crashing the player using player.onPlayerError(ex)");
|
||||||
|
try {
|
||||||
|
player.onPlayerError(exception);
|
||||||
|
} catch (final Exception exPlayer) {
|
||||||
|
Log.e(TAG,
|
||||||
|
"Run into an exception while crashing the player:",
|
||||||
|
exPlayer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -143,7 +143,7 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
|
|||||||
final View focusedItem = itemsList.getFocusedChild();
|
final View focusedItem = itemsList.getFocusedChild();
|
||||||
final RecyclerView.ViewHolder itemHolder =
|
final RecyclerView.ViewHolder itemHolder =
|
||||||
itemsList.findContainingViewHolder(focusedItem);
|
itemsList.findContainingViewHolder(focusedItem);
|
||||||
return itemHolder.getAdapterPosition();
|
return itemHolder.getBindingAdapterPosition();
|
||||||
} catch (final NullPointerException e) {
|
} catch (final NullPointerException e) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -378,6 +378,13 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
|
|||||||
if (KoreUtils.shouldShowPlayWithKodi(context, item.getServiceId())) {
|
if (KoreUtils.shouldShowPlayWithKodi(context, item.getServiceId())) {
|
||||||
entries.add(StreamDialogEntry.play_with_kodi);
|
entries.add(StreamDialogEntry.play_with_kodi);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// show "mark as watched" only when watch history is enabled
|
||||||
|
if (StreamDialogEntry.shouldAddMarkAsWatched(item.getStreamType(), context)) {
|
||||||
|
entries.add(
|
||||||
|
StreamDialogEntry.mark_as_watched
|
||||||
|
);
|
||||||
|
}
|
||||||
if (!isNullOrEmpty(item.getUploaderUrl())) {
|
if (!isNullOrEmpty(item.getUploaderUrl())) {
|
||||||
entries.add(StreamDialogEntry.show_channel_details);
|
entries.add(StreamDialogEntry.show_channel_details);
|
||||||
}
|
}
|
||||||
|
@@ -98,11 +98,9 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setUserVisibleHint(final boolean isVisibleToUser) {
|
public void onResume() {
|
||||||
super.setUserVisibleHint(isVisibleToUser);
|
super.onResume();
|
||||||
if (activity != null
|
if (activity != null && useAsFrontPage) {
|
||||||
&& useAsFrontPage
|
|
||||||
&& isVisibleToUser) {
|
|
||||||
setTitle(currentInfo != null ? currentInfo.getName() : name);
|
setTitle(currentInfo != null ? currentInfo.getName() : name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -99,9 +99,12 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setUserVisibleHint(final boolean isVisibleToUser) {
|
public void onResume() {
|
||||||
super.setUserVisibleHint(isVisibleToUser);
|
super.onResume();
|
||||||
if (useAsFrontPage && isVisibleToUser && activity != null) {
|
if (!Localization.getPreferredContentCountry(requireContext()).equals(contentCountry)) {
|
||||||
|
reloadContent();
|
||||||
|
}
|
||||||
|
if (useAsFrontPage && activity != null) {
|
||||||
try {
|
try {
|
||||||
setTitle(kioskTranslatedName);
|
setTitle(kioskTranslatedName);
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
@@ -117,15 +120,6 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
|
|||||||
return inflater.inflate(R.layout.fragment_kiosk, container, false);
|
return inflater.inflate(R.layout.fragment_kiosk, container, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
|
|
||||||
if (!Localization.getPreferredContentCountry(requireContext()).equals(contentCountry)) {
|
|
||||||
reloadContent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Menu
|
// Menu
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
@@ -176,6 +176,12 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
|||||||
entries.add(StreamDialogEntry.play_with_kodi);
|
entries.add(StreamDialogEntry.play_with_kodi);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// show "mark as watched" only when watch history is enabled
|
||||||
|
if (StreamDialogEntry.shouldAddMarkAsWatched(item.getStreamType(), context)) {
|
||||||
|
entries.add(
|
||||||
|
StreamDialogEntry.mark_as_watched
|
||||||
|
);
|
||||||
|
}
|
||||||
if (!isNullOrEmpty(item.getUploaderUrl())) {
|
if (!isNullOrEmpty(item.getUploaderUrl())) {
|
||||||
entries.add(StreamDialogEntry.show_channel_details);
|
entries.add(StreamDialogEntry.show_channel_details);
|
||||||
}
|
}
|
||||||
|
@@ -1088,7 +1088,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
public int getSuggestionMovementFlags(@NonNull final RecyclerView.ViewHolder viewHolder) {
|
public int getSuggestionMovementFlags(@NonNull final RecyclerView.ViewHolder viewHolder) {
|
||||||
final int position = viewHolder.getAdapterPosition();
|
final int position = viewHolder.getBindingAdapterPosition();
|
||||||
if (position == RecyclerView.NO_POSITION) {
|
if (position == RecyclerView.NO_POSITION) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -1099,7 +1099,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void onSuggestionItemSwiped(@NonNull final RecyclerView.ViewHolder viewHolder) {
|
public void onSuggestionItemSwiped(@NonNull final RecyclerView.ViewHolder viewHolder) {
|
||||||
final int position = viewHolder.getAdapterPosition();
|
final int position = viewHolder.getBindingAdapterPosition();
|
||||||
final String query = suggestionListAdapter.getItem(position).query;
|
final String query = suggestionListAdapter.getItem(position).query;
|
||||||
final Disposable onDelete = historyRecordManager.deleteSearchHistory(query)
|
final Disposable onDelete = historyRecordManager.deleteSearchHistory(query)
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
@@ -299,18 +299,36 @@ private fun View.animateLightSlideAndAlpha(enterOrExit: Boolean, duration: Long,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun View.slideUp(duration: Long, delay: Long, @FloatRange(from = 0.0, to = 1.0) translationPercent: Float) {
|
fun View.slideUp(
|
||||||
|
duration: Long,
|
||||||
|
delay: Long,
|
||||||
|
@FloatRange(from = 0.0, to = 1.0) translationPercent: Float
|
||||||
|
) {
|
||||||
|
slideUp(duration, delay, translationPercent, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun View.slideUp(
|
||||||
|
duration: Long,
|
||||||
|
delay: Long = 0L,
|
||||||
|
@FloatRange(from = 0.0, to = 1.0) translationPercent: Float = 1.0F,
|
||||||
|
execOnEnd: Runnable? = null
|
||||||
|
) {
|
||||||
val newTranslationY = (resources.displayMetrics.heightPixels * translationPercent).toInt()
|
val newTranslationY = (resources.displayMetrics.heightPixels * translationPercent).toInt()
|
||||||
animate().setListener(null).cancel()
|
animate().setListener(null).cancel()
|
||||||
alpha = 0f
|
alpha = 0f
|
||||||
translationY = newTranslationY.toFloat()
|
translationY = newTranslationY.toFloat()
|
||||||
visibility = View.VISIBLE
|
isVisible = true
|
||||||
animate()
|
animate()
|
||||||
.alpha(1f)
|
.alpha(1f)
|
||||||
.translationY(0f)
|
.translationY(0f)
|
||||||
.setStartDelay(delay)
|
.setStartDelay(delay)
|
||||||
.setDuration(duration)
|
.setDuration(duration)
|
||||||
.setInterpolator(FastOutSlowInInterpolator())
|
.setInterpolator(FastOutSlowInInterpolator())
|
||||||
|
.setListener(object : AnimatorListenerAdapter() {
|
||||||
|
override fun onAnimationEnd(animation: Animator) {
|
||||||
|
execOnEnd?.run()
|
||||||
|
}
|
||||||
|
})
|
||||||
.start()
|
.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -78,9 +78,9 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setUserVisibleHint(final boolean isVisibleToUser) {
|
public void onResume() {
|
||||||
super.setUserVisibleHint(isVisibleToUser);
|
super.onResume();
|
||||||
if (activity != null && isVisibleToUser) {
|
if (activity != null) {
|
||||||
setTitle(activity.getString(R.string.tab_bookmarks));
|
setTitle(activity.getString(R.string.tab_bookmarks));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
package org.schabi.newpipe.local.dialog;
|
package org.schabi.newpipe.local.dialog;
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -17,20 +16,14 @@ import org.schabi.newpipe.R;
|
|||||||
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.database.stream.model.StreamEntity;
|
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
|
||||||
import org.schabi.newpipe.local.LocalItemListAdapter;
|
import org.schabi.newpipe.local.LocalItemListAdapter;
|
||||||
import org.schabi.newpipe.local.playlist.LocalPlaylistManager;
|
import org.schabi.newpipe.local.playlist.LocalPlaylistManager;
|
||||||
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
|
||||||
import org.schabi.newpipe.util.OnClickGesture;
|
import org.schabi.newpipe.util.OnClickGesture;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||||
import io.reactivex.rxjava3.disposables.CompositeDisposable;
|
import io.reactivex.rxjava3.disposables.CompositeDisposable;
|
||||||
import io.reactivex.rxjava3.disposables.Disposable;
|
|
||||||
|
|
||||||
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();
|
||||||
@@ -40,47 +33,8 @@ public final class PlaylistAppendDialog extends PlaylistDialog {
|
|||||||
|
|
||||||
private final CompositeDisposable playlistDisposables = new CompositeDisposable();
|
private final CompositeDisposable playlistDisposables = new CompositeDisposable();
|
||||||
|
|
||||||
public static Disposable onPlaylistFound(
|
public PlaylistAppendDialog(final List<StreamEntity> streamEntities) {
|
||||||
final Context context, final Runnable onSuccess, final Runnable onFailed
|
super(streamEntities);
|
||||||
) {
|
|
||||||
final LocalPlaylistManager playlistManager =
|
|
||||||
new LocalPlaylistManager(NewPipeDatabase.getInstance(context));
|
|
||||||
|
|
||||||
return playlistManager.hasPlaylists()
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribe(hasPlaylists -> {
|
|
||||||
if (hasPlaylists) {
|
|
||||||
onSuccess.run();
|
|
||||||
} else {
|
|
||||||
onFailed.run();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public static PlaylistAppendDialog fromStreamInfo(final StreamInfo info) {
|
|
||||||
final PlaylistAppendDialog dialog = new PlaylistAppendDialog();
|
|
||||||
dialog.setInfo(Collections.singletonList(new StreamEntity(info)));
|
|
||||||
return dialog;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static PlaylistAppendDialog fromStreamInfoItems(final List<StreamInfoItem> items) {
|
|
||||||
final PlaylistAppendDialog dialog = new PlaylistAppendDialog();
|
|
||||||
final List<StreamEntity> entities = new ArrayList<>(items.size());
|
|
||||||
for (final StreamInfoItem item : items) {
|
|
||||||
entities.add(new StreamEntity(item));
|
|
||||||
}
|
|
||||||
dialog.setInfo(entities);
|
|
||||||
return dialog;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static PlaylistAppendDialog fromPlayQueueItems(final List<PlayQueueItem> items) {
|
|
||||||
final PlaylistAppendDialog dialog = new PlaylistAppendDialog();
|
|
||||||
final List<StreamEntity> entities = new ArrayList<>(items.size());
|
|
||||||
for (final PlayQueueItem item : items) {
|
|
||||||
entities.add(new StreamEntity(item));
|
|
||||||
}
|
|
||||||
dialog.setInfo(entities);
|
|
||||||
return dialog;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
@@ -104,11 +58,15 @@ public final class PlaylistAppendDialog extends PlaylistDialog {
|
|||||||
playlistAdapter.setSelectedListener(new OnClickGesture<LocalItem>() {
|
playlistAdapter.setSelectedListener(new OnClickGesture<LocalItem>() {
|
||||||
@Override
|
@Override
|
||||||
public void selected(final LocalItem selectedItem) {
|
public void selected(final LocalItem selectedItem) {
|
||||||
if (!(selectedItem instanceof PlaylistMetadataEntry) || getStreams() == null) {
|
if (!(selectedItem instanceof PlaylistMetadataEntry)
|
||||||
|
|| getStreamEntities() == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
onPlaylistSelected(playlistManager, (PlaylistMetadataEntry) selectedItem,
|
onPlaylistSelected(
|
||||||
getStreams());
|
playlistManager,
|
||||||
|
(PlaylistMetadataEntry) selectedItem,
|
||||||
|
getStreamEntities()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -146,11 +104,17 @@ public final class PlaylistAppendDialog extends PlaylistDialog {
|
|||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
public void openCreatePlaylistDialog() {
|
public void openCreatePlaylistDialog() {
|
||||||
if (getStreams() == null || !isAdded()) {
|
if (getStreamEntities() == null || !isAdded()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
PlaylistCreationDialog.newInstance(getStreams()).show(getParentFragmentManager(), TAG);
|
final PlaylistCreationDialog playlistCreationDialog =
|
||||||
|
new PlaylistCreationDialog(getStreamEntities());
|
||||||
|
// Move the dismissListener to the new dialog.
|
||||||
|
playlistCreationDialog.setOnDismissListener(this.getOnDismissListener());
|
||||||
|
this.setOnDismissListener(null);
|
||||||
|
|
||||||
|
playlistCreationDialog.show(getParentFragmentManager(), TAG);
|
||||||
requireDialog().dismiss();
|
requireDialog().dismiss();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,7 +129,7 @@ public final class PlaylistAppendDialog extends PlaylistDialog {
|
|||||||
private void onPlaylistSelected(@NonNull final LocalPlaylistManager manager,
|
private void onPlaylistSelected(@NonNull final LocalPlaylistManager manager,
|
||||||
@NonNull final PlaylistMetadataEntry playlist,
|
@NonNull final PlaylistMetadataEntry playlist,
|
||||||
@NonNull final List<StreamEntity> streams) {
|
@NonNull final List<StreamEntity> streams) {
|
||||||
if (getStreams() == null) {
|
if (getStreamEntities() == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -7,29 +7,22 @@ import android.widget.Toast;
|
|||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog.Builder;
|
||||||
|
|
||||||
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.stream.model.StreamEntity;
|
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
||||||
import org.schabi.newpipe.databinding.DialogEditTextBinding;
|
import org.schabi.newpipe.databinding.DialogEditTextBinding;
|
||||||
import org.schabi.newpipe.local.playlist.LocalPlaylistManager;
|
import org.schabi.newpipe.local.playlist.LocalPlaylistManager;
|
||||||
|
import org.schabi.newpipe.util.ThemeHelper;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||||
|
|
||||||
public final class PlaylistCreationDialog extends PlaylistDialog {
|
public final class PlaylistCreationDialog extends PlaylistDialog {
|
||||||
public static PlaylistCreationDialog newInstance(final List<StreamEntity> streams) {
|
public PlaylistCreationDialog(final List<StreamEntity> streamEntities) {
|
||||||
final PlaylistCreationDialog dialog = new PlaylistCreationDialog();
|
super(streamEntities);
|
||||||
dialog.setInfo(streams);
|
|
||||||
return dialog;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static PlaylistCreationDialog newInstance(final PlaylistAppendDialog appendDialog) {
|
|
||||||
final PlaylistCreationDialog dialog = new PlaylistCreationDialog();
|
|
||||||
dialog.setInfo(appendDialog.getStreams());
|
|
||||||
return dialog;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
@@ -39,16 +32,18 @@ public final class PlaylistCreationDialog extends PlaylistDialog {
|
|||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) {
|
public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) {
|
||||||
if (getStreams() == null) {
|
if (getStreamEntities() == null) {
|
||||||
return super.onCreateDialog(savedInstanceState);
|
return super.onCreateDialog(savedInstanceState);
|
||||||
}
|
}
|
||||||
|
|
||||||
final DialogEditTextBinding dialogBinding
|
final DialogEditTextBinding dialogBinding
|
||||||
= DialogEditTextBinding.inflate(getLayoutInflater());
|
= DialogEditTextBinding.inflate(getLayoutInflater());
|
||||||
|
dialogBinding.getRoot().getContext().setTheme(ThemeHelper.getDialogTheme(requireContext()));
|
||||||
dialogBinding.dialogEditText.setHint(R.string.name);
|
dialogBinding.dialogEditText.setHint(R.string.name);
|
||||||
dialogBinding.dialogEditText.setInputType(InputType.TYPE_CLASS_TEXT);
|
dialogBinding.dialogEditText.setInputType(InputType.TYPE_CLASS_TEXT);
|
||||||
|
|
||||||
final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(requireContext())
|
final Builder dialogBuilder = new Builder(requireContext(),
|
||||||
|
ThemeHelper.getDialogTheme(requireContext()))
|
||||||
.setTitle(R.string.create_playlist)
|
.setTitle(R.string.create_playlist)
|
||||||
.setView(dialogBinding.getRoot())
|
.setView(dialogBinding.getRoot())
|
||||||
.setCancelable(true)
|
.setCancelable(true)
|
||||||
@@ -61,11 +56,10 @@ public final class PlaylistCreationDialog extends PlaylistDialog {
|
|||||||
R.string.playlist_creation_success,
|
R.string.playlist_creation_success,
|
||||||
Toast.LENGTH_SHORT);
|
Toast.LENGTH_SHORT);
|
||||||
|
|
||||||
playlistManager.createPlaylist(name, getStreams())
|
playlistManager.createPlaylist(name, getStreamEntities())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(longs -> successToast.show());
|
.subscribe(longs -> successToast.show());
|
||||||
});
|
});
|
||||||
|
|
||||||
return dialogBuilder.create();
|
return dialogBuilder.create();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
package org.schabi.newpipe.local.dialog;
|
package org.schabi.newpipe.local.dialog;
|
||||||
|
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.Window;
|
import android.view.Window;
|
||||||
|
|
||||||
@@ -8,23 +10,29 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.fragment.app.DialogFragment;
|
import androidx.fragment.app.DialogFragment;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.NewPipeDatabase;
|
||||||
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
||||||
|
import org.schabi.newpipe.local.playlist.LocalPlaylistManager;
|
||||||
import org.schabi.newpipe.util.StateSaver;
|
import org.schabi.newpipe.util.StateSaver;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||||
|
import io.reactivex.rxjava3.disposables.Disposable;
|
||||||
|
|
||||||
public abstract class PlaylistDialog extends DialogFragment implements StateSaver.WriteRead {
|
public abstract class PlaylistDialog extends DialogFragment implements StateSaver.WriteRead {
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private DialogInterface.OnDismissListener onDismissListener = null;
|
||||||
|
|
||||||
private List<StreamEntity> streamEntities;
|
private List<StreamEntity> streamEntities;
|
||||||
|
|
||||||
private org.schabi.newpipe.util.SavedState savedState;
|
private org.schabi.newpipe.util.SavedState savedState;
|
||||||
|
|
||||||
protected void setInfo(final List<StreamEntity> entities) {
|
public PlaylistDialog(final List<StreamEntity> streamEntities) {
|
||||||
this.streamEntities = entities;
|
this.streamEntities = streamEntities;
|
||||||
}
|
|
||||||
|
|
||||||
protected List<StreamEntity> getStreams() {
|
|
||||||
return streamEntities;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
@@ -43,6 +51,10 @@ public abstract class PlaylistDialog extends DialogFragment implements StateSave
|
|||||||
StateSaver.onDestroy(savedState);
|
StateSaver.onDestroy(savedState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<StreamEntity> getStreamEntities() {
|
||||||
|
return streamEntities;
|
||||||
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public Dialog onCreateDialog(final Bundle savedInstanceState) {
|
public Dialog onCreateDialog(final Bundle savedInstanceState) {
|
||||||
@@ -55,6 +67,14 @@ public abstract class PlaylistDialog extends DialogFragment implements StateSave
|
|||||||
return dialog;
|
return dialog;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDismiss(@NonNull final DialogInterface dialog) {
|
||||||
|
super.onDismiss(dialog);
|
||||||
|
if (onDismissListener != null) {
|
||||||
|
onDismissListener.onDismiss(dialog);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// State Saving
|
// State Saving
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
@@ -84,4 +104,47 @@ public abstract class PlaylistDialog extends DialogFragment implements StateSave
|
|||||||
savedState, outState, this);
|
savedState, outState, this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
// Getter + Setter
|
||||||
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public DialogInterface.OnDismissListener getOnDismissListener() {
|
||||||
|
return onDismissListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnDismissListener(
|
||||||
|
@Nullable final DialogInterface.OnDismissListener onDismissListener
|
||||||
|
) {
|
||||||
|
this.onDismissListener = onDismissListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
// Dialog creation
|
||||||
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a {@link PlaylistAppendDialog} when playlists exists,
|
||||||
|
* otherwise a {@link PlaylistCreationDialog}.
|
||||||
|
*
|
||||||
|
* @param context context used for accessing the database
|
||||||
|
* @param streamEntities used for crating the dialog
|
||||||
|
* @param onExec execution that should occur after a dialog got created, e.g. showing it
|
||||||
|
* @return Disposable
|
||||||
|
*/
|
||||||
|
public static Disposable createCorrespondingDialog(
|
||||||
|
final Context context,
|
||||||
|
final List<StreamEntity> streamEntities,
|
||||||
|
final Consumer<PlaylistDialog> onExec
|
||||||
|
) {
|
||||||
|
return new LocalPlaylistManager(NewPipeDatabase.getInstance(context))
|
||||||
|
.hasPlaylists()
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(hasPlaylists ->
|
||||||
|
onExec.accept(hasPlaylists
|
||||||
|
? new PlaylistAppendDialog(streamEntities)
|
||||||
|
: new PlaylistCreationDialog(streamEntities))
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -42,7 +42,7 @@ class FeedDatabaseManager(context: Context) {
|
|||||||
fun getStreams(
|
fun getStreams(
|
||||||
groupId: Long = FeedGroupEntity.GROUP_ALL_ID,
|
groupId: Long = FeedGroupEntity.GROUP_ALL_ID,
|
||||||
getPlayedStreams: Boolean = true
|
getPlayedStreams: Boolean = true
|
||||||
): Flowable<List<StreamWithState>> {
|
): Maybe<List<StreamWithState>> {
|
||||||
return when (groupId) {
|
return when (groupId) {
|
||||||
FeedGroupEntity.GROUP_ALL_ID -> {
|
FeedGroupEntity.GROUP_ALL_ID -> {
|
||||||
if (getPlayedStreams) feedTable.getAllStreams()
|
if (getPlayedStreams) feedTable.getAllStreams()
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -1,15 +1,18 @@
|
|||||||
package org.schabi.newpipe.local.feed
|
package org.schabi.newpipe.local.feed
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import androidx.core.content.edit
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||||
import io.reactivex.rxjava3.core.Flowable
|
import io.reactivex.rxjava3.core.Flowable
|
||||||
import io.reactivex.rxjava3.functions.Function4
|
import io.reactivex.rxjava3.functions.Function4
|
||||||
import io.reactivex.rxjava3.processors.BehaviorProcessor
|
import io.reactivex.rxjava3.processors.BehaviorProcessor
|
||||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||||
|
import org.schabi.newpipe.R
|
||||||
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
|
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
|
||||||
import org.schabi.newpipe.database.stream.StreamWithState
|
import org.schabi.newpipe.database.stream.StreamWithState
|
||||||
import org.schabi.newpipe.local.feed.item.StreamItem
|
import org.schabi.newpipe.local.feed.item.StreamItem
|
||||||
@@ -23,19 +26,16 @@ import java.time.OffsetDateTime
|
|||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class FeedViewModel(
|
class FeedViewModel(
|
||||||
applicationContext: Context,
|
private val applicationContext: Context,
|
||||||
groupId: Long = FeedGroupEntity.GROUP_ALL_ID,
|
groupId: Long = FeedGroupEntity.GROUP_ALL_ID,
|
||||||
initialShowPlayedItems: Boolean = true
|
initialShowPlayedItems: Boolean = true
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
private var feedDatabaseManager: FeedDatabaseManager = FeedDatabaseManager(applicationContext)
|
private var feedDatabaseManager: FeedDatabaseManager = FeedDatabaseManager(applicationContext)
|
||||||
|
|
||||||
private val toggleShowPlayedItems = BehaviorProcessor.create<Boolean>()
|
private val toggleShowPlayedItems = BehaviorProcessor.create<Boolean>()
|
||||||
private val streamItems = toggleShowPlayedItems
|
private val toggleShowPlayedItemsFlowable = toggleShowPlayedItems
|
||||||
.startWithItem(initialShowPlayedItems)
|
.startWithItem(initialShowPlayedItems)
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
.switchMap { showPlayedItems ->
|
|
||||||
feedDatabaseManager.getStreams(groupId, showPlayedItems)
|
|
||||||
}
|
|
||||||
|
|
||||||
private val mutableStateLiveData = MutableLiveData<FeedState>()
|
private val mutableStateLiveData = MutableLiveData<FeedState>()
|
||||||
val stateLiveData: LiveData<FeedState> = mutableStateLiveData
|
val stateLiveData: LiveData<FeedState> = mutableStateLiveData
|
||||||
@@ -43,17 +43,28 @@ class FeedViewModel(
|
|||||||
private var combineDisposable = Flowable
|
private var combineDisposable = Flowable
|
||||||
.combineLatest(
|
.combineLatest(
|
||||||
FeedEventManager.events(),
|
FeedEventManager.events(),
|
||||||
streamItems,
|
toggleShowPlayedItemsFlowable,
|
||||||
feedDatabaseManager.notLoadedCount(groupId),
|
feedDatabaseManager.notLoadedCount(groupId),
|
||||||
feedDatabaseManager.oldestSubscriptionUpdate(groupId),
|
feedDatabaseManager.oldestSubscriptionUpdate(groupId),
|
||||||
|
|
||||||
Function4 { t1: FeedEventManager.Event, t2: List<StreamWithState>,
|
Function4 { t1: FeedEventManager.Event, t2: Boolean,
|
||||||
t3: Long, t4: List<OffsetDateTime> ->
|
t3: Long, t4: List<OffsetDateTime> ->
|
||||||
return@Function4 CombineResultHolder(t1, t2, t3, t4.firstOrNull())
|
return@Function4 CombineResultEventHolder(t1, t2, t3, t4.firstOrNull())
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.throttleLatest(DEFAULT_THROTTLE_TIMEOUT, TimeUnit.MILLISECONDS)
|
.throttleLatest(DEFAULT_THROTTLE_TIMEOUT, TimeUnit.MILLISECONDS)
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(Schedulers.io())
|
||||||
|
.map { (event, showPlayedItems, notLoadedCount, oldestUpdate) ->
|
||||||
|
var streamItems = if (event is SuccessResultEvent || event is IdleEvent)
|
||||||
|
feedDatabaseManager
|
||||||
|
.getStreams(groupId, showPlayedItems)
|
||||||
|
.blockingGet(arrayListOf())
|
||||||
|
else
|
||||||
|
arrayListOf()
|
||||||
|
|
||||||
|
CombineResultDataHolder(event, streamItems, notLoadedCount, oldestUpdate)
|
||||||
|
}
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe { (event, listFromDB, notLoadedCount, oldestUpdate) ->
|
.subscribe { (event, listFromDB, notLoadedCount, oldestUpdate) ->
|
||||||
mutableStateLiveData.postValue(
|
mutableStateLiveData.postValue(
|
||||||
@@ -75,20 +86,50 @@ class FeedViewModel(
|
|||||||
combineDisposable.dispose()
|
combineDisposable.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
private data class CombineResultHolder(val t1: FeedEventManager.Event, val t2: List<StreamWithState>, val t3: Long, val t4: OffsetDateTime?)
|
private data class CombineResultEventHolder(
|
||||||
|
val t1: FeedEventManager.Event,
|
||||||
|
val t2: Boolean,
|
||||||
|
val t3: Long,
|
||||||
|
val t4: OffsetDateTime?
|
||||||
|
)
|
||||||
|
|
||||||
|
private data class CombineResultDataHolder(
|
||||||
|
val t1: FeedEventManager.Event,
|
||||||
|
val t2: List<StreamWithState>,
|
||||||
|
val t3: Long,
|
||||||
|
val t4: OffsetDateTime?
|
||||||
|
)
|
||||||
|
|
||||||
fun togglePlayedItems(showPlayedItems: Boolean) {
|
fun togglePlayedItems(showPlayedItems: Boolean) {
|
||||||
toggleShowPlayedItems.onNext(showPlayedItems)
|
toggleShowPlayedItems.onNext(showPlayedItems)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun saveShowPlayedItemsToPreferences(showPlayedItems: Boolean) =
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(applicationContext).edit {
|
||||||
|
this.putBoolean(applicationContext.getString(R.string.feed_show_played_items_key), showPlayedItems)
|
||||||
|
this.apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getShowPlayedItemsFromPreferences() = getShowPlayedItemsFromPreferences(applicationContext)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private fun getShowPlayedItemsFromPreferences(context: Context) =
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
|
.getBoolean(context.getString(R.string.feed_show_played_items_key), true)
|
||||||
|
}
|
||||||
|
|
||||||
class Factory(
|
class Factory(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val groupId: Long = FeedGroupEntity.GROUP_ALL_ID,
|
private val groupId: Long = FeedGroupEntity.GROUP_ALL_ID
|
||||||
private val showPlayedItems: Boolean
|
|
||||||
) : ViewModelProvider.Factory {
|
) : ViewModelProvider.Factory {
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
|
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
|
||||||
return FeedViewModel(context.applicationContext, groupId, showPlayedItems) as T
|
return FeedViewModel(
|
||||||
|
context.applicationContext,
|
||||||
|
groupId,
|
||||||
|
// Read initial value from preferences
|
||||||
|
getShowPlayedItemsFromPreferences(context.applicationContext)
|
||||||
|
) as T
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -19,6 +19,7 @@ import org.schabi.newpipe.util.Localization
|
|||||||
import org.schabi.newpipe.util.PicassoHelper
|
import org.schabi.newpipe.util.PicassoHelper
|
||||||
import org.schabi.newpipe.util.StreamTypeUtil
|
import org.schabi.newpipe.util.StreamTypeUtil
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
import java.util.function.Consumer
|
||||||
|
|
||||||
data class StreamItem(
|
data class StreamItem(
|
||||||
val streamWithState: StreamWithState,
|
val streamWithState: StreamWithState,
|
||||||
@@ -31,6 +32,12 @@ data class StreamItem(
|
|||||||
private val stream: StreamEntity = streamWithState.stream
|
private val stream: StreamEntity = streamWithState.stream
|
||||||
private val stateProgressTime: Long? = streamWithState.stateProgressMillis
|
private val stateProgressTime: Long? = streamWithState.stateProgressMillis
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will be executed at the end of the [StreamItem.bind] (with (ListStreamItemBinding,Int)).
|
||||||
|
* Can be used e.g. for highlighting a item.
|
||||||
|
*/
|
||||||
|
var execBindEnd: Consumer<ListStreamItemBinding>? = null
|
||||||
|
|
||||||
override fun getId(): Long = stream.uid
|
override fun getId(): Long = stream.uid
|
||||||
|
|
||||||
enum class ItemVersion { NORMAL, MINI, GRID }
|
enum class ItemVersion { NORMAL, MINI, GRID }
|
||||||
@@ -97,6 +104,8 @@ data class StreamItem(
|
|||||||
viewBinding.itemAdditionalDetails.text =
|
viewBinding.itemAdditionalDetails.text =
|
||||||
getStreamInfoDetailLine(viewBinding.itemAdditionalDetails.context)
|
getStreamInfoDetailLine(viewBinding.itemAdditionalDetails.context)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
execBindEnd?.accept(viewBinding)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun isLongClickable() = when (stream.streamType) {
|
override fun isLongClickable() = when (stream.streamType) {
|
||||||
|
@@ -120,19 +120,11 @@ public class HistoryRecordManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update the stream progress to the full duration of the video
|
// Update the stream progress to the full duration of the video
|
||||||
final List<StreamStateEntity> states = streamStateTable.getState(streamId)
|
|
||||||
.blockingFirst();
|
|
||||||
if (!states.isEmpty()) {
|
|
||||||
final StreamStateEntity entity = states.get(0);
|
|
||||||
entity.setProgressMillis(duration * 1000);
|
|
||||||
streamStateTable.update(entity);
|
|
||||||
} else {
|
|
||||||
final StreamStateEntity entity = new StreamStateEntity(
|
final StreamStateEntity entity = new StreamStateEntity(
|
||||||
streamId,
|
streamId,
|
||||||
duration * 1000
|
duration * 1000
|
||||||
);
|
);
|
||||||
streamStateTable.insert(entity);
|
streamStateTable.upsert(entity);
|
||||||
}
|
|
||||||
|
|
||||||
// Add a history entry
|
// Add a history entry
|
||||||
final StreamHistoryEntity latestEntry = streamHistoryTable.getLatestEntry(streamId);
|
final StreamHistoryEntity latestEntry = streamHistoryTable.getLatestEntry(streamId);
|
||||||
@@ -334,10 +326,10 @@ public class HistoryRecordManager {
|
|||||||
.getState(entities.get(0).getUid()).blockingFirst();
|
.getState(entities.get(0).getUid()).blockingFirst();
|
||||||
if (states.isEmpty()) {
|
if (states.isEmpty()) {
|
||||||
result.add(null);
|
result.add(null);
|
||||||
continue;
|
} else {
|
||||||
}
|
|
||||||
result.add(states.get(0));
|
result.add(states.get(0));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}).subscribeOn(Schedulers.io());
|
}).subscribeOn(Schedulers.io());
|
||||||
}
|
}
|
||||||
@@ -362,10 +354,10 @@ public class HistoryRecordManager {
|
|||||||
.blockingFirst();
|
.blockingFirst();
|
||||||
if (states.isEmpty()) {
|
if (states.isEmpty()) {
|
||||||
result.add(null);
|
result.add(null);
|
||||||
continue;
|
} else {
|
||||||
}
|
|
||||||
result.add(states.get(0));
|
result.add(states.get(0));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}).subscribeOn(Schedulers.io());
|
}).subscribeOn(Schedulers.io());
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user