Merge pull request #3669 from TeamNewPipe/release_0.19.4
Release 0.19.4
This commit is contained in:
commit
b2a5ff5f9d
|
@ -10,3 +10,11 @@
|
||||||
*~
|
*~
|
||||||
.weblate
|
.weblate
|
||||||
*.class
|
*.class
|
||||||
|
|
||||||
|
# vscode / eclipse files
|
||||||
|
*.classpath
|
||||||
|
*.project
|
||||||
|
*.settings
|
||||||
|
bin/
|
||||||
|
.vscode/
|
||||||
|
*.code-workspace
|
||||||
|
|
|
@ -5,13 +5,13 @@ android:
|
||||||
components:
|
components:
|
||||||
# The BuildTools version used by NewPipe
|
# The BuildTools version used by NewPipe
|
||||||
- tools
|
- tools
|
||||||
- build-tools-28.0.3
|
- build-tools-29.0.3
|
||||||
|
|
||||||
# The SDK version used to compile NewPipe
|
# The SDK version used to compile NewPipe
|
||||||
- android-28
|
- android-29
|
||||||
|
|
||||||
before_install:
|
before_install:
|
||||||
- yes | sdkmanager "platforms;android-28"
|
- yes | sdkmanager "platforms;android-29"
|
||||||
script: ./gradlew -Dorg.gradle.jvmargs=-Xmx1536m assembleDebug lintDebug testDebugUnitTest
|
script: ./gradlew -Dorg.gradle.jvmargs=-Xmx1536m assembleDebug lintDebug testDebugUnitTest
|
||||||
|
|
||||||
licenses:
|
licenses:
|
||||||
|
|
155
app/build.gradle
155
app/build.gradle
|
@ -5,16 +5,16 @@ apply plugin: 'kotlin-kapt'
|
||||||
apply plugin: 'checkstyle'
|
apply plugin: 'checkstyle'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 28
|
compileSdkVersion 29
|
||||||
buildToolsVersion '28.0.3'
|
buildToolsVersion '29.0.3'
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "org.schabi.newpipe"
|
applicationId "org.schabi.newpipe"
|
||||||
resValue "string", "app_name", "NewPipe"
|
resValue "string", "app_name", "NewPipe"
|
||||||
minSdkVersion 19
|
minSdkVersion 19
|
||||||
targetSdkVersion 28
|
targetSdkVersion 29
|
||||||
versionCode 930
|
versionCode 940
|
||||||
versionName "0.19.3"
|
versionName "0.19.4"
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = true
|
||||||
|
@ -66,6 +66,7 @@ android {
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility JavaVersion.VERSION_1_8
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
targetCompatibility JavaVersion.VERSION_1_8
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
encoding 'utf-8'
|
||||||
}
|
}
|
||||||
|
|
||||||
// Required and used only by groupie
|
// Required and used only by groupie
|
||||||
|
@ -79,22 +80,22 @@ android {
|
||||||
}
|
}
|
||||||
|
|
||||||
ext {
|
ext {
|
||||||
androidxLibVersion = '1.0.0'
|
icepickVersion = '3.2.0'
|
||||||
exoPlayerLibVersion = '2.10.8'
|
checkstyleVersion = '8.32'
|
||||||
roomDbLibVersion = '2.1.0'
|
stethoVersion = '1.5.1'
|
||||||
leakCanaryLibVersion = '1.5.4' //1.6.1
|
leakCanaryVersion = '2.2'
|
||||||
okHttpLibVersion = '3.12.6'
|
exoPlayerVersion = '2.11.4'
|
||||||
icepickLibVersion = '3.2.0'
|
androidxLifecycleVersion = '2.2.0'
|
||||||
stethoLibVersion = '1.5.0'
|
androidxRoomVersion = '2.2.5'
|
||||||
markwonVersion = '4.2.1'
|
groupieVersion = '2.8.0'
|
||||||
checkstyleVersion = '8.31'
|
markwonVersion = '4.3.1'
|
||||||
}
|
}
|
||||||
|
|
||||||
checkstyle {
|
checkstyle {
|
||||||
configFile rootProject.file('checkstyle.xml')
|
configFile rootProject.file('checkstyle.xml')
|
||||||
ignoreFailures false
|
ignoreFailures false
|
||||||
showViolations true
|
showViolations true
|
||||||
toolVersion = "${checkstyleVersion}"
|
toolVersion = checkstyleVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
task runCheckstyle(type: Checkstyle) {
|
task runCheckstyle(type: Checkstyle) {
|
||||||
|
@ -116,88 +117,96 @@ task runCheckstyle(type: Checkstyle) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.withType(Checkstyle).each {
|
configurations {
|
||||||
checkstyleTask -> checkstyleTask.doLast {
|
ktlint
|
||||||
reports.all { report ->
|
}
|
||||||
def outputFile = report.destination
|
|
||||||
if (outputFile.exists() && outputFile.text.contains("severity=\"error\"")) {
|
task runKtlint(type: JavaExec) {
|
||||||
throw new GradleException("There were checkstyle errors! For more info check $outputFile")
|
main = "com.pinterest.ktlint.Main"
|
||||||
}
|
classpath = configurations.ktlint
|
||||||
}
|
args "src/**/*.kt"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
task formatKtlint(type: JavaExec) {
|
||||||
|
main = "com.pinterest.ktlint.Main"
|
||||||
|
classpath = configurations.ktlint
|
||||||
|
args "-F", "src/**/*.kt"
|
||||||
}
|
}
|
||||||
|
|
||||||
afterEvaluate {
|
afterEvaluate {
|
||||||
preDebugBuild.dependsOn runCheckstyle
|
preDebugBuild.dependsOn runCheckstyle, runKtlint
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
|
|
||||||
|
implementation "frankiesardo:icepick:${icepickVersion}"
|
||||||
|
kapt "frankiesardo:icepick-processor:${icepickVersion}"
|
||||||
|
|
||||||
debugImplementation "com.puppycrawl.tools:checkstyle:${checkstyleVersion}"
|
debugImplementation "com.puppycrawl.tools:checkstyle:${checkstyleVersion}"
|
||||||
|
ktlint "com.pinterest:ktlint:0.35.0"
|
||||||
|
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
debugImplementation "com.facebook.stetho:stetho:${stethoVersion}"
|
||||||
androidTestImplementation "android.arch.persistence.room:testing:1.1.1"
|
debugImplementation "com.facebook.stetho:stetho-okhttp3:${stethoVersion}"
|
||||||
androidTestImplementation('androidx.test.espresso:espresso-core:3.1.0', {
|
|
||||||
|
debugImplementation "com.squareup.leakcanary:leakcanary-android:${leakCanaryVersion}"
|
||||||
|
implementation "com.squareup.leakcanary:leakcanary-object-watcher-android:${leakCanaryVersion}"
|
||||||
|
|
||||||
|
debugImplementation "androidx.multidex:multidex:2.0.1"
|
||||||
|
|
||||||
|
testImplementation 'junit:junit:4.13'
|
||||||
|
testImplementation 'org.mockito:mockito-core:3.3.3'
|
||||||
|
|
||||||
|
androidTestImplementation "androidx.test.ext:junit:1.1.1"
|
||||||
|
androidTestImplementation "androidx.room:room-testing:${androidxRoomVersion}"
|
||||||
|
androidTestImplementation "androidx.test.espresso:espresso-core:3.2.0", {
|
||||||
exclude module: 'support-annotations'
|
exclude module: 'support-annotations'
|
||||||
})
|
}
|
||||||
|
|
||||||
implementation 'com.github.TeamNewPipe:NewPipeExtractor:fc3a69ed54b393e3e4e3a78ae6e89edc1d47c45a'
|
implementation 'com.github.TeamNewPipe:NewPipeExtractor:98055a3c3c17f2543a63d375a44c1d1f557fa76e'
|
||||||
testImplementation 'junit:junit:4.12'
|
|
||||||
testImplementation 'org.mockito:mockito-core:2.23.0'
|
|
||||||
|
|
||||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
implementation "com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751"
|
||||||
implementation "androidx.legacy:legacy-support-v4:${androidxLibVersion}"
|
implementation "org.jsoup:jsoup:1.13.1"
|
||||||
implementation "com.google.android.material:material:${androidxLibVersion}"
|
|
||||||
implementation "androidx.recyclerview:recyclerview:${androidxLibVersion}"
|
|
||||||
implementation "androidx.legacy:legacy-preference-v14:${androidxLibVersion}"
|
|
||||||
implementation "androidx.cardview:cardview:${androidxLibVersion}"
|
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
|
||||||
|
|
||||||
implementation 'com.xwray:groupie:2.7.0'
|
implementation "com.squareup.okhttp3:okhttp:3.12.11"
|
||||||
implementation 'com.xwray:groupie-kotlin-android-extensions:2.7.0'
|
|
||||||
|
|
||||||
implementation 'androidx.lifecycle:lifecycle-livedata:2.0.0'
|
implementation "com.google.android.exoplayer:exoplayer:${exoPlayerVersion}"
|
||||||
implementation 'androidx.lifecycle:lifecycle-viewmodel:2.0.0'
|
implementation "com.google.android.exoplayer:extension-mediasession:${exoPlayerVersion}"
|
||||||
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
|
|
||||||
|
|
||||||
// Originally in NewPipeExtractor
|
implementation "com.google.android.material:material:1.1.0"
|
||||||
implementation 'com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751'
|
|
||||||
implementation 'org.jsoup:jsoup:1.9.2'
|
|
||||||
|
|
||||||
implementation 'ch.acra:acra:4.9.2' //4.11
|
implementation "androidx.appcompat:appcompat:1.1.0"
|
||||||
|
implementation "androidx.preference:preference:1.1.1"
|
||||||
|
implementation "androidx.recyclerview:recyclerview:1.1.0"
|
||||||
|
implementation "androidx.cardview:cardview:1.0.0"
|
||||||
|
implementation "androidx.constraintlayout:constraintlayout:1.1.3"
|
||||||
|
|
||||||
implementation 'com.nostra13.universalimageloader:universal-image-loader:1.9.5'
|
implementation "androidx.lifecycle:lifecycle-livedata:${androidxLifecycleVersion}"
|
||||||
implementation 'de.hdodenhof:circleimageview:2.2.0'
|
implementation "androidx.lifecycle:lifecycle-viewmodel:${androidxLifecycleVersion}"
|
||||||
implementation 'com.nononsenseapps:filepicker:4.2.1'
|
implementation "androidx.lifecycle:lifecycle-extensions:${androidxLifecycleVersion}"
|
||||||
|
|
||||||
implementation "com.google.android.exoplayer:exoplayer:${exoPlayerLibVersion}"
|
implementation "androidx.room:room-runtime:${androidxRoomVersion}"
|
||||||
implementation "com.google.android.exoplayer:extension-mediasession:${exoPlayerLibVersion}"
|
implementation "androidx.room:room-rxjava2:${androidxRoomVersion}"
|
||||||
|
kapt "androidx.room:room-compiler:${androidxRoomVersion}"
|
||||||
|
|
||||||
debugImplementation "com.facebook.stetho:stetho:${stethoLibVersion}"
|
implementation "com.xwray:groupie:${groupieVersion}"
|
||||||
debugImplementation "com.facebook.stetho:stetho-urlconnection:${stethoLibVersion}"
|
implementation "com.xwray:groupie-kotlin-android-extensions:${groupieVersion}"
|
||||||
debugImplementation 'androidx.multidex:multidex:2.0.1'
|
|
||||||
|
|
||||||
implementation 'io.reactivex.rxjava2:rxjava:2.2.2'
|
implementation "de.hdodenhof:circleimageview:3.1.0"
|
||||||
implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
|
implementation "com.nostra13.universalimageloader:universal-image-loader:1.9.5"
|
||||||
implementation 'com.jakewharton.rxbinding2:rxbinding:2.1.1'
|
|
||||||
implementation 'org.ocpsoft.prettytime:prettytime:4.0.3.Final'
|
|
||||||
|
|
||||||
implementation "androidx.room:room-runtime:${roomDbLibVersion}"
|
|
||||||
implementation "androidx.room:room-rxjava2:${roomDbLibVersion}"
|
|
||||||
kapt "androidx.room:room-compiler:${roomDbLibVersion}"
|
|
||||||
|
|
||||||
implementation "frankiesardo:icepick:${icepickLibVersion}"
|
|
||||||
kapt "frankiesardo:icepick-processor:${icepickLibVersion}"
|
|
||||||
|
|
||||||
debugImplementation "com.squareup.leakcanary:leakcanary-android:${leakCanaryLibVersion}"
|
|
||||||
releaseImplementation "com.squareup.leakcanary:leakcanary-android-no-op:${leakCanaryLibVersion}"
|
|
||||||
|
|
||||||
implementation "com.squareup.okhttp3:okhttp:${okHttpLibVersion}"
|
|
||||||
debugImplementation "com.facebook.stetho:stetho-okhttp3:${stethoLibVersion}"
|
|
||||||
|
|
||||||
implementation "io.noties.markwon:core:${markwonVersion}"
|
implementation "io.noties.markwon:core:${markwonVersion}"
|
||||||
implementation "io.noties.markwon:linkify:${markwonVersion}"
|
implementation "io.noties.markwon:linkify:${markwonVersion}"
|
||||||
|
|
||||||
|
implementation "com.nononsenseapps:filepicker:4.2.1"
|
||||||
|
|
||||||
|
implementation "ch.acra:acra-core:5.5.0"
|
||||||
|
|
||||||
|
implementation "io.reactivex.rxjava2:rxjava:2.2.19"
|
||||||
|
implementation "io.reactivex.rxjava2:rxandroid:2.1.1"
|
||||||
|
implementation "com.jakewharton.rxbinding2:rxbinding:2.2.0"
|
||||||
|
|
||||||
|
implementation "org.ocpsoft.prettytime:prettytime:4.0.5.Final"
|
||||||
}
|
}
|
||||||
|
|
||||||
static String getGitWorkingBranch() {
|
static String getGitWorkingBranch() {
|
||||||
|
|
|
@ -116,4 +116,4 @@ class AppDatabaseTest {
|
||||||
testHelper.closeWhenFinished(database)
|
testHelper.closeWhenFinished(database)
|
||||||
return database
|
return database
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,107 +0,0 @@
|
||||||
package org.schabi.newpipe;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.preference.PreferenceManager;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.multidex.MultiDex;
|
|
||||||
|
|
||||||
import com.facebook.stetho.Stetho;
|
|
||||||
import com.facebook.stetho.okhttp3.StethoInterceptor;
|
|
||||||
import com.squareup.leakcanary.AndroidHeapDumper;
|
|
||||||
import com.squareup.leakcanary.DefaultLeakDirectoryProvider;
|
|
||||||
import com.squareup.leakcanary.HeapDumper;
|
|
||||||
import com.squareup.leakcanary.LeakCanary;
|
|
||||||
import com.squareup.leakcanary.LeakDirectoryProvider;
|
|
||||||
import com.squareup.leakcanary.RefWatcher;
|
|
||||||
|
|
||||||
import org.schabi.newpipe.extractor.downloader.Downloader;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import okhttp3.OkHttpClient;
|
|
||||||
|
|
||||||
public class DebugApp extends App {
|
|
||||||
private static final String TAG = DebugApp.class.toString();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void attachBaseContext(final Context base) {
|
|
||||||
super.attachBaseContext(base);
|
|
||||||
MultiDex.install(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate() {
|
|
||||||
super.onCreate();
|
|
||||||
initStetho();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Downloader getDownloader() {
|
|
||||||
DownloaderImpl downloader = DownloaderImpl.init(new OkHttpClient.Builder()
|
|
||||||
.addNetworkInterceptor(new StethoInterceptor()));
|
|
||||||
setCookiesToDownloader(downloader);
|
|
||||||
return downloader;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initStetho() {
|
|
||||||
// Create an InitializerBuilder
|
|
||||||
Stetho.InitializerBuilder initializerBuilder =
|
|
||||||
Stetho.newInitializerBuilder(this);
|
|
||||||
|
|
||||||
// Enable Chrome DevTools
|
|
||||||
initializerBuilder.enableWebKitInspector(
|
|
||||||
Stetho.defaultInspectorModulesProvider(this)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Enable command line interface
|
|
||||||
initializerBuilder.enableDumpapp(
|
|
||||||
Stetho.defaultDumperPluginsProvider(getApplicationContext())
|
|
||||||
);
|
|
||||||
|
|
||||||
// Use the InitializerBuilder to generate an Initializer
|
|
||||||
Stetho.Initializer initializer = initializerBuilder.build();
|
|
||||||
|
|
||||||
// Initialize Stetho with the Initializer
|
|
||||||
Stetho.initialize(initializer);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean isDisposedRxExceptionsReported() {
|
|
||||||
return PreferenceManager.getDefaultSharedPreferences(this)
|
|
||||||
.getBoolean(getString(R.string.allow_disposed_exceptions_key), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected RefWatcher installLeakCanary() {
|
|
||||||
return LeakCanary.refWatcher(this)
|
|
||||||
.heapDumper(new ToggleableHeapDumper(this))
|
|
||||||
// give each object 10 seconds to be gc'ed, before leak canary gets nosy on it
|
|
||||||
.watchDelay(10, TimeUnit.SECONDS)
|
|
||||||
.buildAndInstall();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class ToggleableHeapDumper implements HeapDumper {
|
|
||||||
private final HeapDumper dumper;
|
|
||||||
private final SharedPreferences preferences;
|
|
||||||
private final String dumpingAllowanceKey;
|
|
||||||
|
|
||||||
ToggleableHeapDumper(@NonNull final Context context) {
|
|
||||||
LeakDirectoryProvider leakDirectoryProvider = new DefaultLeakDirectoryProvider(context);
|
|
||||||
this.dumper = new AndroidHeapDumper(context, leakDirectoryProvider);
|
|
||||||
this.preferences = PreferenceManager.getDefaultSharedPreferences(context);
|
|
||||||
this.dumpingAllowanceKey = context.getString(R.string.allow_heap_dumping_key);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isDumpingAllowed() {
|
|
||||||
return preferences.getBoolean(dumpingAllowanceKey, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public File dumpHeap() {
|
|
||||||
return isDumpingAllowed() ? dumper.dumpHeap() : HeapDumper.RETRY_LATER;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
package org.schabi.newpipe
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.multidex.MultiDex
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
|
import com.facebook.stetho.Stetho
|
||||||
|
import com.facebook.stetho.okhttp3.StethoInterceptor
|
||||||
|
import leakcanary.AppWatcher
|
||||||
|
import leakcanary.LeakCanary
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import org.schabi.newpipe.extractor.downloader.Downloader
|
||||||
|
|
||||||
|
class DebugApp : App() {
|
||||||
|
override fun attachBaseContext(base: Context) {
|
||||||
|
super.attachBaseContext(base)
|
||||||
|
MultiDex.install(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
|
initStetho()
|
||||||
|
|
||||||
|
// Give each object 10 seconds to be GC'ed, before LeakCanary gets nosy on it
|
||||||
|
AppWatcher.config = AppWatcher.config.copy(watchDurationMillis = 10000)
|
||||||
|
LeakCanary.config = LeakCanary.config.copy(dumpHeap = PreferenceManager
|
||||||
|
.getDefaultSharedPreferences(this).getBoolean(getString(
|
||||||
|
R.string.allow_heap_dumping_key), false))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getDownloader(): Downloader {
|
||||||
|
val downloader = DownloaderImpl.init(OkHttpClient.Builder()
|
||||||
|
.addNetworkInterceptor(StethoInterceptor()))
|
||||||
|
setCookiesToDownloader(downloader)
|
||||||
|
return downloader
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initStetho() {
|
||||||
|
// Create an InitializerBuilder
|
||||||
|
val initializerBuilder = Stetho.newInitializerBuilder(this)
|
||||||
|
|
||||||
|
// Enable Chrome DevTools
|
||||||
|
initializerBuilder.enableWebKitInspector(Stetho.defaultInspectorModulesProvider(this))
|
||||||
|
|
||||||
|
// Enable command line interface
|
||||||
|
initializerBuilder.enableDumpapp(
|
||||||
|
Stetho.defaultDumperPluginsProvider(applicationContext))
|
||||||
|
|
||||||
|
// Use the InitializerBuilder to generate an Initializer
|
||||||
|
val initializer = initializerBuilder.build()
|
||||||
|
|
||||||
|
// Initialize Stetho with the Initializer
|
||||||
|
Stetho.initialize(initializer)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isDisposedRxExceptionsReported(): Boolean {
|
||||||
|
return PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
|
.getBoolean(getString(R.string.allow_disposed_exceptions_key), false)
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,19 +9,16 @@ import android.content.SharedPreferences;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
|
|
||||||
import com.nostra13.universalimageloader.cache.memory.impl.LRULimitedMemoryCache;
|
import com.nostra13.universalimageloader.cache.memory.impl.LRULimitedMemoryCache;
|
||||||
import com.nostra13.universalimageloader.core.ImageLoader;
|
import com.nostra13.universalimageloader.core.ImageLoader;
|
||||||
import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
|
import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
|
||||||
import com.squareup.leakcanary.LeakCanary;
|
|
||||||
import com.squareup.leakcanary.RefWatcher;
|
|
||||||
|
|
||||||
import org.acra.ACRA;
|
import org.acra.ACRA;
|
||||||
import org.acra.config.ACRAConfiguration;
|
|
||||||
import org.acra.config.ACRAConfigurationException;
|
import org.acra.config.ACRAConfigurationException;
|
||||||
import org.acra.config.ConfigurationBuilder;
|
import org.acra.config.CoreConfiguration;
|
||||||
|
import org.acra.config.CoreConfigurationBuilder;
|
||||||
import org.acra.sender.ReportSenderFactory;
|
import org.acra.sender.ReportSenderFactory;
|
||||||
import org.schabi.newpipe.extractor.NewPipe;
|
import org.schabi.newpipe.extractor.NewPipe;
|
||||||
import org.schabi.newpipe.extractor.downloader.Downloader;
|
import org.schabi.newpipe.extractor.downloader.Downloader;
|
||||||
|
@ -72,13 +69,6 @@ public class App extends Application {
|
||||||
private static final Class<? extends ReportSenderFactory>[]
|
private static final Class<? extends ReportSenderFactory>[]
|
||||||
REPORT_SENDER_FACTORY_CLASSES = new Class[]{AcraReportSenderFactory.class};
|
REPORT_SENDER_FACTORY_CLASSES = new Class[]{AcraReportSenderFactory.class};
|
||||||
private static App app;
|
private static App app;
|
||||||
private RefWatcher refWatcher;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public static RefWatcher getRefWatcher(final Context context) {
|
|
||||||
final App application = (App) context.getApplicationContext();
|
|
||||||
return application.refWatcher;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static App getApp() {
|
public static App getApp() {
|
||||||
return app;
|
return app;
|
||||||
|
@ -95,13 +85,6 @@ public class App extends Application {
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
super.onCreate();
|
super.onCreate();
|
||||||
|
|
||||||
if (LeakCanary.isInAnalyzerProcess(this)) {
|
|
||||||
// This process is dedicated to LeakCanary for heap analysis.
|
|
||||||
// You should not init your app in this process.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
refWatcher = installLeakCanary();
|
|
||||||
|
|
||||||
app = this;
|
app = this;
|
||||||
|
|
||||||
// Initialize settings first because others inits can use its values
|
// Initialize settings first because others inits can use its values
|
||||||
|
@ -136,7 +119,8 @@ public class App extends Application {
|
||||||
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(
|
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(
|
||||||
getApplicationContext());
|
getApplicationContext());
|
||||||
final String key = getApplicationContext().getString(R.string.recaptcha_cookies_key);
|
final String key = getApplicationContext().getString(R.string.recaptcha_cookies_key);
|
||||||
downloader.setCookies(prefs.getString(key, ""));
|
downloader.setCookie(ReCaptchaActivity.RECAPTCHA_COOKIES_KEY, prefs.getString(key, ""));
|
||||||
|
downloader.updateYoutubeRestrictedModeCookies(getApplicationContext());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void configureRxJavaErrorHandler() {
|
private void configureRxJavaErrorHandler() {
|
||||||
|
@ -218,7 +202,7 @@ public class App extends Application {
|
||||||
|
|
||||||
private void initACRA() {
|
private void initACRA() {
|
||||||
try {
|
try {
|
||||||
final ACRAConfiguration acraConfig = new ConfigurationBuilder(this)
|
final CoreConfiguration acraConfig = new CoreConfigurationBuilder(this)
|
||||||
.setReportSenderFactoryClasses(REPORT_SENDER_FACTORY_CLASSES)
|
.setReportSenderFactoryClasses(REPORT_SENDER_FACTORY_CLASSES)
|
||||||
.setBuildConfigClass(BuildConfig.class)
|
.setBuildConfigClass(BuildConfig.class)
|
||||||
.build();
|
.build();
|
||||||
|
@ -279,10 +263,6 @@ public class App extends Application {
|
||||||
appUpdateNotificationManager.createNotificationChannel(appUpdateChannel);
|
appUpdateNotificationManager.createNotificationChannel(appUpdateChannel);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected RefWatcher installLeakCanary() {
|
|
||||||
return RefWatcher.DISABLED;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean isDisposedRxExceptionsReported() {
|
protected boolean isDisposedRxExceptionsReported() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,10 +11,10 @@ import androidx.fragment.app.Fragment;
|
||||||
import androidx.fragment.app.FragmentManager;
|
import androidx.fragment.app.FragmentManager;
|
||||||
|
|
||||||
import com.nostra13.universalimageloader.core.ImageLoader;
|
import com.nostra13.universalimageloader.core.ImageLoader;
|
||||||
import com.squareup.leakcanary.RefWatcher;
|
|
||||||
|
|
||||||
import icepick.Icepick;
|
import icepick.Icepick;
|
||||||
import icepick.State;
|
import icepick.State;
|
||||||
|
import leakcanary.AppWatcher;
|
||||||
|
|
||||||
public abstract class BaseFragment extends Fragment {
|
public abstract class BaseFragment extends Fragment {
|
||||||
public static final ImageLoader IMAGE_LOADER = ImageLoader.getInstance();
|
public static final ImageLoader IMAGE_LOADER = ImageLoader.getInstance();
|
||||||
|
@ -78,16 +78,14 @@ public abstract class BaseFragment extends Fragment {
|
||||||
Icepick.saveInstanceState(this, outState);
|
Icepick.saveInstanceState(this, outState);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void onRestoreInstanceState(@NonNull final Bundle savedInstanceState) { }
|
protected void onRestoreInstanceState(@NonNull final Bundle savedInstanceState) {
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
|
|
||||||
RefWatcher refWatcher = App.getRefWatcher(getActivity());
|
AppWatcher.INSTANCE.getObjectWatcher().watch(this);
|
||||||
if (refWatcher != null) {
|
|
||||||
refWatcher.watch(this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -100,9 +98,11 @@ public abstract class BaseFragment extends Fragment {
|
||||||
// Init
|
// Init
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
protected void initViews(final View rootView, final Bundle savedInstanceState) { }
|
protected void initViews(final View rootView, final Bundle savedInstanceState) {
|
||||||
|
}
|
||||||
|
|
||||||
protected void initListeners() { }
|
protected void initListeners() {
|
||||||
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Utils
|
// Utils
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
package org.schabi.newpipe;
|
package org.schabi.newpipe;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.text.TextUtils;
|
import android.preference.PreferenceManager;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
@ -10,6 +11,8 @@ import org.schabi.newpipe.extractor.downloader.Downloader;
|
||||||
import org.schabi.newpipe.extractor.downloader.Request;
|
import org.schabi.newpipe.extractor.downloader.Request;
|
||||||
import org.schabi.newpipe.extractor.downloader.Response;
|
import org.schabi.newpipe.extractor.downloader.Response;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
|
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
|
||||||
|
import org.schabi.newpipe.util.CookieUtils;
|
||||||
|
import org.schabi.newpipe.util.InfoCache;
|
||||||
import org.schabi.newpipe.util.TLSSocketFactoryCompat;
|
import org.schabi.newpipe.util.TLSSocketFactoryCompat;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -20,6 +23,7 @@ import java.security.KeyStoreException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
@ -40,9 +44,13 @@ import static org.schabi.newpipe.MainActivity.DEBUG;
|
||||||
public final class DownloaderImpl extends Downloader {
|
public final class DownloaderImpl extends Downloader {
|
||||||
public static final String USER_AGENT
|
public static final String USER_AGENT
|
||||||
= "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:68.0) Gecko/20100101 Firefox/68.0";
|
= "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:68.0) Gecko/20100101 Firefox/68.0";
|
||||||
|
public static final String YOUTUBE_RESTRICTED_MODE_COOKIE_KEY
|
||||||
|
= "youtube_restricted_mode_key";
|
||||||
|
public static final String YOUTUBE_RESTRICTED_MODE_COOKIE = "PREF=f2=8000000";
|
||||||
|
public static final String YOUTUBE_DOMAIN = "youtube.com";
|
||||||
|
|
||||||
private static DownloaderImpl instance;
|
private static DownloaderImpl instance;
|
||||||
private String mCookies;
|
private Map<String, String> mCookies;
|
||||||
private OkHttpClient client;
|
private OkHttpClient client;
|
||||||
|
|
||||||
private DownloaderImpl(final OkHttpClient.Builder builder) {
|
private DownloaderImpl(final OkHttpClient.Builder builder) {
|
||||||
|
@ -54,6 +62,7 @@ public final class DownloaderImpl extends Downloader {
|
||||||
// .cache(new Cache(new File(context.getExternalCacheDir(), "okhttp"),
|
// .cache(new Cache(new File(context.getExternalCacheDir(), "okhttp"),
|
||||||
// 16 * 1024 * 1024))
|
// 16 * 1024 * 1024))
|
||||||
.build();
|
.build();
|
||||||
|
this.mCookies = new HashMap<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -121,12 +130,50 @@ public final class DownloaderImpl extends Downloader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getCookies() {
|
public String getCookies(final String url) {
|
||||||
return mCookies;
|
List<String> resultCookies = new ArrayList<>();
|
||||||
|
if (url.contains(YOUTUBE_DOMAIN)) {
|
||||||
|
String youtubeCookie = getCookie(YOUTUBE_RESTRICTED_MODE_COOKIE_KEY);
|
||||||
|
if (youtubeCookie != null) {
|
||||||
|
resultCookies.add(youtubeCookie);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Recaptcha cookie is always added TODO: not sure if this is necessary
|
||||||
|
String recaptchaCookie = getCookie(ReCaptchaActivity.RECAPTCHA_COOKIES_KEY);
|
||||||
|
if (recaptchaCookie != null) {
|
||||||
|
resultCookies.add(recaptchaCookie);
|
||||||
|
}
|
||||||
|
return CookieUtils.concatCookies(resultCookies);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCookies(final String cookies) {
|
public String getCookie(final String key) {
|
||||||
mCookies = cookies;
|
return mCookies.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCookie(final String key, final String cookie) {
|
||||||
|
mCookies.put(key, cookie);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeCookie(final String key) {
|
||||||
|
mCookies.remove(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateYoutubeRestrictedModeCookies(final Context context) {
|
||||||
|
String restrictedModeEnabledKey =
|
||||||
|
context.getString(R.string.youtube_restricted_mode_enabled);
|
||||||
|
boolean restrictedModeEnabled = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
|
.getBoolean(restrictedModeEnabledKey, false);
|
||||||
|
updateYoutubeRestrictedModeCookies(restrictedModeEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateYoutubeRestrictedModeCookies(final boolean youtubeRestrictedModeEnabled) {
|
||||||
|
if (youtubeRestrictedModeEnabled) {
|
||||||
|
setCookie(YOUTUBE_RESTRICTED_MODE_COOKIE_KEY,
|
||||||
|
YOUTUBE_RESTRICTED_MODE_COOKIE);
|
||||||
|
} else {
|
||||||
|
removeCookie(YOUTUBE_RESTRICTED_MODE_COOKIE_KEY);
|
||||||
|
}
|
||||||
|
InfoCache.getInstance().clearCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -152,8 +199,9 @@ public final class DownloaderImpl extends Downloader {
|
||||||
.method("GET", null).url(siteUrl)
|
.method("GET", null).url(siteUrl)
|
||||||
.addHeader("User-Agent", USER_AGENT);
|
.addHeader("User-Agent", USER_AGENT);
|
||||||
|
|
||||||
if (!TextUtils.isEmpty(mCookies)) {
|
String cookies = getCookies(siteUrl);
|
||||||
requestBuilder.addHeader("Cookie", mCookies);
|
if (!cookies.isEmpty()) {
|
||||||
|
requestBuilder.addHeader("Cookie", cookies);
|
||||||
}
|
}
|
||||||
|
|
||||||
final okhttp3.Request request = requestBuilder.build();
|
final okhttp3.Request request = requestBuilder.build();
|
||||||
|
@ -192,8 +240,9 @@ public final class DownloaderImpl extends Downloader {
|
||||||
.method(httpMethod, requestBody).url(url)
|
.method(httpMethod, requestBody).url(url)
|
||||||
.addHeader("User-Agent", USER_AGENT);
|
.addHeader("User-Agent", USER_AGENT);
|
||||||
|
|
||||||
if (!TextUtils.isEmpty(mCookies)) {
|
String cookies = getCookies(url);
|
||||||
requestBuilder.addHeader("Cookie", mCookies);
|
if (!cookies.isEmpty()) {
|
||||||
|
requestBuilder.addHeader("Cookie", cookies);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Map.Entry<String, List<String>> pair : headers.entrySet()) {
|
for (Map.Entry<String, List<String>> pair : headers.entrySet()) {
|
||||||
|
|
|
@ -165,8 +165,7 @@ public class MainActivity extends AppCompatActivity {
|
||||||
drawerItems.getMenu()
|
drawerItems.getMenu()
|
||||||
.add(R.id.menu_tabs_group, kioskId, 0, KioskTranslator
|
.add(R.id.menu_tabs_group, kioskId, 0, KioskTranslator
|
||||||
.getTranslatedKioskName(ks, this))
|
.getTranslatedKioskName(ks, this))
|
||||||
.setIcon(KioskTranslator.getKioskIcons(ks, this));
|
.setIcon(KioskTranslator.getKioskIcon(ks, this));
|
||||||
kioskId++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
drawerItems.getMenu()
|
drawerItems.getMenu()
|
||||||
|
@ -175,24 +174,24 @@ public class MainActivity extends AppCompatActivity {
|
||||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_channel));
|
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_channel));
|
||||||
drawerItems.getMenu()
|
drawerItems.getMenu()
|
||||||
.add(R.id.menu_tabs_group, ITEM_ID_FEED, ORDER, R.string.fragment_feed_title)
|
.add(R.id.menu_tabs_group, ITEM_ID_FEED, ORDER, R.string.fragment_feed_title)
|
||||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.rss));
|
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_rss));
|
||||||
drawerItems.getMenu()
|
drawerItems.getMenu()
|
||||||
.add(R.id.menu_tabs_group, ITEM_ID_BOOKMARKS, ORDER, R.string.tab_bookmarks)
|
.add(R.id.menu_tabs_group, ITEM_ID_BOOKMARKS, ORDER, R.string.tab_bookmarks)
|
||||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_bookmark));
|
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_bookmark));
|
||||||
drawerItems.getMenu()
|
drawerItems.getMenu()
|
||||||
.add(R.id.menu_tabs_group, ITEM_ID_DOWNLOADS, ORDER, R.string.downloads)
|
.add(R.id.menu_tabs_group, ITEM_ID_DOWNLOADS, ORDER, R.string.downloads)
|
||||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.download));
|
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_file_download));
|
||||||
drawerItems.getMenu()
|
drawerItems.getMenu()
|
||||||
.add(R.id.menu_tabs_group, ITEM_ID_HISTORY, ORDER, R.string.action_history)
|
.add(R.id.menu_tabs_group, ITEM_ID_HISTORY, ORDER, R.string.action_history)
|
||||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.history));
|
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_history));
|
||||||
|
|
||||||
//Settings and About
|
//Settings and About
|
||||||
drawerItems.getMenu()
|
drawerItems.getMenu()
|
||||||
.add(R.id.menu_options_about_group, ITEM_ID_SETTINGS, ORDER, R.string.settings)
|
.add(R.id.menu_options_about_group, ITEM_ID_SETTINGS, ORDER, R.string.settings)
|
||||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.settings));
|
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_settings));
|
||||||
drawerItems.getMenu()
|
drawerItems.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(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.info));
|
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_info_outline));
|
||||||
|
|
||||||
toggle = new ActionBarDrawerToggle(this, drawer, toolbar, R.string.drawer_open,
|
toggle = new ActionBarDrawerToggle(this, drawer, toolbar, R.string.drawer_open,
|
||||||
R.string.drawer_close);
|
R.string.drawer_close);
|
||||||
|
@ -420,7 +419,7 @@ public class MainActivity extends AppCompatActivity {
|
||||||
drawerItems.getMenu()
|
drawerItems.getMenu()
|
||||||
.add(R.id.menu_tabs_group, kioskId, ORDER,
|
.add(R.id.menu_tabs_group, kioskId, ORDER,
|
||||||
KioskTranslator.getTranslatedKioskName(ks, this))
|
KioskTranslator.getTranslatedKioskName(ks, this))
|
||||||
.setIcon(KioskTranslator.getKioskIcons(ks, this));
|
.setIcon(KioskTranslator.getKioskIcon(ks, this));
|
||||||
kioskId++;
|
kioskId++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -429,24 +428,24 @@ public class MainActivity extends AppCompatActivity {
|
||||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_channel));
|
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_channel));
|
||||||
drawerItems.getMenu()
|
drawerItems.getMenu()
|
||||||
.add(R.id.menu_tabs_group, ITEM_ID_FEED, ORDER, R.string.fragment_feed_title)
|
.add(R.id.menu_tabs_group, ITEM_ID_FEED, ORDER, R.string.fragment_feed_title)
|
||||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.rss));
|
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_rss));
|
||||||
drawerItems.getMenu()
|
drawerItems.getMenu()
|
||||||
.add(R.id.menu_tabs_group, ITEM_ID_BOOKMARKS, ORDER, R.string.tab_bookmarks)
|
.add(R.id.menu_tabs_group, ITEM_ID_BOOKMARKS, ORDER, R.string.tab_bookmarks)
|
||||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_bookmark));
|
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_bookmark));
|
||||||
drawerItems.getMenu()
|
drawerItems.getMenu()
|
||||||
.add(R.id.menu_tabs_group, ITEM_ID_DOWNLOADS, ORDER, R.string.downloads)
|
.add(R.id.menu_tabs_group, ITEM_ID_DOWNLOADS, ORDER, R.string.downloads)
|
||||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.download));
|
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_file_download));
|
||||||
drawerItems.getMenu()
|
drawerItems.getMenu()
|
||||||
.add(R.id.menu_tabs_group, ITEM_ID_HISTORY, ORDER, R.string.action_history)
|
.add(R.id.menu_tabs_group, ITEM_ID_HISTORY, ORDER, R.string.action_history)
|
||||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.history));
|
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_history));
|
||||||
|
|
||||||
//Settings and About
|
//Settings and About
|
||||||
drawerItems.getMenu()
|
drawerItems.getMenu()
|
||||||
.add(R.id.menu_options_about_group, ITEM_ID_SETTINGS, ORDER, R.string.settings)
|
.add(R.id.menu_options_about_group, ITEM_ID_SETTINGS, ORDER, R.string.settings)
|
||||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.settings));
|
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_settings));
|
||||||
drawerItems.getMenu()
|
drawerItems.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(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.info));
|
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_info_outline));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -51,6 +51,7 @@ public class ReCaptchaActivity extends AppCompatActivity {
|
||||||
public static final String RECAPTCHA_URL_EXTRA = "recaptcha_url_extra";
|
public static final String RECAPTCHA_URL_EXTRA = "recaptcha_url_extra";
|
||||||
public static final String TAG = ReCaptchaActivity.class.toString();
|
public static final String TAG = ReCaptchaActivity.class.toString();
|
||||||
public static final String YT_URL = "https://www.youtube.com";
|
public static final String YT_URL = "https://www.youtube.com";
|
||||||
|
public static final String RECAPTCHA_COOKIES_KEY = "recaptcha_cookies";
|
||||||
|
|
||||||
private WebView webView;
|
private WebView webView;
|
||||||
private String foundCookies = "";
|
private String foundCookies = "";
|
||||||
|
@ -168,7 +169,7 @@ public class ReCaptchaActivity extends AppCompatActivity {
|
||||||
prefs.edit().putString(key, foundCookies).apply();
|
prefs.edit().putString(key, foundCookies).apply();
|
||||||
|
|
||||||
// give cookies to Downloader class
|
// give cookies to Downloader class
|
||||||
DownloaderImpl.getInstance().setCookies(foundCookies);
|
DownloaderImpl.getInstance().setCookie(RECAPTCHA_COOKIES_KEY, foundCookies);
|
||||||
setResult(RESULT_OK);
|
setResult(RESULT_OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@ import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.appcompat.content.res.AppCompatResources;
|
||||||
import androidx.core.app.NotificationCompat;
|
import androidx.core.app.NotificationCompat;
|
||||||
import androidx.fragment.app.FragmentManager;
|
import androidx.fragment.app.FragmentManager;
|
||||||
|
|
||||||
|
@ -43,15 +44,15 @@ import org.schabi.newpipe.player.playqueue.PlayQueue;
|
||||||
import org.schabi.newpipe.player.playqueue.PlaylistPlayQueue;
|
import org.schabi.newpipe.player.playqueue.PlaylistPlayQueue;
|
||||||
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
|
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
|
||||||
import org.schabi.newpipe.report.UserAction;
|
import org.schabi.newpipe.report.UserAction;
|
||||||
|
import org.schabi.newpipe.util.AndroidTvUtils;
|
||||||
import org.schabi.newpipe.util.Constants;
|
import org.schabi.newpipe.util.Constants;
|
||||||
import org.schabi.newpipe.util.ExtractorHelper;
|
import org.schabi.newpipe.util.ExtractorHelper;
|
||||||
import org.schabi.newpipe.util.AndroidTvUtils;
|
|
||||||
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.ThemeHelper;
|
import org.schabi.newpipe.util.ThemeHelper;
|
||||||
import org.schabi.newpipe.views.FocusOverlayView;
|
|
||||||
import org.schabi.newpipe.util.urlfinder.UrlFinder;
|
import org.schabi.newpipe.util.urlfinder.UrlFinder;
|
||||||
|
import org.schabi.newpipe.views.FocusOverlayView;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -313,7 +314,9 @@ public class RouterActivity extends AppCompatActivity {
|
||||||
final RadioButton radioButton
|
final RadioButton radioButton
|
||||||
= (RadioButton) inflater.inflate(R.layout.list_radio_icon_item, null);
|
= (RadioButton) inflater.inflate(R.layout.list_radio_icon_item, null);
|
||||||
radioButton.setText(item.description);
|
radioButton.setText(item.description);
|
||||||
radioButton.setCompoundDrawablesWithIntrinsicBounds(item.icon, 0, 0, 0);
|
radioButton.setCompoundDrawablesWithIntrinsicBounds(
|
||||||
|
AppCompatResources.getDrawable(getApplicationContext(), item.icon),
|
||||||
|
null, null, null);
|
||||||
radioButton.setChecked(false);
|
radioButton.setChecked(false);
|
||||||
radioButton.setId(id++);
|
radioButton.setId(id++);
|
||||||
radioButton.setLayoutParams(new RadioGroup.LayoutParams(
|
radioButton.setLayoutParams(new RadioGroup.LayoutParams(
|
||||||
|
@ -366,26 +369,26 @@ public class RouterActivity extends AppCompatActivity {
|
||||||
|
|
||||||
returnList.add(new AdapterChoiceItem(getString(R.string.show_info_key),
|
returnList.add(new AdapterChoiceItem(getString(R.string.show_info_key),
|
||||||
getString(R.string.show_info),
|
getString(R.string.show_info),
|
||||||
resolveResourceIdFromAttr(context, R.attr.info)));
|
resolveResourceIdFromAttr(context, R.attr.ic_info_outline)));
|
||||||
|
|
||||||
if (capabilities.contains(VIDEO) && !(isExtVideoEnabled && linkType != LinkType.STREAM)) {
|
if (capabilities.contains(VIDEO) && !(isExtVideoEnabled && linkType != LinkType.STREAM)) {
|
||||||
returnList.add(new AdapterChoiceItem(getString(R.string.video_player_key),
|
returnList.add(new AdapterChoiceItem(getString(R.string.video_player_key),
|
||||||
getString(R.string.video_player),
|
getString(R.string.video_player),
|
||||||
resolveResourceIdFromAttr(context, R.attr.play)));
|
resolveResourceIdFromAttr(context, R.attr.ic_play_arrow)));
|
||||||
returnList.add(new AdapterChoiceItem(getString(R.string.popup_player_key),
|
returnList.add(new AdapterChoiceItem(getString(R.string.popup_player_key),
|
||||||
getString(R.string.popup_player),
|
getString(R.string.popup_player),
|
||||||
resolveResourceIdFromAttr(context, R.attr.popup)));
|
resolveResourceIdFromAttr(context, R.attr.ic_popup)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (capabilities.contains(AUDIO) && !(isExtAudioEnabled && linkType != LinkType.STREAM)) {
|
if (capabilities.contains(AUDIO) && !(isExtAudioEnabled && linkType != LinkType.STREAM)) {
|
||||||
returnList.add(new AdapterChoiceItem(getString(R.string.background_player_key),
|
returnList.add(new AdapterChoiceItem(getString(R.string.background_player_key),
|
||||||
getString(R.string.background_player),
|
getString(R.string.background_player),
|
||||||
resolveResourceIdFromAttr(context, R.attr.audio)));
|
resolveResourceIdFromAttr(context, R.attr.ic_headset)));
|
||||||
}
|
}
|
||||||
|
|
||||||
returnList.add(new AdapterChoiceItem(getString(R.string.download_key),
|
returnList.add(new AdapterChoiceItem(getString(R.string.download_key),
|
||||||
getString(R.string.download),
|
getString(R.string.download),
|
||||||
resolveResourceIdFromAttr(context, R.attr.download)));
|
resolveResourceIdFromAttr(context, R.attr.ic_file_download)));
|
||||||
|
|
||||||
return returnList;
|
return returnList;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,6 @@ package org.schabi.newpipe.about;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.ContextMenu;
|
import android.view.ContextMenu;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
|
@ -17,9 +15,9 @@ import androidx.annotation.Nullable;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
|
import org.schabi.newpipe.util.ShareUtils;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Comparator;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fragment containing the software licenses.
|
* Fragment containing the software licenses.
|
||||||
|
@ -27,7 +25,7 @@ import java.util.Comparator;
|
||||||
public class LicenseFragment extends Fragment {
|
public class LicenseFragment extends Fragment {
|
||||||
private static final String ARG_COMPONENTS = "components";
|
private static final String ARG_COMPONENTS = "components";
|
||||||
private SoftwareComponent[] softwareComponents;
|
private SoftwareComponent[] softwareComponents;
|
||||||
private SoftwareComponent mComponentForContextMenu;
|
private SoftwareComponent componentForContextMenu;
|
||||||
|
|
||||||
public static LicenseFragment newInstance(final SoftwareComponent[] softwareComponents) {
|
public static LicenseFragment newInstance(final SoftwareComponent[] softwareComponents) {
|
||||||
if (softwareComponents == null) {
|
if (softwareComponents == null) {
|
||||||
|
@ -46,7 +44,7 @@ public class LicenseFragment extends Fragment {
|
||||||
* @param context the context to use
|
* @param context the context to use
|
||||||
* @param license the license to show
|
* @param license the license to show
|
||||||
*/
|
*/
|
||||||
public static void showLicense(final Context context, final License license) {
|
private static void showLicense(final Context context, final License license) {
|
||||||
new LicenseFragmentHelper((Activity) context).execute(license);
|
new LicenseFragmentHelper((Activity) context).execute(license);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,45 +55,34 @@ public class LicenseFragment extends Fragment {
|
||||||
.getParcelableArray(ARG_COMPONENTS);
|
.getParcelableArray(ARG_COMPONENTS);
|
||||||
|
|
||||||
// Sort components by name
|
// Sort components by name
|
||||||
Arrays.sort(softwareComponents, new Comparator<SoftwareComponent>() {
|
Arrays.sort(softwareComponents, (o1, o2) -> o1.getName().compareTo(o2.getName()));
|
||||||
@Override
|
|
||||||
public int compare(final SoftwareComponent o1, final SoftwareComponent o2) {
|
|
||||||
return o1.getName().compareTo(o2.getName());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(final LayoutInflater inflater, @Nullable final ViewGroup container,
|
public View onCreateView(final LayoutInflater inflater, @Nullable final ViewGroup container,
|
||||||
@Nullable final Bundle savedInstanceState) {
|
@Nullable final Bundle savedInstanceState) {
|
||||||
View rootView = inflater.inflate(R.layout.fragment_licenses, container, false);
|
final View rootView = inflater.inflate(R.layout.fragment_licenses, container, false);
|
||||||
ViewGroup softwareComponentsView = rootView.findViewById(R.id.software_components);
|
final ViewGroup softwareComponentsView = rootView.findViewById(R.id.software_components);
|
||||||
|
|
||||||
View licenseLink = rootView.findViewById(R.id.app_read_license);
|
final View licenseLink = rootView.findViewById(R.id.app_read_license);
|
||||||
licenseLink.setOnClickListener(new OnReadFullLicenseClickListener());
|
licenseLink.setOnClickListener(v ->
|
||||||
|
showLicense(getActivity(), StandardLicenses.GPL3));
|
||||||
|
|
||||||
for (final SoftwareComponent component : softwareComponents) {
|
for (final SoftwareComponent component : softwareComponents) {
|
||||||
View componentView = inflater
|
final View componentView = inflater
|
||||||
.inflate(R.layout.item_software_component, container, false);
|
.inflate(R.layout.item_software_component, container, false);
|
||||||
TextView softwareName = componentView.findViewById(R.id.name);
|
final TextView softwareName = componentView.findViewById(R.id.name);
|
||||||
TextView copyright = componentView.findViewById(R.id.copyright);
|
final TextView copyright = componentView.findViewById(R.id.copyright);
|
||||||
softwareName.setText(component.getName());
|
softwareName.setText(component.getName());
|
||||||
copyright.setText(getContext().getString(R.string.copyright,
|
copyright.setText(getString(R.string.copyright,
|
||||||
component.getYears(),
|
component.getYears(),
|
||||||
component.getCopyrightOwner(),
|
component.getCopyrightOwner(),
|
||||||
component.getLicense().getAbbreviation()));
|
component.getLicense().getAbbreviation()));
|
||||||
|
|
||||||
componentView.setTag(component);
|
componentView.setTag(component);
|
||||||
componentView.setOnClickListener(new View.OnClickListener() {
|
componentView.setOnClickListener(v ->
|
||||||
@Override
|
showLicense(getActivity(), component.getLicense()));
|
||||||
public void onClick(final View v) {
|
|
||||||
Context context = v.getContext();
|
|
||||||
if (context != null) {
|
|
||||||
showLicense(context, component.getLicense());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
softwareComponentsView.addView(componentView);
|
softwareComponentsView.addView(componentView);
|
||||||
registerForContextMenu(componentView);
|
registerForContextMenu(componentView);
|
||||||
}
|
}
|
||||||
|
@ -105,40 +92,28 @@ public class LicenseFragment extends Fragment {
|
||||||
@Override
|
@Override
|
||||||
public void onCreateContextMenu(final ContextMenu menu, final View v,
|
public void onCreateContextMenu(final ContextMenu menu, final View v,
|
||||||
final ContextMenu.ContextMenuInfo menuInfo) {
|
final ContextMenu.ContextMenuInfo menuInfo) {
|
||||||
MenuInflater inflater = getActivity().getMenuInflater();
|
final MenuInflater inflater = getActivity().getMenuInflater();
|
||||||
SoftwareComponent component = (SoftwareComponent) v.getTag();
|
final SoftwareComponent component = (SoftwareComponent) v.getTag();
|
||||||
menu.setHeaderTitle(component.getName());
|
menu.setHeaderTitle(component.getName());
|
||||||
inflater.inflate(R.menu.software_component, menu);
|
inflater.inflate(R.menu.software_component, menu);
|
||||||
super.onCreateContextMenu(menu, v, menuInfo);
|
super.onCreateContextMenu(menu, v, menuInfo);
|
||||||
mComponentForContextMenu = (SoftwareComponent) v.getTag();
|
componentForContextMenu = (SoftwareComponent) v.getTag();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onContextItemSelected(final MenuItem item) {
|
public boolean onContextItemSelected(final MenuItem item) {
|
||||||
// item.getMenuInfo() is null so we use the tag of the view
|
// item.getMenuInfo() is null so we use the tag of the view
|
||||||
final SoftwareComponent component = mComponentForContextMenu;
|
final SoftwareComponent component = componentForContextMenu;
|
||||||
if (component == null) {
|
if (component == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
switch (item.getItemId()) {
|
switch (item.getItemId()) {
|
||||||
case R.id.action_website:
|
case R.id.action_website:
|
||||||
openWebsite(component.getLink());
|
ShareUtils.openUrlInBrowser(getActivity(), component.getLink());
|
||||||
return true;
|
return true;
|
||||||
case R.id.action_show_license:
|
case R.id.action_show_license:
|
||||||
showLicense(getContext(), component.getLicense());
|
showLicense(getActivity(), component.getLicense());
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void openWebsite(final String componentLink) {
|
|
||||||
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(componentLink));
|
|
||||||
startActivity(browserIntent);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class OnReadFullLicenseClickListener implements View.OnClickListener {
|
|
||||||
@Override
|
|
||||||
public void onClick(final View v) {
|
|
||||||
LicenseFragment.showLicense(v.getContext(), StandardLicenses.GPL3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,10 @@ package org.schabi.newpipe.about;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
|
import android.util.Base64;
|
||||||
import android.webkit.WebView;
|
import android.webkit.WebView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
|
||||||
|
@ -12,6 +14,7 @@ import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.util.ThemeHelper;
|
import org.schabi.newpipe.util.ThemeHelper;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
@ -26,28 +29,18 @@ public class LicenseFragmentHelper extends AsyncTask<Object, Void, Integer> {
|
||||||
weakReference = new WeakReference<>(activity);
|
weakReference = new WeakReference<>(activity);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String getFinishString(final Activity activity) {
|
|
||||||
return activity.getApplicationContext().getResources().getString(R.string.finish);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param context the context to use
|
* @param context the context to use
|
||||||
* @param license the license
|
* @param license the license
|
||||||
* @return String which contains a HTML formatted license page
|
* @return String which contains a HTML formatted license page
|
||||||
* styled according to the context's theme
|
* styled according to the context's theme
|
||||||
*/
|
*/
|
||||||
public static String getFormattedLicense(final Context context, final License license) {
|
private static String getFormattedLicense(@NonNull final Context context,
|
||||||
if (context == null) {
|
@NonNull final License license) {
|
||||||
throw new NullPointerException("context is null");
|
final StringBuilder licenseContent = new StringBuilder();
|
||||||
}
|
final String webViewData;
|
||||||
if (license == null) {
|
|
||||||
throw new NullPointerException("license is null");
|
|
||||||
}
|
|
||||||
|
|
||||||
StringBuilder licenseContent = new StringBuilder();
|
|
||||||
String webViewData;
|
|
||||||
try {
|
try {
|
||||||
BufferedReader in = new BufferedReader(new InputStreamReader(
|
final BufferedReader in = new BufferedReader(new InputStreamReader(
|
||||||
context.getAssets().open(license.getFilename()), StandardCharsets.UTF_8));
|
context.getAssets().open(license.getFilename()), StandardCharsets.UTF_8));
|
||||||
String str;
|
String str;
|
||||||
while ((str = in.readLine()) != null) {
|
while ((str = in.readLine()) != null) {
|
||||||
|
@ -56,13 +49,11 @@ public class LicenseFragmentHelper extends AsyncTask<Object, Void, Integer> {
|
||||||
in.close();
|
in.close();
|
||||||
|
|
||||||
// split the HTML file and insert the stylesheet into the HEAD of the file
|
// split the HTML file and insert the stylesheet into the HEAD of the file
|
||||||
String[] insert = licenseContent.toString().split("</head>");
|
webViewData = licenseContent.toString().replace("</head>",
|
||||||
webViewData = insert[0] + "<style type=\"text/css\">"
|
"<style>" + getLicenseStylesheet(context) + "</style></head>");
|
||||||
+ getLicenseStylesheet(context) + "</style></head>"
|
} catch (IOException e) {
|
||||||
+ insert[1];
|
throw new IllegalArgumentException(
|
||||||
} catch (Exception e) {
|
"Could not get license file: " + license.getFilename(), e);
|
||||||
throw new NullPointerException("could not get license file:"
|
|
||||||
+ getLicenseStylesheet(context));
|
|
||||||
}
|
}
|
||||||
return webViewData;
|
return webViewData;
|
||||||
}
|
}
|
||||||
|
@ -71,21 +62,19 @@ public class LicenseFragmentHelper extends AsyncTask<Object, Void, Integer> {
|
||||||
* @param context
|
* @param context
|
||||||
* @return String which is a CSS stylesheet according to the context's theme
|
* @return String which is a CSS stylesheet according to the context's theme
|
||||||
*/
|
*/
|
||||||
public static String getLicenseStylesheet(final Context context) {
|
private static String getLicenseStylesheet(final Context context) {
|
||||||
boolean isLightTheme = ThemeHelper.isLightThemeSelected(context);
|
final boolean isLightTheme = ThemeHelper.isLightThemeSelected(context);
|
||||||
return "body{padding:12px 15px;margin:0;background:#"
|
return "body{padding:12px 15px;margin:0;"
|
||||||
+ getHexRGBColor(context, isLightTheme
|
+ "background:#" + getHexRGBColor(context, isLightTheme
|
||||||
? R.color.light_license_background_color
|
? R.color.light_license_background_color
|
||||||
: R.color.dark_license_background_color)
|
: R.color.dark_license_background_color) + ";"
|
||||||
+ ";color:#"
|
+ "color:#" + getHexRGBColor(context, isLightTheme
|
||||||
+ getHexRGBColor(context, isLightTheme
|
|
||||||
? R.color.light_license_text_color
|
? R.color.light_license_text_color
|
||||||
: R.color.dark_license_text_color) + ";}"
|
: R.color.dark_license_text_color) + "}"
|
||||||
+ "a[href]{color:#"
|
+ "a[href]{color:#" + getHexRGBColor(context, isLightTheme
|
||||||
+ getHexRGBColor(context, isLightTheme
|
|
||||||
? R.color.light_youtube_primary_color
|
? R.color.light_youtube_primary_color
|
||||||
: R.color.dark_youtube_primary_color) + ";}"
|
: R.color.dark_youtube_primary_color) + "}"
|
||||||
+ "pre{white-space: pre-wrap;}";
|
+ "pre{white-space:pre-wrap}";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -95,13 +84,13 @@ public class LicenseFragmentHelper extends AsyncTask<Object, Void, Integer> {
|
||||||
* @param color the color number from R.color
|
* @param color the color number from R.color
|
||||||
* @return a six characters long String with hexadecimal RGB values
|
* @return a six characters long String with hexadecimal RGB values
|
||||||
*/
|
*/
|
||||||
public static String getHexRGBColor(final Context context, final int color) {
|
private static String getHexRGBColor(final Context context, final int color) {
|
||||||
return context.getResources().getString(color).substring(3);
|
return context.getResources().getString(color).substring(3);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private Activity getActivity() {
|
private Activity getActivity() {
|
||||||
Activity activity = weakReference.get();
|
final Activity activity = weakReference.get();
|
||||||
|
|
||||||
if (activity != null && activity.isFinishing()) {
|
if (activity != null && activity.isFinishing()) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -118,22 +107,22 @@ public class LicenseFragmentHelper extends AsyncTask<Object, Void, Integer> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPostExecute(final Integer result) {
|
protected void onPostExecute(final Integer result) {
|
||||||
Activity activity = getActivity();
|
final Activity activity = getActivity();
|
||||||
if (activity == null) {
|
if (activity == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String webViewData = getFormattedLicense(activity, license);
|
final String webViewData = Base64.encodeToString(getFormattedLicense(activity, license)
|
||||||
AlertDialog.Builder alert = new AlertDialog.Builder(activity);
|
.getBytes(StandardCharsets.UTF_8), Base64.NO_PADDING);
|
||||||
|
final WebView webView = new WebView(activity);
|
||||||
|
webView.loadData(webViewData, "text/html; charset=UTF-8", "base64");
|
||||||
|
|
||||||
|
final AlertDialog.Builder alert = new AlertDialog.Builder(activity);
|
||||||
alert.setTitle(license.getName());
|
alert.setTitle(license.getName());
|
||||||
|
alert.setView(webView);
|
||||||
WebView wv = new WebView(activity);
|
assureCorrectAppLanguage(activity);
|
||||||
wv.loadData(webViewData, "text/html; charset=UTF-8", null);
|
alert.setNegativeButton(activity.getString(R.string.finish),
|
||||||
|
(dialog, which) -> dialog.dismiss());
|
||||||
alert.setView(wv);
|
|
||||||
assureCorrectAppLanguage(activity.getApplicationContext());
|
|
||||||
alert.setNegativeButton(getFinishString(activity), (dialog, which) -> dialog.dismiss());
|
|
||||||
alert.show();
|
alert.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,17 @@
|
||||||
package org.schabi.newpipe.database.feed.dao
|
package org.schabi.newpipe.database.feed.dao
|
||||||
|
|
||||||
import androidx.room.*
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Insert
|
||||||
|
import androidx.room.OnConflictStrategy
|
||||||
|
import androidx.room.Query
|
||||||
|
import androidx.room.Transaction
|
||||||
|
import androidx.room.Update
|
||||||
import io.reactivex.Flowable
|
import io.reactivex.Flowable
|
||||||
|
import java.util.Date
|
||||||
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.model.StreamEntity
|
import org.schabi.newpipe.database.stream.model.StreamEntity
|
||||||
import org.schabi.newpipe.database.subscription.SubscriptionEntity
|
import org.schabi.newpipe.database.subscription.SubscriptionEntity
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
abstract class FeedDAO {
|
abstract class FeedDAO {
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
package org.schabi.newpipe.database.feed.dao
|
package org.schabi.newpipe.database.feed.dao
|
||||||
|
|
||||||
import androidx.room.*
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Insert
|
||||||
|
import androidx.room.OnConflictStrategy
|
||||||
|
import androidx.room.Query
|
||||||
|
import androidx.room.Transaction
|
||||||
|
import androidx.room.Update
|
||||||
import io.reactivex.Flowable
|
import io.reactivex.Flowable
|
||||||
import io.reactivex.Maybe
|
import io.reactivex.Maybe
|
||||||
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
|
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
|
||||||
|
|
|
@ -27,11 +27,11 @@ import org.schabi.newpipe.database.subscription.SubscriptionEntity
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
data class FeedEntity(
|
data class FeedEntity(
|
||||||
@ColumnInfo(name = STREAM_ID)
|
@ColumnInfo(name = STREAM_ID)
|
||||||
var streamId: Long,
|
var streamId: Long,
|
||||||
|
|
||||||
@ColumnInfo(name = SUBSCRIPTION_ID)
|
@ColumnInfo(name = SUBSCRIPTION_ID)
|
||||||
var subscriptionId: Long
|
var subscriptionId: Long
|
||||||
) {
|
) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -40,4 +40,4 @@ data class FeedEntity(
|
||||||
const val STREAM_ID = "stream_id"
|
const val STREAM_ID = "stream_id"
|
||||||
const val SUBSCRIPTION_ID = "subscription_id"
|
const val SUBSCRIPTION_ID = "subscription_id"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,18 +13,18 @@ import org.schabi.newpipe.local.subscription.FeedGroupIcon
|
||||||
indices = [Index(SORT_ORDER)]
|
indices = [Index(SORT_ORDER)]
|
||||||
)
|
)
|
||||||
data class FeedGroupEntity(
|
data class FeedGroupEntity(
|
||||||
@PrimaryKey(autoGenerate = true)
|
@PrimaryKey(autoGenerate = true)
|
||||||
@ColumnInfo(name = ID)
|
@ColumnInfo(name = ID)
|
||||||
val uid: Long,
|
val uid: Long,
|
||||||
|
|
||||||
@ColumnInfo(name = NAME)
|
@ColumnInfo(name = NAME)
|
||||||
var name: String,
|
var name: String,
|
||||||
|
|
||||||
@ColumnInfo(name = ICON)
|
@ColumnInfo(name = ICON)
|
||||||
var icon: FeedGroupIcon,
|
var icon: FeedGroupIcon,
|
||||||
|
|
||||||
@ColumnInfo(name = SORT_ORDER)
|
@ColumnInfo(name = SORT_ORDER)
|
||||||
var sortOrder: Long = -1
|
var sortOrder: Long = -1
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
const val FEED_GROUP_TABLE = "feed_group"
|
const val FEED_GROUP_TABLE = "feed_group"
|
||||||
|
@ -36,4 +36,4 @@ data class FeedGroupEntity(
|
||||||
|
|
||||||
const val GROUP_ALL_ID = -1L
|
const val GROUP_ALL_ID = -1L
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,11 +29,11 @@ import org.schabi.newpipe.database.subscription.SubscriptionEntity
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
data class FeedGroupSubscriptionEntity(
|
data class FeedGroupSubscriptionEntity(
|
||||||
@ColumnInfo(name = GROUP_ID)
|
@ColumnInfo(name = GROUP_ID)
|
||||||
var feedGroupId: Long,
|
var feedGroupId: Long,
|
||||||
|
|
||||||
@ColumnInfo(name = SUBSCRIPTION_ID)
|
@ColumnInfo(name = SUBSCRIPTION_ID)
|
||||||
var subscriptionId: Long
|
var subscriptionId: Long
|
||||||
) {
|
) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -42,4 +42,4 @@ data class FeedGroupSubscriptionEntity(
|
||||||
const val GROUP_ID = "group_id"
|
const val GROUP_ID = "group_id"
|
||||||
const val SUBSCRIPTION_ID = "subscription_id"
|
const val SUBSCRIPTION_ID = "subscription_id"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,10 +4,10 @@ import androidx.room.ColumnInfo
|
||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import androidx.room.ForeignKey
|
import androidx.room.ForeignKey
|
||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
|
import java.util.Date
|
||||||
import org.schabi.newpipe.database.feed.model.FeedLastUpdatedEntity.Companion.FEED_LAST_UPDATED_TABLE
|
import org.schabi.newpipe.database.feed.model.FeedLastUpdatedEntity.Companion.FEED_LAST_UPDATED_TABLE
|
||||||
import org.schabi.newpipe.database.feed.model.FeedLastUpdatedEntity.Companion.SUBSCRIPTION_ID
|
import org.schabi.newpipe.database.feed.model.FeedLastUpdatedEntity.Companion.SUBSCRIPTION_ID
|
||||||
import org.schabi.newpipe.database.subscription.SubscriptionEntity
|
import org.schabi.newpipe.database.subscription.SubscriptionEntity
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
@Entity(
|
@Entity(
|
||||||
tableName = FEED_LAST_UPDATED_TABLE,
|
tableName = FEED_LAST_UPDATED_TABLE,
|
||||||
|
@ -20,12 +20,12 @@ import java.util.*
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
data class FeedLastUpdatedEntity(
|
data class FeedLastUpdatedEntity(
|
||||||
@PrimaryKey
|
@PrimaryKey
|
||||||
@ColumnInfo(name = SUBSCRIPTION_ID)
|
@ColumnInfo(name = SUBSCRIPTION_ID)
|
||||||
var subscriptionId: Long,
|
var subscriptionId: Long,
|
||||||
|
|
||||||
@ColumnInfo(name = LAST_UPDATED)
|
@ColumnInfo(name = LAST_UPDATED)
|
||||||
var lastUpdated: Date? = null
|
var lastUpdated: Date? = null
|
||||||
) {
|
) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -34,4 +34,4 @@ data class FeedLastUpdatedEntity(
|
||||||
const val SUBSCRIPTION_ID = "subscription_id"
|
const val SUBSCRIPTION_ID = "subscription_id"
|
||||||
const val LAST_UPDATED = "last_updated"
|
const val LAST_UPDATED = "last_updated"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,21 +2,21 @@ package org.schabi.newpipe.database.history.model
|
||||||
|
|
||||||
import androidx.room.ColumnInfo
|
import androidx.room.ColumnInfo
|
||||||
import androidx.room.Embedded
|
import androidx.room.Embedded
|
||||||
|
import java.util.Date
|
||||||
import org.schabi.newpipe.database.stream.model.StreamEntity
|
import org.schabi.newpipe.database.stream.model.StreamEntity
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
data class StreamHistoryEntry(
|
data class StreamHistoryEntry(
|
||||||
@Embedded
|
@Embedded
|
||||||
val streamEntity: StreamEntity,
|
val streamEntity: StreamEntity,
|
||||||
|
|
||||||
@ColumnInfo(name = StreamHistoryEntity.JOIN_STREAM_ID)
|
@ColumnInfo(name = StreamHistoryEntity.JOIN_STREAM_ID)
|
||||||
val streamId: Long,
|
val streamId: Long,
|
||||||
|
|
||||||
@ColumnInfo(name = StreamHistoryEntity.STREAM_ACCESS_DATE)
|
@ColumnInfo(name = StreamHistoryEntity.STREAM_ACCESS_DATE)
|
||||||
val accessDate: Date,
|
val accessDate: Date,
|
||||||
|
|
||||||
@ColumnInfo(name = StreamHistoryEntity.STREAM_REPEAT_COUNT)
|
@ColumnInfo(name = StreamHistoryEntity.STREAM_REPEAT_COUNT)
|
||||||
val repeatCount: Long
|
val repeatCount: Long
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun toStreamHistoryEntity(): StreamHistoryEntity {
|
fun toStreamHistoryEntity(): StreamHistoryEntity {
|
||||||
|
|
|
@ -8,14 +8,14 @@ import org.schabi.newpipe.database.stream.model.StreamEntity
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem
|
import org.schabi.newpipe.extractor.stream.StreamInfoItem
|
||||||
|
|
||||||
class PlaylistStreamEntry(
|
class PlaylistStreamEntry(
|
||||||
@Embedded
|
@Embedded
|
||||||
val streamEntity: StreamEntity,
|
val streamEntity: StreamEntity,
|
||||||
|
|
||||||
@ColumnInfo(name = PlaylistStreamEntity.JOIN_STREAM_ID)
|
@ColumnInfo(name = PlaylistStreamEntity.JOIN_STREAM_ID)
|
||||||
val streamId: Long,
|
val streamId: Long,
|
||||||
|
|
||||||
@ColumnInfo(name = PlaylistStreamEntity.JOIN_INDEX)
|
@ColumnInfo(name = PlaylistStreamEntity.JOIN_INDEX)
|
||||||
val joinIndex: Int
|
val joinIndex: Int
|
||||||
) : LocalItem {
|
) : LocalItem {
|
||||||
|
|
||||||
@Throws(IllegalArgumentException::class)
|
@Throws(IllegalArgumentException::class)
|
||||||
|
|
|
@ -2,24 +2,24 @@ package org.schabi.newpipe.database.stream
|
||||||
|
|
||||||
import androidx.room.ColumnInfo
|
import androidx.room.ColumnInfo
|
||||||
import androidx.room.Embedded
|
import androidx.room.Embedded
|
||||||
|
import java.util.Date
|
||||||
import org.schabi.newpipe.database.LocalItem
|
import org.schabi.newpipe.database.LocalItem
|
||||||
import org.schabi.newpipe.database.history.model.StreamHistoryEntity
|
import org.schabi.newpipe.database.history.model.StreamHistoryEntity
|
||||||
import org.schabi.newpipe.database.stream.model.StreamEntity
|
import org.schabi.newpipe.database.stream.model.StreamEntity
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem
|
import org.schabi.newpipe.extractor.stream.StreamInfoItem
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
class StreamStatisticsEntry(
|
class StreamStatisticsEntry(
|
||||||
@Embedded
|
@Embedded
|
||||||
val streamEntity: StreamEntity,
|
val streamEntity: StreamEntity,
|
||||||
|
|
||||||
@ColumnInfo(name = StreamHistoryEntity.JOIN_STREAM_ID)
|
@ColumnInfo(name = StreamHistoryEntity.JOIN_STREAM_ID)
|
||||||
val streamId: Long,
|
val streamId: Long,
|
||||||
|
|
||||||
@ColumnInfo(name = STREAM_LATEST_DATE)
|
@ColumnInfo(name = STREAM_LATEST_DATE)
|
||||||
val latestAccessDate: Date,
|
val latestAccessDate: Date,
|
||||||
|
|
||||||
@ColumnInfo(name = STREAM_WATCH_COUNT)
|
@ColumnInfo(name = STREAM_WATCH_COUNT)
|
||||||
val watchCount: Long
|
val watchCount: Long
|
||||||
) : LocalItem {
|
) : LocalItem {
|
||||||
|
|
||||||
fun toStreamInfoItem(): StreamInfoItem {
|
fun toStreamInfoItem(): StreamInfoItem {
|
||||||
|
|
|
@ -1,15 +1,19 @@
|
||||||
package org.schabi.newpipe.database.stream.dao
|
package org.schabi.newpipe.database.stream.dao
|
||||||
|
|
||||||
import androidx.room.*
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Insert
|
||||||
|
import androidx.room.OnConflictStrategy
|
||||||
|
import androidx.room.Query
|
||||||
|
import androidx.room.Transaction
|
||||||
import io.reactivex.Flowable
|
import io.reactivex.Flowable
|
||||||
|
import java.util.Date
|
||||||
import org.schabi.newpipe.database.BasicDAO
|
import org.schabi.newpipe.database.BasicDAO
|
||||||
import org.schabi.newpipe.database.stream.model.StreamEntity
|
import org.schabi.newpipe.database.stream.model.StreamEntity
|
||||||
import org.schabi.newpipe.database.stream.model.StreamEntity.Companion.STREAM_ID
|
import org.schabi.newpipe.database.stream.model.StreamEntity.Companion.STREAM_ID
|
||||||
import org.schabi.newpipe.extractor.stream.StreamType
|
import org.schabi.newpipe.extractor.stream.StreamType
|
||||||
import org.schabi.newpipe.extractor.stream.StreamType.AUDIO_LIVE_STREAM
|
import org.schabi.newpipe.extractor.stream.StreamType.AUDIO_LIVE_STREAM
|
||||||
import org.schabi.newpipe.extractor.stream.StreamType.LIVE_STREAM
|
import org.schabi.newpipe.extractor.stream.StreamType.LIVE_STREAM
|
||||||
import java.util.*
|
|
||||||
import kotlin.collections.ArrayList
|
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
abstract class StreamDAO : BasicDAO<StreamEntity> {
|
abstract class StreamDAO : BasicDAO<StreamEntity> {
|
||||||
|
@ -94,7 +98,6 @@ abstract class StreamDAO : BasicDAO<StreamEntity> {
|
||||||
if (existentMinimalStream.duration > 0 && newerStream.duration < 0) {
|
if (existentMinimalStream.duration > 0 && newerStream.duration < 0) {
|
||||||
newerStream.duration = existentMinimalStream.duration
|
newerStream.duration = existentMinimalStream.duration
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,21 +119,22 @@ abstract class StreamDAO : BasicDAO<StreamEntity> {
|
||||||
* Minimal entry class used when comparing/updating an existent stream.
|
* Minimal entry class used when comparing/updating an existent stream.
|
||||||
*/
|
*/
|
||||||
internal data class StreamCompareFeed(
|
internal data class StreamCompareFeed(
|
||||||
@ColumnInfo(name = STREAM_ID)
|
@ColumnInfo(name = STREAM_ID)
|
||||||
var uid: Long = 0,
|
var uid: Long = 0,
|
||||||
|
|
||||||
@ColumnInfo(name = StreamEntity.STREAM_TYPE)
|
@ColumnInfo(name = StreamEntity.STREAM_TYPE)
|
||||||
var streamType: StreamType,
|
var streamType: StreamType,
|
||||||
|
|
||||||
@ColumnInfo(name = StreamEntity.STREAM_TEXTUAL_UPLOAD_DATE)
|
@ColumnInfo(name = StreamEntity.STREAM_TEXTUAL_UPLOAD_DATE)
|
||||||
var textualUploadDate: String? = null,
|
var textualUploadDate: String? = null,
|
||||||
|
|
||||||
@ColumnInfo(name = StreamEntity.STREAM_UPLOAD_DATE)
|
@ColumnInfo(name = StreamEntity.STREAM_UPLOAD_DATE)
|
||||||
var uploadDate: Date? = null,
|
var uploadDate: Date? = null,
|
||||||
|
|
||||||
@ColumnInfo(name = StreamEntity.STREAM_IS_UPLOAD_DATE_APPROXIMATION)
|
@ColumnInfo(name = StreamEntity.STREAM_IS_UPLOAD_DATE_APPROXIMATION)
|
||||||
var isUploadDateApproximation: Boolean? = null,
|
var isUploadDateApproximation: Boolean? = null,
|
||||||
|
|
||||||
@ColumnInfo(name = StreamEntity.STREAM_DURATION)
|
@ColumnInfo(name = StreamEntity.STREAM_DURATION)
|
||||||
var duration: Long)
|
var duration: Long
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,13 @@
|
||||||
package org.schabi.newpipe.database.stream.model
|
package org.schabi.newpipe.database.stream.model
|
||||||
|
|
||||||
import androidx.room.*
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.Ignore
|
||||||
|
import androidx.room.Index
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
import java.io.Serializable
|
||||||
|
import java.util.Calendar
|
||||||
|
import java.util.Date
|
||||||
import org.schabi.newpipe.database.stream.model.StreamEntity.Companion.STREAM_SERVICE_ID
|
import org.schabi.newpipe.database.stream.model.StreamEntity.Companion.STREAM_SERVICE_ID
|
||||||
import org.schabi.newpipe.database.stream.model.StreamEntity.Companion.STREAM_TABLE
|
import org.schabi.newpipe.database.stream.model.StreamEntity.Companion.STREAM_TABLE
|
||||||
import org.schabi.newpipe.database.stream.model.StreamEntity.Companion.STREAM_URL
|
import org.schabi.newpipe.database.stream.model.StreamEntity.Companion.STREAM_URL
|
||||||
|
@ -9,8 +16,6 @@ import org.schabi.newpipe.extractor.stream.StreamInfo
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem
|
import org.schabi.newpipe.extractor.stream.StreamInfoItem
|
||||||
import org.schabi.newpipe.extractor.stream.StreamType
|
import org.schabi.newpipe.extractor.stream.StreamType
|
||||||
import org.schabi.newpipe.player.playqueue.PlayQueueItem
|
import org.schabi.newpipe.player.playqueue.PlayQueueItem
|
||||||
import java.io.Serializable
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
@Entity(tableName = STREAM_TABLE,
|
@Entity(tableName = STREAM_TABLE,
|
||||||
indices = [
|
indices = [
|
||||||
|
@ -18,42 +23,42 @@ import java.util.*
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
data class StreamEntity(
|
data class StreamEntity(
|
||||||
@PrimaryKey(autoGenerate = true)
|
@PrimaryKey(autoGenerate = true)
|
||||||
@ColumnInfo(name = STREAM_ID)
|
@ColumnInfo(name = STREAM_ID)
|
||||||
var uid: Long = 0,
|
var uid: Long = 0,
|
||||||
|
|
||||||
@ColumnInfo(name = STREAM_SERVICE_ID)
|
@ColumnInfo(name = STREAM_SERVICE_ID)
|
||||||
var serviceId: Int,
|
var serviceId: Int,
|
||||||
|
|
||||||
@ColumnInfo(name = STREAM_URL)
|
@ColumnInfo(name = STREAM_URL)
|
||||||
var url: String,
|
var url: String,
|
||||||
|
|
||||||
@ColumnInfo(name = STREAM_TITLE)
|
@ColumnInfo(name = STREAM_TITLE)
|
||||||
var title: String,
|
var title: String,
|
||||||
|
|
||||||
@ColumnInfo(name = STREAM_TYPE)
|
@ColumnInfo(name = STREAM_TYPE)
|
||||||
var streamType: StreamType,
|
var streamType: StreamType,
|
||||||
|
|
||||||
@ColumnInfo(name = STREAM_DURATION)
|
@ColumnInfo(name = STREAM_DURATION)
|
||||||
var duration: Long,
|
var duration: Long,
|
||||||
|
|
||||||
@ColumnInfo(name = STREAM_UPLOADER)
|
@ColumnInfo(name = STREAM_UPLOADER)
|
||||||
var uploader: String,
|
var uploader: String,
|
||||||
|
|
||||||
@ColumnInfo(name = STREAM_THUMBNAIL_URL)
|
@ColumnInfo(name = STREAM_THUMBNAIL_URL)
|
||||||
var thumbnailUrl: String? = null,
|
var thumbnailUrl: String? = null,
|
||||||
|
|
||||||
@ColumnInfo(name = STREAM_VIEWS)
|
@ColumnInfo(name = STREAM_VIEWS)
|
||||||
var viewCount: Long? = null,
|
var viewCount: Long? = null,
|
||||||
|
|
||||||
@ColumnInfo(name = STREAM_TEXTUAL_UPLOAD_DATE)
|
@ColumnInfo(name = STREAM_TEXTUAL_UPLOAD_DATE)
|
||||||
var textualUploadDate: String? = null,
|
var textualUploadDate: String? = null,
|
||||||
|
|
||||||
@ColumnInfo(name = STREAM_UPLOAD_DATE)
|
@ColumnInfo(name = STREAM_UPLOAD_DATE)
|
||||||
var uploadDate: Date? = null,
|
var uploadDate: Date? = null,
|
||||||
|
|
||||||
@ColumnInfo(name = STREAM_IS_UPLOAD_DATE_APPROXIMATION)
|
@ColumnInfo(name = STREAM_IS_UPLOAD_DATE_APPROXIMATION)
|
||||||
var isUploadDateApproximation: Boolean? = null
|
var isUploadDateApproximation: Boolean? = null
|
||||||
) : Serializable {
|
) : Serializable {
|
||||||
|
|
||||||
@Ignore
|
@Ignore
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
package org.schabi.newpipe.database.subscription
|
package org.schabi.newpipe.database.subscription
|
||||||
|
|
||||||
import androidx.room.*
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Insert
|
||||||
|
import androidx.room.OnConflictStrategy
|
||||||
|
import androidx.room.Query
|
||||||
|
import androidx.room.Transaction
|
||||||
import io.reactivex.Flowable
|
import io.reactivex.Flowable
|
||||||
import io.reactivex.Maybe
|
import io.reactivex.Maybe
|
||||||
import org.schabi.newpipe.database.BasicDAO
|
import org.schabi.newpipe.database.BasicDAO
|
||||||
|
|
|
@ -396,13 +396,11 @@ public class DownloadDialog extends DialogFragment
|
||||||
Log.d(TAG, "initToolbar() called with: toolbar = [" + toolbar + "]");
|
Log.d(TAG, "initToolbar() called with: toolbar = [" + toolbar + "]");
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isLight = ThemeHelper.isLightThemeSelected(getActivity());
|
|
||||||
|
|
||||||
toolbar.setTitle(R.string.download_dialog_title);
|
toolbar.setTitle(R.string.download_dialog_title);
|
||||||
toolbar.setNavigationIcon(isLight ? R.drawable.ic_arrow_back_black_24dp
|
toolbar.setNavigationIcon(
|
||||||
: R.drawable.ic_arrow_back_white_24dp);
|
ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_arrow_back));
|
||||||
toolbar.inflateMenu(R.menu.dialog_url);
|
toolbar.inflateMenu(R.menu.dialog_url);
|
||||||
toolbar.setNavigationOnClickListener(v -> getDialog().dismiss());
|
toolbar.setNavigationOnClickListener(v -> requireDialog().dismiss());
|
||||||
toolbar.setNavigationContentDescription(R.string.cancel);
|
toolbar.setNavigationContentDescription(R.string.cancel);
|
||||||
|
|
||||||
okButton = toolbar.findViewById(R.id.okay);
|
okButton = toolbar.findViewById(R.id.okay);
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
package org.schabi.newpipe.fragments;
|
package org.schabi.newpipe.fragments;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.res.ColorStateList;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.preference.PreferenceManager;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
|
@ -30,6 +32,7 @@ import org.schabi.newpipe.settings.tabs.Tab;
|
||||||
import org.schabi.newpipe.settings.tabs.TabsManager;
|
import org.schabi.newpipe.settings.tabs.TabsManager;
|
||||||
import org.schabi.newpipe.util.NavigationHelper;
|
import org.schabi.newpipe.util.NavigationHelper;
|
||||||
import org.schabi.newpipe.util.ServiceHelper;
|
import org.schabi.newpipe.util.ServiceHelper;
|
||||||
|
import org.schabi.newpipe.util.ThemeHelper;
|
||||||
import org.schabi.newpipe.views.ScrollableTabLayout;
|
import org.schabi.newpipe.views.ScrollableTabLayout;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -45,6 +48,9 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
||||||
|
|
||||||
private boolean hasTabsChanged = false;
|
private boolean hasTabsChanged = false;
|
||||||
|
|
||||||
|
private boolean previousYoutubeRestrictedModeEnabled;
|
||||||
|
private String youtubeRestrictedModeEnabledKey;
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Fragment's LifeCycle
|
// Fragment's LifeCycle
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
@ -53,7 +59,6 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
||||||
public void onCreate(final Bundle savedInstanceState) {
|
public void onCreate(final Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setHasOptionsMenu(true);
|
setHasOptionsMenu(true);
|
||||||
|
|
||||||
tabsManager = TabsManager.getManager(activity);
|
tabsManager = TabsManager.getManager(activity);
|
||||||
tabsManager.setSavedTabsListener(() -> {
|
tabsManager.setSavedTabsListener(() -> {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
|
@ -66,6 +71,11 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
||||||
hasTabsChanged = true;
|
hasTabsChanged = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
youtubeRestrictedModeEnabledKey = getString(R.string.youtube_restricted_mode_enabled);
|
||||||
|
previousYoutubeRestrictedModeEnabled =
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(getContext())
|
||||||
|
.getBoolean(youtubeRestrictedModeEnabledKey, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -82,6 +92,8 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
||||||
tabLayout = rootView.findViewById(R.id.main_tab_layout);
|
tabLayout = rootView.findViewById(R.id.main_tab_layout);
|
||||||
viewPager = rootView.findViewById(R.id.pager);
|
viewPager = rootView.findViewById(R.id.pager);
|
||||||
|
|
||||||
|
tabLayout.setTabIconTint(ColorStateList.valueOf(
|
||||||
|
ThemeHelper.resolveColorFromAttr(requireContext(), R.attr.colorAccent)));
|
||||||
tabLayout.setupWithViewPager(viewPager);
|
tabLayout.setupWithViewPager(viewPager);
|
||||||
tabLayout.addOnTabSelectedListener(this);
|
tabLayout.addOnTabSelectedListener(this);
|
||||||
|
|
||||||
|
@ -92,7 +104,13 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
|
|
||||||
if (hasTabsChanged) {
|
boolean youtubeRestrictedModeEnabled =
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(getContext())
|
||||||
|
.getBoolean(youtubeRestrictedModeEnabledKey, false);
|
||||||
|
if (previousYoutubeRestrictedModeEnabled != youtubeRestrictedModeEnabled) {
|
||||||
|
previousYoutubeRestrictedModeEnabled = youtubeRestrictedModeEnabled;
|
||||||
|
setupTabs();
|
||||||
|
} else if (hasTabsChanged) {
|
||||||
setupTabs();
|
setupTabs();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import android.app.Activity;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
@ -35,6 +36,7 @@ import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.ActionBar;
|
import androidx.appcompat.app.ActionBar;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.appcompat.content.res.AppCompatResources;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.viewpager.widget.ViewPager;
|
import androidx.viewpager.widget.ViewPager;
|
||||||
|
@ -85,6 +87,7 @@ import org.schabi.newpipe.util.PermissionHelper;
|
||||||
import org.schabi.newpipe.util.ShareUtils;
|
import org.schabi.newpipe.util.ShareUtils;
|
||||||
import org.schabi.newpipe.util.StreamItemAdapter;
|
import org.schabi.newpipe.util.StreamItemAdapter;
|
||||||
import org.schabi.newpipe.util.StreamItemAdapter.StreamSizeWrapper;
|
import org.schabi.newpipe.util.StreamItemAdapter.StreamSizeWrapper;
|
||||||
|
import org.schabi.newpipe.util.ThemeHelper;
|
||||||
import org.schabi.newpipe.views.AnimatedProgressBar;
|
import org.schabi.newpipe.views.AnimatedProgressBar;
|
||||||
import org.schabi.newpipe.views.LargeTextMovementMethod;
|
import org.schabi.newpipe.views.LargeTextMovementMethod;
|
||||||
|
|
||||||
|
@ -175,6 +178,8 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
|
||||||
private View uploaderRootLayout;
|
private View uploaderRootLayout;
|
||||||
private TextView uploaderTextView;
|
private TextView uploaderTextView;
|
||||||
private ImageView uploaderThumb;
|
private ImageView uploaderThumb;
|
||||||
|
private TextView subChannelTextView;
|
||||||
|
private ImageView subChannelThumb;
|
||||||
|
|
||||||
private TextView thumbsUpTextView;
|
private TextView thumbsUpTextView;
|
||||||
private ImageView thumbsUpImageView;
|
private ImageView thumbsUpImageView;
|
||||||
|
@ -419,18 +424,17 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case R.id.detail_uploader_root_layout:
|
case R.id.detail_uploader_root_layout:
|
||||||
if (TextUtils.isEmpty(currentInfo.getUploaderUrl())) {
|
if (TextUtils.isEmpty(currentInfo.getSubChannelUrl())) {
|
||||||
Log.w(TAG, "Can't open channel because we got no channel URL");
|
if (!TextUtils.isEmpty(currentInfo.getUploaderUrl())) {
|
||||||
} else {
|
openChannel(currentInfo.getUploaderUrl(), currentInfo.getUploaderName());
|
||||||
try {
|
|
||||||
NavigationHelper.openChannelFragment(
|
|
||||||
getFragmentManager(),
|
|
||||||
currentInfo.getServiceId(),
|
|
||||||
currentInfo.getUploaderUrl(),
|
|
||||||
currentInfo.getUploaderName());
|
|
||||||
} catch (Exception e) {
|
|
||||||
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.i(TAG, "Can't open sub-channel because we got no channel URL");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
openChannel(currentInfo.getSubChannelUrl(),
|
||||||
|
currentInfo.getSubChannelName());
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case R.id.detail_thumbnail_root_layout:
|
case R.id.detail_thumbnail_root_layout:
|
||||||
|
@ -447,6 +451,18 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void openChannel(final String subChannelUrl, final String subChannelName) {
|
||||||
|
try {
|
||||||
|
NavigationHelper.openChannelFragment(
|
||||||
|
getFragmentManager(),
|
||||||
|
currentInfo.getServiceId(),
|
||||||
|
subChannelUrl,
|
||||||
|
subChannelName);
|
||||||
|
} catch (Exception e) {
|
||||||
|
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onLongClick(final View v) {
|
public boolean onLongClick(final View v) {
|
||||||
if (isLoading.get() || currentInfo == null) {
|
if (isLoading.get() || currentInfo == null) {
|
||||||
|
@ -463,6 +479,15 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
|
||||||
case R.id.detail_controls_download:
|
case R.id.detail_controls_download:
|
||||||
NavigationHelper.openDownloads(getActivity());
|
NavigationHelper.openDownloads(getActivity());
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case R.id.detail_uploader_root_layout:
|
||||||
|
if (TextUtils.isEmpty(currentInfo.getSubChannelUrl())) {
|
||||||
|
Log.w(TAG,
|
||||||
|
"Can't open parent channel because we got no parent channel URL");
|
||||||
|
} else {
|
||||||
|
openChannel(currentInfo.getUploaderUrl(), currentInfo.getUploaderName());
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -473,13 +498,15 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
|
||||||
videoTitleTextView.setMaxLines(1);
|
videoTitleTextView.setMaxLines(1);
|
||||||
videoDescriptionRootLayout.setVisibility(View.GONE);
|
videoDescriptionRootLayout.setVisibility(View.GONE);
|
||||||
videoDescriptionView.setFocusable(false);
|
videoDescriptionView.setFocusable(false);
|
||||||
videoTitleToggleArrow.setImageResource(R.drawable.arrow_down);
|
videoTitleToggleArrow.setImageResource(
|
||||||
|
ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_expand_more));
|
||||||
} else {
|
} else {
|
||||||
videoTitleTextView.setMaxLines(10);
|
videoTitleTextView.setMaxLines(10);
|
||||||
videoDescriptionRootLayout.setVisibility(View.VISIBLE);
|
videoDescriptionRootLayout.setVisibility(View.VISIBLE);
|
||||||
videoDescriptionView.setFocusable(true);
|
videoDescriptionView.setFocusable(true);
|
||||||
videoDescriptionView.setMovementMethod(new LargeTextMovementMethod());
|
videoDescriptionView.setMovementMethod(new LargeTextMovementMethod());
|
||||||
videoTitleToggleArrow.setImageResource(R.drawable.arrow_up);
|
videoTitleToggleArrow.setImageResource(
|
||||||
|
ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_expand_less));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -525,6 +552,8 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
|
||||||
uploaderRootLayout = rootView.findViewById(R.id.detail_uploader_root_layout);
|
uploaderRootLayout = rootView.findViewById(R.id.detail_uploader_root_layout);
|
||||||
uploaderTextView = rootView.findViewById(R.id.detail_uploader_text_view);
|
uploaderTextView = rootView.findViewById(R.id.detail_uploader_text_view);
|
||||||
uploaderThumb = rootView.findViewById(R.id.detail_uploader_thumbnail_view);
|
uploaderThumb = rootView.findViewById(R.id.detail_uploader_thumbnail_view);
|
||||||
|
subChannelTextView = rootView.findViewById(R.id.detail_sub_channel_text_view);
|
||||||
|
subChannelThumb = rootView.findViewById(R.id.detail_sub_channel_thumbnail_view);
|
||||||
|
|
||||||
appBarLayout = rootView.findViewById(R.id.appbarlayout);
|
appBarLayout = rootView.findViewById(R.id.appbarlayout);
|
||||||
viewPager = rootView.findViewById(R.id.viewpager);
|
viewPager = rootView.findViewById(R.id.viewpager);
|
||||||
|
@ -554,8 +583,9 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
|
||||||
protected void initListeners() {
|
protected void initListeners() {
|
||||||
super.initListeners();
|
super.initListeners();
|
||||||
|
|
||||||
videoTitleRoot.setOnClickListener(this);
|
|
||||||
uploaderRootLayout.setOnClickListener(this);
|
uploaderRootLayout.setOnClickListener(this);
|
||||||
|
uploaderRootLayout.setOnLongClickListener(this);
|
||||||
|
videoTitleRoot.setOnClickListener(this);
|
||||||
thumbnailBackgroundButton.setOnClickListener(this);
|
thumbnailBackgroundButton.setOnClickListener(this);
|
||||||
detailControlsBackground.setOnClickListener(this);
|
detailControlsBackground.setOnClickListener(this);
|
||||||
detailControlsPopup.setOnClickListener(this);
|
detailControlsPopup.setOnClickListener(this);
|
||||||
|
@ -603,6 +633,11 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
|
||||||
ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS, onFailListener);
|
ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS, onFailListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!TextUtils.isEmpty(info.getSubChannelAvatarUrl())) {
|
||||||
|
IMAGE_LOADER.displayImage(info.getSubChannelAvatarUrl(), subChannelThumb,
|
||||||
|
ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS);
|
||||||
|
}
|
||||||
|
|
||||||
if (!TextUtils.isEmpty(info.getUploaderAvatarUrl())) {
|
if (!TextUtils.isEmpty(info.getUploaderAvatarUrl())) {
|
||||||
IMAGE_LOADER.displayImage(info.getUploaderAvatarUrl(), uploaderThumb,
|
IMAGE_LOADER.displayImage(info.getUploaderAvatarUrl(), uploaderThumb,
|
||||||
ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS);
|
ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS);
|
||||||
|
@ -632,9 +667,10 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateMenuItemVisibility() {
|
private void updateMenuItemVisibility() {
|
||||||
// show kodi if set in settings
|
// show kodi button if it supports the current service and it is enabled in settings
|
||||||
menu.findItem(R.id.action_play_with_kodi).setVisible(
|
menu.findItem(R.id.action_play_with_kodi).setVisible(
|
||||||
PreferenceManager.getDefaultSharedPreferences(activity).getBoolean(
|
KoreUtil.isServiceSupportedByKore(serviceId)
|
||||||
|
&& PreferenceManager.getDefaultSharedPreferences(activity).getBoolean(
|
||||||
activity.getString(R.string.show_play_with_kodi_key), false));
|
activity.getString(R.string.show_play_with_kodi_key), false));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -665,8 +701,7 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
|
||||||
return true;
|
return true;
|
||||||
case R.id.action_play_with_kodi:
|
case R.id.action_play_with_kodi:
|
||||||
try {
|
try {
|
||||||
NavigationHelper.playWithKore(activity, Uri.parse(
|
NavigationHelper.playWithKore(activity, Uri.parse(currentInfo.getUrl()));
|
||||||
url.replace("https", "http")));
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.i(TAG, "Failed to start kore", e);
|
Log.i(TAG, "Failed to start kore", e);
|
||||||
|
@ -964,7 +999,7 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
|
||||||
@NonNull final StreamInfo info,
|
@NonNull final StreamInfo info,
|
||||||
@NonNull final Stream selectedStream) {
|
@NonNull final Stream selectedStream) {
|
||||||
NavigationHelper.playOnExternalPlayer(context, currentInfo.getName(),
|
NavigationHelper.playOnExternalPlayer(context, currentInfo.getName(),
|
||||||
currentInfo.getUploaderName(), selectedStream);
|
currentInfo.getSubChannelName(), selectedStream);
|
||||||
|
|
||||||
final HistoryRecordManager recordManager = new HistoryRecordManager(requireContext());
|
final HistoryRecordManager recordManager = new HistoryRecordManager(requireContext());
|
||||||
disposables.add(recordManager.onViewed(info).onErrorComplete()
|
disposables.add(recordManager.onViewed(info).onErrorComplete()
|
||||||
|
@ -1043,7 +1078,8 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
thumbnailImageView.setImageDrawable(ContextCompat.getDrawable(activity, imageResource));
|
thumbnailImageView.setImageDrawable(
|
||||||
|
AppCompatResources.getDrawable(requireContext(), imageResource));
|
||||||
animateView(thumbnailImageView, false, 0, 0,
|
animateView(thumbnailImageView, false, 0, 0,
|
||||||
() -> animateView(thumbnailImageView, true, 500));
|
() -> animateView(thumbnailImageView, true, 500));
|
||||||
}
|
}
|
||||||
|
@ -1084,7 +1120,6 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
|
||||||
animateView(videoTitleTextView, true, 0);
|
animateView(videoTitleTextView, true, 0);
|
||||||
|
|
||||||
videoDescriptionRootLayout.setVisibility(View.GONE);
|
videoDescriptionRootLayout.setVisibility(View.GONE);
|
||||||
videoTitleToggleArrow.setImageResource(R.drawable.arrow_down);
|
|
||||||
videoTitleToggleArrow.setVisibility(View.GONE);
|
videoTitleToggleArrow.setVisibility(View.GONE);
|
||||||
videoTitleRoot.setClickable(false);
|
videoTitleRoot.setClickable(false);
|
||||||
|
|
||||||
|
@ -1097,9 +1132,9 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
|
||||||
}
|
}
|
||||||
|
|
||||||
IMAGE_LOADER.cancelDisplayTask(thumbnailImageView);
|
IMAGE_LOADER.cancelDisplayTask(thumbnailImageView);
|
||||||
IMAGE_LOADER.cancelDisplayTask(uploaderThumb);
|
IMAGE_LOADER.cancelDisplayTask(subChannelThumb);
|
||||||
thumbnailImageView.setImageBitmap(null);
|
thumbnailImageView.setImageBitmap(null);
|
||||||
uploaderThumb.setImageBitmap(null);
|
subChannelThumb.setImageBitmap(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1127,14 +1162,18 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
|
||||||
animateView(thumbnailPlayButton, true, 200);
|
animateView(thumbnailPlayButton, true, 200);
|
||||||
videoTitleTextView.setText(name);
|
videoTitleTextView.setText(name);
|
||||||
|
|
||||||
if (!TextUtils.isEmpty(info.getUploaderName())) {
|
if (!TextUtils.isEmpty(info.getSubChannelName())) {
|
||||||
uploaderTextView.setText(info.getUploaderName());
|
displayBothUploaderAndSubChannel(info);
|
||||||
uploaderTextView.setVisibility(View.VISIBLE);
|
} else if (!TextUtils.isEmpty(info.getUploaderName())) {
|
||||||
uploaderTextView.setSelected(true);
|
displayUploaderAsSubChannel(info);
|
||||||
} else {
|
} else {
|
||||||
uploaderTextView.setVisibility(View.GONE);
|
uploaderTextView.setVisibility(View.GONE);
|
||||||
|
uploaderThumb.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
uploaderThumb.setImageDrawable(ContextCompat.getDrawable(activity, R.drawable.buddy));
|
|
||||||
|
Drawable buddyDrawable = AppCompatResources.getDrawable(activity, R.drawable.buddy);
|
||||||
|
subChannelThumb.setImageDrawable(buddyDrawable);
|
||||||
|
uploaderThumb.setImageDrawable(buddyDrawable);
|
||||||
|
|
||||||
if (info.getViewCount() >= 0) {
|
if (info.getViewCount() >= 0) {
|
||||||
if (info.getStreamType().equals(StreamType.AUDIO_LIVE_STREAM)) {
|
if (info.getStreamType().equals(StreamType.AUDIO_LIVE_STREAM)) {
|
||||||
|
@ -1196,8 +1235,9 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
|
||||||
|
|
||||||
videoDescriptionView.setVisibility(View.GONE);
|
videoDescriptionView.setVisibility(View.GONE);
|
||||||
videoTitleRoot.setClickable(true);
|
videoTitleRoot.setClickable(true);
|
||||||
|
videoTitleToggleArrow.setImageResource(
|
||||||
|
ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_expand_more));
|
||||||
videoTitleToggleArrow.setVisibility(View.VISIBLE);
|
videoTitleToggleArrow.setVisibility(View.VISIBLE);
|
||||||
videoTitleToggleArrow.setImageResource(R.drawable.arrow_down);
|
|
||||||
videoDescriptionRootLayout.setVisibility(View.GONE);
|
videoDescriptionRootLayout.setVisibility(View.GONE);
|
||||||
|
|
||||||
if (info.getUploadDate() != null) {
|
if (info.getUploadDate() != null) {
|
||||||
|
@ -1243,7 +1283,7 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
|
||||||
|
|
||||||
detailControlsPopup.setVisibility(View.GONE);
|
detailControlsPopup.setVisibility(View.GONE);
|
||||||
spinnerToolbar.setVisibility(View.GONE);
|
spinnerToolbar.setVisibility(View.GONE);
|
||||||
thumbnailPlayButton.setImageResource(R.drawable.ic_headset_white_24dp);
|
thumbnailPlayButton.setImageResource(R.drawable.ic_headset_shadow);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1265,6 +1305,31 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
|
||||||
tabLayout.setVisibility(View.GONE);
|
tabLayout.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void displayUploaderAsSubChannel(final StreamInfo info) {
|
||||||
|
subChannelTextView.setText(info.getUploaderName());
|
||||||
|
subChannelTextView.setVisibility(View.VISIBLE);
|
||||||
|
subChannelTextView.setSelected(true);
|
||||||
|
uploaderTextView.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void displayBothUploaderAndSubChannel(final StreamInfo info) {
|
||||||
|
subChannelTextView.setText(info.getSubChannelName());
|
||||||
|
subChannelTextView.setVisibility(View.VISIBLE);
|
||||||
|
subChannelTextView.setSelected(true);
|
||||||
|
|
||||||
|
subChannelThumb.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
if (!TextUtils.isEmpty(info.getUploaderName())) {
|
||||||
|
uploaderTextView.setText(
|
||||||
|
String.format(getString(R.string.video_detail_by), info.getUploaderName()));
|
||||||
|
uploaderTextView.setVisibility(View.VISIBLE);
|
||||||
|
uploaderTextView.setSelected(true);
|
||||||
|
} else {
|
||||||
|
uploaderTextView.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public void openDownloadDialog() {
|
public void openDownloadDialog() {
|
||||||
try {
|
try {
|
||||||
DownloadDialog downloadDialog = DownloadDialog.newInstance(currentInfo);
|
DownloadDialog downloadDialog = DownloadDialog.newInstance(currentInfo);
|
||||||
|
|
|
@ -21,6 +21,7 @@ import android.widget.TextView;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.ActionBar;
|
import androidx.appcompat.app.ActionBar;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
import com.jakewharton.rxbinding2.view.RxView;
|
import com.jakewharton.rxbinding2.view.RxView;
|
||||||
|
@ -38,6 +39,7 @@ import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
|
||||||
import org.schabi.newpipe.local.subscription.SubscriptionManager;
|
import org.schabi.newpipe.local.subscription.SubscriptionManager;
|
||||||
import org.schabi.newpipe.player.playqueue.ChannelPlayQueue;
|
import org.schabi.newpipe.player.playqueue.ChannelPlayQueue;
|
||||||
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
||||||
|
import org.schabi.newpipe.report.ErrorActivity;
|
||||||
import org.schabi.newpipe.report.UserAction;
|
import org.schabi.newpipe.report.UserAction;
|
||||||
import org.schabi.newpipe.util.AnimationUtils;
|
import org.schabi.newpipe.util.AnimationUtils;
|
||||||
import org.schabi.newpipe.util.ExtractorHelper;
|
import org.schabi.newpipe.util.ExtractorHelper;
|
||||||
|
@ -45,6 +47,7 @@ import org.schabi.newpipe.util.ImageDisplayConstants;
|
||||||
import org.schabi.newpipe.util.Localization;
|
import org.schabi.newpipe.util.Localization;
|
||||||
import org.schabi.newpipe.util.NavigationHelper;
|
import org.schabi.newpipe.util.NavigationHelper;
|
||||||
import org.schabi.newpipe.util.ShareUtils;
|
import org.schabi.newpipe.util.ShareUtils;
|
||||||
|
import org.schabi.newpipe.util.ThemeHelper;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
@ -65,7 +68,8 @@ import static org.schabi.newpipe.util.AnimationUtils.animateBackgroundColor;
|
||||||
import static org.schabi.newpipe.util.AnimationUtils.animateTextColor;
|
import static org.schabi.newpipe.util.AnimationUtils.animateTextColor;
|
||||||
import static org.schabi.newpipe.util.AnimationUtils.animateView;
|
import static org.schabi.newpipe.util.AnimationUtils.animateView;
|
||||||
|
|
||||||
public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
|
||||||
|
implements View.OnClickListener {
|
||||||
private static final int BUTTON_DEBOUNCE_INTERVAL = 100;
|
private static final int BUTTON_DEBOUNCE_INTERVAL = 100;
|
||||||
private final CompositeDisposable disposables = new CompositeDisposable();
|
private final CompositeDisposable disposables = new CompositeDisposable();
|
||||||
private Disposable subscribeButtonMonitor;
|
private Disposable subscribeButtonMonitor;
|
||||||
|
@ -79,6 +83,8 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||||
private ImageView headerChannelBanner;
|
private ImageView headerChannelBanner;
|
||||||
private ImageView headerAvatarView;
|
private ImageView headerAvatarView;
|
||||||
private TextView headerTitleView;
|
private TextView headerTitleView;
|
||||||
|
private ImageView headerSubChannelAvatarView;
|
||||||
|
private TextView headerSubChannelTitleView;
|
||||||
private TextView headerSubscribersTextView;
|
private TextView headerSubscribersTextView;
|
||||||
private Button headerSubscribeButton;
|
private Button headerSubscribeButton;
|
||||||
private View playlistCtrl;
|
private View playlistCtrl;
|
||||||
|
@ -156,7 +162,10 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||||
headerSubscribersTextView = headerRootLayout.findViewById(R.id.channel_subscriber_view);
|
headerSubscribersTextView = headerRootLayout.findViewById(R.id.channel_subscriber_view);
|
||||||
headerSubscribeButton = headerRootLayout.findViewById(R.id.channel_subscribe_button);
|
headerSubscribeButton = headerRootLayout.findViewById(R.id.channel_subscribe_button);
|
||||||
playlistCtrl = headerRootLayout.findViewById(R.id.playlist_control);
|
playlistCtrl = headerRootLayout.findViewById(R.id.playlist_control);
|
||||||
|
headerSubChannelAvatarView =
|
||||||
|
headerRootLayout.findViewById(R.id.sub_channel_avatar_view);
|
||||||
|
headerSubChannelTitleView =
|
||||||
|
headerRootLayout.findViewById(R.id.sub_channel_title_view);
|
||||||
|
|
||||||
headerPlayAllButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_all_button);
|
headerPlayAllButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_all_button);
|
||||||
headerPopupButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_popup_button);
|
headerPopupButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_popup_button);
|
||||||
|
@ -165,6 +174,14 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||||
return headerRootLayout;
|
return headerRootLayout;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void initListeners() {
|
||||||
|
super.initListeners();
|
||||||
|
|
||||||
|
headerSubChannelTitleView.setOnClickListener(this);
|
||||||
|
headerSubChannelAvatarView.setOnClickListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Menu
|
// Menu
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
@ -358,8 +375,8 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||||
int backgroundDuration = isButtonVisible ? 300 : 0;
|
int backgroundDuration = isButtonVisible ? 300 : 0;
|
||||||
int textDuration = isButtonVisible ? 200 : 0;
|
int textDuration = isButtonVisible ? 200 : 0;
|
||||||
|
|
||||||
int subscribeBackground = ContextCompat
|
int subscribeBackground = ThemeHelper
|
||||||
.getColor(activity, R.color.subscribe_background_color);
|
.resolveColorFromAttr(activity, R.attr.colorPrimary);
|
||||||
int subscribeText = ContextCompat.getColor(activity, R.color.subscribe_text_color);
|
int subscribeText = ContextCompat.getColor(activity, R.color.subscribe_text_color);
|
||||||
int subscribedBackground = ContextCompat
|
int subscribedBackground = ContextCompat
|
||||||
.getColor(activity, R.color.subscribed_background_color);
|
.getColor(activity, R.color.subscribed_background_color);
|
||||||
|
@ -394,6 +411,34 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||||
return ExtractorHelper.getChannelInfo(serviceId, url, forceLoad);
|
return ExtractorHelper.getChannelInfo(serviceId, url, forceLoad);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
// OnClick
|
||||||
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(final View v) {
|
||||||
|
if (isLoading.get() || currentInfo == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (v.getId()) {
|
||||||
|
case R.id.sub_channel_avatar_view:
|
||||||
|
case R.id.sub_channel_title_view:
|
||||||
|
if (!TextUtils.isEmpty(currentInfo.getParentChannelUrl())) {
|
||||||
|
try {
|
||||||
|
NavigationHelper.openChannelFragment(getFragmentManager(),
|
||||||
|
currentInfo.getServiceId(), currentInfo.getParentChannelUrl(),
|
||||||
|
currentInfo.getParentChannelName());
|
||||||
|
} catch (Exception e) {
|
||||||
|
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
|
||||||
|
}
|
||||||
|
} else if (DEBUG) {
|
||||||
|
Log.i(TAG, "Can't open parent channel because we got no channel URL");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Contract
|
// Contract
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
@ -404,6 +449,7 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||||
|
|
||||||
IMAGE_LOADER.cancelDisplayTask(headerChannelBanner);
|
IMAGE_LOADER.cancelDisplayTask(headerChannelBanner);
|
||||||
IMAGE_LOADER.cancelDisplayTask(headerAvatarView);
|
IMAGE_LOADER.cancelDisplayTask(headerAvatarView);
|
||||||
|
IMAGE_LOADER.cancelDisplayTask(headerSubChannelAvatarView);
|
||||||
animateView(headerSubscribeButton, false, 100);
|
animateView(headerSubscribeButton, false, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -416,6 +462,8 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||||
ImageDisplayConstants.DISPLAY_BANNER_OPTIONS);
|
ImageDisplayConstants.DISPLAY_BANNER_OPTIONS);
|
||||||
IMAGE_LOADER.displayImage(result.getAvatarUrl(), headerAvatarView,
|
IMAGE_LOADER.displayImage(result.getAvatarUrl(), headerAvatarView,
|
||||||
ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS);
|
ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS);
|
||||||
|
IMAGE_LOADER.displayImage(result.getParentChannelAvatarUrl(), headerSubChannelAvatarView,
|
||||||
|
ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS);
|
||||||
|
|
||||||
headerSubscribersTextView.setVisibility(View.VISIBLE);
|
headerSubscribersTextView.setVisibility(View.VISIBLE);
|
||||||
if (result.getSubscriberCount() >= 0) {
|
if (result.getSubscriberCount() >= 0) {
|
||||||
|
@ -425,6 +473,17 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||||
headerSubscribersTextView.setText(R.string.subscribers_count_not_available);
|
headerSubscribersTextView.setText(R.string.subscribers_count_not_available);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!TextUtils.isEmpty(currentInfo.getParentChannelName())) {
|
||||||
|
headerSubChannelTitleView.setText(String.format(
|
||||||
|
getString(R.string.channel_created_by),
|
||||||
|
currentInfo.getParentChannelName())
|
||||||
|
);
|
||||||
|
headerSubChannelTitleView.setVisibility(View.VISIBLE);
|
||||||
|
headerSubChannelAvatarView.setVisibility(View.VISIBLE);
|
||||||
|
} else {
|
||||||
|
headerSubChannelTitleView.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
if (menuRssButton != null) {
|
if (menuRssButton != null) {
|
||||||
menuRssButton.setVisible(!TextUtils.isEmpty(result.getFeedUrl()));
|
menuRssButton.setVisible(!TextUtils.isEmpty(result.getFeedUrl()));
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,12 +41,12 @@ import org.schabi.newpipe.extractor.StreamingService;
|
||||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||||
import org.schabi.newpipe.extractor.search.SearchExtractor;
|
import org.schabi.newpipe.extractor.search.SearchExtractor;
|
||||||
import org.schabi.newpipe.extractor.search.SearchInfo;
|
import org.schabi.newpipe.extractor.search.SearchInfo;
|
||||||
import org.schabi.newpipe.util.AndroidTvUtils;
|
|
||||||
import org.schabi.newpipe.fragments.BackPressable;
|
import org.schabi.newpipe.fragments.BackPressable;
|
||||||
import org.schabi.newpipe.fragments.list.BaseListFragment;
|
import org.schabi.newpipe.fragments.list.BaseListFragment;
|
||||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||||
import org.schabi.newpipe.report.ErrorActivity;
|
import org.schabi.newpipe.report.ErrorActivity;
|
||||||
import org.schabi.newpipe.report.UserAction;
|
import org.schabi.newpipe.report.UserAction;
|
||||||
|
import org.schabi.newpipe.util.AndroidTvUtils;
|
||||||
import org.schabi.newpipe.util.AnimationUtils;
|
import org.schabi.newpipe.util.AnimationUtils;
|
||||||
import org.schabi.newpipe.util.Constants;
|
import org.schabi.newpipe.util.Constants;
|
||||||
import org.schabi.newpipe.util.ExtractorHelper;
|
import org.schabi.newpipe.util.ExtractorHelper;
|
||||||
|
|
|
@ -117,8 +117,8 @@ public class SuggestionListAdapter
|
||||||
queryView = rootView.findViewById(R.id.suggestion_search);
|
queryView = rootView.findViewById(R.id.suggestion_search);
|
||||||
insertView = rootView.findViewById(R.id.suggestion_insert);
|
insertView = rootView.findViewById(R.id.suggestion_insert);
|
||||||
|
|
||||||
historyResId = resolveResourceIdFromAttr(rootView.getContext(), R.attr.history);
|
historyResId = resolveResourceIdFromAttr(rootView.getContext(), R.attr.ic_history);
|
||||||
searchResId = resolveResourceIdFromAttr(rootView.getContext(), R.attr.search);
|
searchResId = resolveResourceIdFromAttr(rootView.getContext(), R.attr.ic_search);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int resolveResourceIdFromAttr(final Context context,
|
private static int resolveResourceIdFromAttr(final Context context,
|
||||||
|
|
|
@ -48,6 +48,6 @@ public class CommentsInfoItemHolder extends CommentsMiniInfoItemHolder {
|
||||||
}
|
}
|
||||||
final CommentsInfoItem item = (CommentsInfoItem) infoItem;
|
final CommentsInfoItem item = (CommentsInfoItem) infoItem;
|
||||||
|
|
||||||
itemTitleView.setText(item.getAuthorName());
|
itemTitleView.setText(item.getUploaderName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package org.schabi.newpipe.info_list.holder;
|
||||||
import android.content.ClipData;
|
import android.content.ClipData;
|
||||||
import android.content.ClipboardManager;
|
import android.content.ClipboardManager;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.text.TextUtils;
|
||||||
import android.text.method.LinkMovementMethod;
|
import android.text.method.LinkMovementMethod;
|
||||||
import android.text.style.URLSpan;
|
import android.text.style.URLSpan;
|
||||||
import android.text.util.Linkify;
|
import android.text.util.Linkify;
|
||||||
|
@ -12,7 +13,6 @@ import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
|
||||||
import org.jsoup.helper.StringUtil;
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.extractor.InfoItem;
|
import org.schabi.newpipe.extractor.InfoItem;
|
||||||
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
|
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
|
||||||
|
@ -89,7 +89,7 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
|
||||||
final CommentsInfoItem item = (CommentsInfoItem) infoItem;
|
final CommentsInfoItem item = (CommentsInfoItem) infoItem;
|
||||||
|
|
||||||
itemBuilder.getImageLoader()
|
itemBuilder.getImageLoader()
|
||||||
.displayImage(item.getAuthorThumbnail(),
|
.displayImage(item.getUploaderAvatarUrl(),
|
||||||
itemThumbnailView,
|
itemThumbnailView,
|
||||||
ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS);
|
ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS);
|
||||||
|
|
||||||
|
@ -114,10 +114,10 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
|
||||||
itemLikesCountView.setText("-");
|
itemLikesCountView.setText("-");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.getPublishedTime() != null) {
|
if (item.getUploadDate() != null) {
|
||||||
itemPublishedTime.setText(Localization.relativeTime(item.getPublishedTime().date()));
|
itemPublishedTime.setText(Localization.relativeTime(item.getUploadDate().date()));
|
||||||
} else {
|
} else {
|
||||||
itemPublishedTime.setText(item.getTextualPublishedTime());
|
itemPublishedTime.setText(item.getTextualUploadDate());
|
||||||
}
|
}
|
||||||
|
|
||||||
itemView.setOnClickListener(view -> {
|
itemView.setOnClickListener(view -> {
|
||||||
|
@ -143,7 +143,7 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void openCommentAuthor(final CommentsInfoItem item) {
|
private void openCommentAuthor(final CommentsInfoItem item) {
|
||||||
if (StringUtil.isBlank(item.getAuthorEndpoint())) {
|
if (TextUtils.isEmpty(item.getUploaderUrl())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
@ -151,8 +151,8 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
|
||||||
NavigationHelper.openChannelFragment(
|
NavigationHelper.openChannelFragment(
|
||||||
activity.getSupportFragmentManager(),
|
activity.getSupportFragmentManager(),
|
||||||
item.getServiceId(),
|
item.getServiceId(),
|
||||||
item.getAuthorEndpoint(),
|
item.getUploaderUrl(),
|
||||||
item.getAuthorName());
|
item.getUploaderName());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
ErrorActivity.reportUiError((AppCompatActivity) itemBuilder.getContext(), e);
|
ErrorActivity.reportUiError((AppCompatActivity) itemBuilder.getContext(), e);
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,8 @@ import io.reactivex.Flowable
|
||||||
import io.reactivex.Maybe
|
import io.reactivex.Maybe
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
import io.reactivex.schedulers.Schedulers
|
import io.reactivex.schedulers.Schedulers
|
||||||
|
import java.util.Calendar
|
||||||
|
import java.util.Date
|
||||||
import org.schabi.newpipe.MainActivity.DEBUG
|
import org.schabi.newpipe.MainActivity.DEBUG
|
||||||
import org.schabi.newpipe.NewPipeDatabase
|
import org.schabi.newpipe.NewPipeDatabase
|
||||||
import org.schabi.newpipe.database.feed.model.FeedEntity
|
import org.schabi.newpipe.database.feed.model.FeedEntity
|
||||||
|
@ -16,8 +18,6 @@ import org.schabi.newpipe.database.stream.model.StreamEntity
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem
|
import org.schabi.newpipe.extractor.stream.StreamInfoItem
|
||||||
import org.schabi.newpipe.extractor.stream.StreamType
|
import org.schabi.newpipe.extractor.stream.StreamType
|
||||||
import org.schabi.newpipe.local.subscription.FeedGroupIcon
|
import org.schabi.newpipe.local.subscription.FeedGroupIcon
|
||||||
import java.util.*
|
|
||||||
import kotlin.collections.ArrayList
|
|
||||||
|
|
||||||
class FeedDatabaseManager(context: Context) {
|
class FeedDatabaseManager(context: Context) {
|
||||||
private val database = NewPipeDatabase.getInstance(context)
|
private val database = NewPipeDatabase.getInstance(context)
|
||||||
|
@ -70,8 +70,11 @@ class FeedDatabaseManager(context: Context) {
|
||||||
fun markAsOutdated(subscriptionId: Long) = feedTable
|
fun markAsOutdated(subscriptionId: Long) = feedTable
|
||||||
.setLastUpdatedForSubscription(FeedLastUpdatedEntity(subscriptionId, null))
|
.setLastUpdatedForSubscription(FeedLastUpdatedEntity(subscriptionId, null))
|
||||||
|
|
||||||
fun upsertAll(subscriptionId: Long, items: List<StreamInfoItem>,
|
fun upsertAll(
|
||||||
oldestAllowedDate: Date = FEED_OLDEST_ALLOWED_DATE.time) {
|
subscriptionId: Long,
|
||||||
|
items: List<StreamInfoItem>,
|
||||||
|
oldestAllowedDate: Date = FEED_OLDEST_ALLOWED_DATE.time
|
||||||
|
) {
|
||||||
val itemsToInsert = ArrayList<StreamInfoItem>()
|
val itemsToInsert = ArrayList<StreamInfoItem>()
|
||||||
loop@ for (streamItem in items) {
|
loop@ for (streamItem in items) {
|
||||||
val uploadDate = streamItem.uploadDate
|
val uploadDate = streamItem.uploadDate
|
||||||
|
@ -107,9 +110,9 @@ class FeedDatabaseManager(context: Context) {
|
||||||
if (DEBUG) Log.d(this::class.java.simpleName, "clear() → streamTable.deleteOrphans() → $deletedOrphans")
|
if (DEBUG) Log.d(this::class.java.simpleName, "clear() → streamTable.deleteOrphans() → $deletedOrphans")
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
// /////////////////////////////////////////////////////////////////////////
|
||||||
// Feed Groups
|
// Feed Groups
|
||||||
///////////////////////////////////////////////////////////////////////////
|
// /////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
fun subscriptionIdsForGroup(groupId: Long): Flowable<List<Long>> {
|
fun subscriptionIdsForGroup(groupId: Long): Flowable<List<Long>> {
|
||||||
return feedGroupTable.getSubscriptionIdsFor(groupId)
|
return feedGroupTable.getSubscriptionIdsFor(groupId)
|
||||||
|
@ -161,6 +164,5 @@ class FeedDatabaseManager(context: Context) {
|
||||||
FeedGroupEntity.GROUP_ALL_ID -> feedTable.oldestSubscriptionUpdateFromAll()
|
FeedGroupEntity.GROUP_ALL_ID -> feedTable.oldestSubscriptionUpdateFromAll()
|
||||||
else -> feedTable.oldestSubscriptionUpdate(groupId)
|
else -> feedTable.oldestSubscriptionUpdate(groupId)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,14 +22,28 @@ package org.schabi.newpipe.local.feed
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import android.view.*
|
import android.view.LayoutInflater
|
||||||
|
import android.view.Menu
|
||||||
|
import android.view.MenuInflater
|
||||||
|
import android.view.MenuItem
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
import androidx.lifecycle.ViewModelProviders
|
import androidx.lifecycle.ViewModelProviders
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import icepick.State
|
import icepick.State
|
||||||
import kotlinx.android.synthetic.main.error_retry.*
|
import java.util.Calendar
|
||||||
import kotlinx.android.synthetic.main.fragment_feed.*
|
import kotlinx.android.synthetic.main.error_retry.error_button_retry
|
||||||
|
import kotlinx.android.synthetic.main.error_retry.error_message_view
|
||||||
|
import kotlinx.android.synthetic.main.fragment_feed.empty_state_view
|
||||||
|
import kotlinx.android.synthetic.main.fragment_feed.error_panel
|
||||||
|
import kotlinx.android.synthetic.main.fragment_feed.items_list
|
||||||
|
import kotlinx.android.synthetic.main.fragment_feed.loading_progress_bar
|
||||||
|
import kotlinx.android.synthetic.main.fragment_feed.loading_progress_text
|
||||||
|
import kotlinx.android.synthetic.main.fragment_feed.refresh_root_view
|
||||||
|
import kotlinx.android.synthetic.main.fragment_feed.refresh_subtitle_text
|
||||||
|
import kotlinx.android.synthetic.main.fragment_feed.refresh_text
|
||||||
import org.schabi.newpipe.R
|
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.fragments.list.BaseListFragment
|
import org.schabi.newpipe.fragments.list.BaseListFragment
|
||||||
|
@ -37,7 +51,6 @@ import org.schabi.newpipe.local.feed.service.FeedLoadService
|
||||||
import org.schabi.newpipe.report.UserAction
|
import org.schabi.newpipe.report.UserAction
|
||||||
import org.schabi.newpipe.util.AnimationUtils.animateView
|
import org.schabi.newpipe.util.AnimationUtils.animateView
|
||||||
import org.schabi.newpipe.util.Localization
|
import org.schabi.newpipe.util.Localization
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
class FeedFragment : BaseListFragment<FeedState, Unit>() {
|
class FeedFragment : BaseListFragment<FeedState, Unit>() {
|
||||||
private lateinit var viewModel: FeedViewModel
|
private lateinit var viewModel: FeedViewModel
|
||||||
|
@ -98,9 +111,9 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
// /////////////////////////////////////////////////////////////////////////
|
||||||
// Menu
|
// Menu
|
||||||
///////////////////////////////////////////////////////////////////////////
|
// /////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||||
super.onCreateOptionsMenu(menu, inflater)
|
super.onCreateOptionsMenu(menu, inflater)
|
||||||
|
@ -150,9 +163,9 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
|
||||||
activity?.supportActionBar?.subtitle = null
|
activity?.supportActionBar?.subtitle = null
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
// /////////////////////////////////////////////////////////////////////////
|
||||||
// Handling
|
// Handling
|
||||||
///////////////////////////////////////////////////////////////////////////
|
// /////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
override fun showLoading() {
|
override fun showLoading() {
|
||||||
animateView(refresh_root_view, false, 0)
|
animateView(refresh_root_view, false, 0)
|
||||||
|
@ -259,7 +272,6 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun handleErrorState(errorState: FeedState.ErrorState): Boolean {
|
private fun handleErrorState(errorState: FeedState.ErrorState): Boolean {
|
||||||
hideLoading()
|
hideLoading()
|
||||||
errorState.error?.let {
|
errorState.error?.let {
|
||||||
|
@ -283,9 +295,9 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
|
||||||
refresh_text?.text = getString(R.string.feed_oldest_subscription_update, oldestSubscriptionUpdateText)
|
refresh_text?.text = getString(R.string.feed_oldest_subscription_update, oldestSubscriptionUpdateText)
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
// /////////////////////////////////////////////////////////////////////////
|
||||||
// Load Service Handling
|
// Load Service Handling
|
||||||
///////////////////////////////////////////////////////////////////////////
|
// /////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
override fun doInitialLoadLogic() {}
|
override fun doInitialLoadLogic() {}
|
||||||
override fun reloadContent() = triggerUpdate()
|
override fun reloadContent() = triggerUpdate()
|
||||||
|
|
|
@ -1,24 +1,24 @@
|
||||||
package org.schabi.newpipe.local.feed
|
package org.schabi.newpipe.local.feed
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
|
import java.util.Calendar
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem
|
import org.schabi.newpipe.extractor.stream.StreamInfoItem
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
sealed class FeedState {
|
sealed class FeedState {
|
||||||
data class ProgressState(
|
data class ProgressState(
|
||||||
val currentProgress: Int = -1,
|
val currentProgress: Int = -1,
|
||||||
val maxProgress: Int = -1,
|
val maxProgress: Int = -1,
|
||||||
@StringRes val progressMessage: Int = 0
|
@StringRes val progressMessage: Int = 0
|
||||||
) : FeedState()
|
) : FeedState()
|
||||||
|
|
||||||
data class LoadedState(
|
data class LoadedState(
|
||||||
val items: List<StreamInfoItem>,
|
val items: List<StreamInfoItem>,
|
||||||
val oldestUpdate: Calendar? = null,
|
val oldestUpdate: Calendar? = null,
|
||||||
val notLoadedCount: Long,
|
val notLoadedCount: Long,
|
||||||
val itemsErrors: List<Throwable> = emptyList()
|
val itemsErrors: List<Throwable> = emptyList()
|
||||||
) : FeedState()
|
) : FeedState()
|
||||||
|
|
||||||
data class ErrorState(
|
data class ErrorState(
|
||||||
val error: Throwable? = null
|
val error: Throwable? = null
|
||||||
) : FeedState()
|
) : FeedState()
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,13 +9,17 @@ import io.reactivex.Flowable
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
import io.reactivex.functions.Function4
|
import io.reactivex.functions.Function4
|
||||||
import io.reactivex.schedulers.Schedulers
|
import io.reactivex.schedulers.Schedulers
|
||||||
|
import java.util.Calendar
|
||||||
|
import java.util.Date
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
|
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem
|
import org.schabi.newpipe.extractor.stream.StreamInfoItem
|
||||||
import org.schabi.newpipe.local.feed.service.FeedEventManager
|
import org.schabi.newpipe.local.feed.service.FeedEventManager
|
||||||
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.*
|
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.ErrorResultEvent
|
||||||
|
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.IdleEvent
|
||||||
|
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.ProgressEvent
|
||||||
|
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.SuccessResultEvent
|
||||||
import org.schabi.newpipe.util.DEFAULT_THROTTLE_TIMEOUT
|
import org.schabi.newpipe.util.DEFAULT_THROTTLE_TIMEOUT
|
||||||
import java.util.*
|
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
|
|
||||||
class FeedViewModel(applicationContext: Context, val groupId: Long = FeedGroupEntity.GROUP_ALL_ID) : ViewModel() {
|
class FeedViewModel(applicationContext: Context, val groupId: Long = FeedGroupEntity.GROUP_ALL_ID) : ViewModel() {
|
||||||
class Factory(val context: Context, val groupId: Long = FeedGroupEntity.GROUP_ALL_ID) : ViewModelProvider.Factory {
|
class Factory(val context: Context, val groupId: Long = FeedGroupEntity.GROUP_ALL_ID) : ViewModelProvider.Factory {
|
||||||
|
@ -68,4 +72,4 @@ class FeedViewModel(applicationContext: Context, val groupId: Long = FeedGroupEn
|
||||||
}
|
}
|
||||||
|
|
||||||
private data class CombineResultHolder(val t1: FeedEventManager.Event, val t2: List<StreamInfoItem>, val t3: Long, val t4: Date?)
|
private data class CombineResultHolder(val t1: FeedEventManager.Event, val t2: List<StreamInfoItem>, val t3: Long, val t4: Date?)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,8 @@ package org.schabi.newpipe.local.feed.service
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import io.reactivex.Flowable
|
import io.reactivex.Flowable
|
||||||
import io.reactivex.processors.BehaviorProcessor
|
import io.reactivex.processors.BehaviorProcessor
|
||||||
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.IdleEvent
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
|
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.IdleEvent
|
||||||
|
|
||||||
object FeedEventManager {
|
object FeedEventManager {
|
||||||
private var processor: BehaviorProcessor<Event> = BehaviorProcessor.create()
|
private var processor: BehaviorProcessor<Event> = BehaviorProcessor.create()
|
||||||
|
@ -34,5 +34,4 @@ object FeedEventManager {
|
||||||
data class SuccessResultEvent(val itemsErrors: List<Throwable> = emptyList()) : Event()
|
data class SuccessResultEvent(val itemsErrors: List<Throwable> = emptyList()) : Event()
|
||||||
data class ErrorResultEvent(val error: Throwable) : Event()
|
data class ErrorResultEvent(val error: Throwable) : Event()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,6 +40,11 @@ import io.reactivex.functions.Consumer
|
||||||
import io.reactivex.functions.Function
|
import io.reactivex.functions.Function
|
||||||
import io.reactivex.processors.PublishProcessor
|
import io.reactivex.processors.PublishProcessor
|
||||||
import io.reactivex.schedulers.Schedulers
|
import io.reactivex.schedulers.Schedulers
|
||||||
|
import java.io.IOException
|
||||||
|
import java.util.Calendar
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
import org.reactivestreams.Subscriber
|
import org.reactivestreams.Subscriber
|
||||||
import org.reactivestreams.Subscription
|
import org.reactivestreams.Subscription
|
||||||
import org.schabi.newpipe.MainActivity.DEBUG
|
import org.schabi.newpipe.MainActivity.DEBUG
|
||||||
|
@ -49,17 +54,14 @@ import org.schabi.newpipe.extractor.ListInfo
|
||||||
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException
|
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem
|
import org.schabi.newpipe.extractor.stream.StreamInfoItem
|
||||||
import org.schabi.newpipe.local.feed.FeedDatabaseManager
|
import org.schabi.newpipe.local.feed.FeedDatabaseManager
|
||||||
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.*
|
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.ErrorResultEvent
|
||||||
|
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.IdleEvent
|
||||||
|
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.ProgressEvent
|
||||||
|
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.SuccessResultEvent
|
||||||
import org.schabi.newpipe.local.feed.service.FeedEventManager.postEvent
|
import org.schabi.newpipe.local.feed.service.FeedEventManager.postEvent
|
||||||
import org.schabi.newpipe.local.subscription.SubscriptionManager
|
import org.schabi.newpipe.local.subscription.SubscriptionManager
|
||||||
import org.schabi.newpipe.util.ExceptionUtils
|
import org.schabi.newpipe.util.ExceptionUtils
|
||||||
import org.schabi.newpipe.util.ExtractorHelper
|
import org.schabi.newpipe.util.ExtractorHelper
|
||||||
import java.io.IOException
|
|
||||||
import java.util.*
|
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
|
||||||
import kotlin.collections.ArrayList
|
|
||||||
|
|
||||||
class FeedLoadService : Service() {
|
class FeedLoadService : Service() {
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -94,9 +96,9 @@ class FeedLoadService : Service() {
|
||||||
private var disposables = CompositeDisposable()
|
private var disposables = CompositeDisposable()
|
||||||
private var notificationUpdater = PublishProcessor.create<String>()
|
private var notificationUpdater = PublishProcessor.create<String>()
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
// /////////////////////////////////////////////////////////////////////////
|
||||||
// Lifecycle
|
// Lifecycle
|
||||||
///////////////////////////////////////////////////////////////////////////
|
// /////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
|
@ -151,9 +153,9 @@ class FeedLoadService : Service() {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
// /////////////////////////////////////////////////////////////////////////
|
||||||
// Loading & Handling
|
// Loading & Handling
|
||||||
///////////////////////////////////////////////////////////////////////////
|
// /////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
private class RequestException(val subscriptionId: Long, message: String, cause: Throwable) : Exception(message, cause) {
|
private class RequestException(val subscriptionId: Long, message: String, cause: Throwable) : Exception(message, cause) {
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -312,7 +314,6 @@ class FeedLoadService : Service() {
|
||||||
feedResultsHolder.addErrors(RequestException.wrapList(subscriptionId, info))
|
feedResultsHolder.addErrors(RequestException.wrapList(subscriptionId, info))
|
||||||
feedDatabaseManager.markAsOutdated(subscriptionId)
|
feedDatabaseManager.markAsOutdated(subscriptionId)
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (notification.isOnError) {
|
} else if (notification.isOnError) {
|
||||||
val error = notification.error!!
|
val error = notification.error!!
|
||||||
feedResultsHolder.addError(error)
|
feedResultsHolder.addError(error)
|
||||||
|
@ -325,7 +326,6 @@ class FeedLoadService : Service() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private val errorHandlingConsumer: Consumer<Notification<Pair<Long, ListInfo<StreamInfoItem>>>>
|
private val errorHandlingConsumer: Consumer<Notification<Pair<Long, ListInfo<StreamInfoItem>>>>
|
||||||
get() = Consumer {
|
get() = Consumer {
|
||||||
if (it.isOnError) {
|
if (it.isOnError) {
|
||||||
|
@ -354,9 +354,9 @@ class FeedLoadService : Service() {
|
||||||
broadcastProgress()
|
broadcastProgress()
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
// /////////////////////////////////////////////////////////////////////////
|
||||||
// Notification
|
// Notification
|
||||||
///////////////////////////////////////////////////////////////////////////
|
// /////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
private lateinit var notificationManager: NotificationManagerCompat
|
private lateinit var notificationManager: NotificationManagerCompat
|
||||||
private lateinit var notificationBuilder: NotificationCompat.Builder
|
private lateinit var notificationBuilder: NotificationCompat.Builder
|
||||||
|
@ -412,9 +412,9 @@ class FeedLoadService : Service() {
|
||||||
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build())
|
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build())
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
// /////////////////////////////////////////////////////////////////////////
|
||||||
// Notification Actions
|
// Notification Actions
|
||||||
///////////////////////////////////////////////////////////////////////////
|
// /////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
private lateinit var broadcastReceiver: BroadcastReceiver
|
private lateinit var broadcastReceiver: BroadcastReceiver
|
||||||
private val cancelSignal = AtomicBoolean()
|
private val cancelSignal = AtomicBoolean()
|
||||||
|
@ -430,18 +430,18 @@ class FeedLoadService : Service() {
|
||||||
registerReceiver(broadcastReceiver, IntentFilter(ACTION_CANCEL))
|
registerReceiver(broadcastReceiver, IntentFilter(ACTION_CANCEL))
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
// /////////////////////////////////////////////////////////////////////////
|
||||||
// Error handling
|
// Error handling
|
||||||
///////////////////////////////////////////////////////////////////////////
|
// /////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
private fun handleError(error: Throwable) {
|
private fun handleError(error: Throwable) {
|
||||||
postEvent(ErrorResultEvent(error))
|
postEvent(ErrorResultEvent(error))
|
||||||
stopService()
|
stopService()
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
// /////////////////////////////////////////////////////////////////////////
|
||||||
// Results Holder
|
// Results Holder
|
||||||
///////////////////////////////////////////////////////////////////////////
|
// /////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
class ResultsHolder {
|
class ResultsHolder {
|
||||||
/**
|
/**
|
||||||
|
@ -463,4 +463,4 @@ class FeedLoadService : Service() {
|
||||||
itemsErrors = itemsErrorsHolder.toList()
|
itemsErrors = itemsErrorsHolder.toList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -362,14 +362,14 @@ public class StatisticsPlaylistFragment
|
||||||
if (sortMode == StatisticSortMode.LAST_PLAYED) {
|
if (sortMode == StatisticSortMode.LAST_PLAYED) {
|
||||||
sortMode = StatisticSortMode.MOST_PLAYED;
|
sortMode = StatisticSortMode.MOST_PLAYED;
|
||||||
setTitle(getString(R.string.title_most_played));
|
setTitle(getString(R.string.title_most_played));
|
||||||
sortButtonIcon
|
sortButtonIcon.setImageResource(
|
||||||
.setImageResource(ThemeHelper.getIconByAttr(R.attr.history, getContext()));
|
ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_history));
|
||||||
sortButtonText.setText(R.string.title_last_played);
|
sortButtonText.setText(R.string.title_last_played);
|
||||||
} else {
|
} else {
|
||||||
sortMode = StatisticSortMode.LAST_PLAYED;
|
sortMode = StatisticSortMode.LAST_PLAYED;
|
||||||
setTitle(getString(R.string.title_last_played));
|
setTitle(getString(R.string.title_last_played));
|
||||||
sortButtonIcon
|
sortButtonIcon.setImageResource(
|
||||||
.setImageResource(ThemeHelper.getIconByAttr(R.attr.filter, getContext()));
|
ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_filter_list));
|
||||||
sortButtonText.setText(R.string.title_most_played);
|
sortButtonText.setText(R.string.title_most_played);
|
||||||
}
|
}
|
||||||
startLoading(true);
|
startLoading(true);
|
||||||
|
|
|
@ -7,23 +7,23 @@ import org.schabi.newpipe.R
|
||||||
import org.schabi.newpipe.util.ThemeHelper
|
import org.schabi.newpipe.util.ThemeHelper
|
||||||
|
|
||||||
enum class FeedGroupIcon(
|
enum class FeedGroupIcon(
|
||||||
/**
|
/**
|
||||||
* The id that will be used to store and retrieve icons from some persistent storage (e.g. DB).
|
* The id that will be used to store and retrieve icons from some persistent storage (e.g. DB).
|
||||||
*/
|
*/
|
||||||
val id: Int,
|
val id: Int,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The attribute that points to a drawable resource. "R.attr" is used here to support multiple themes.
|
* The attribute that points to a drawable resource. "R.attr" is used here to support multiple themes.
|
||||||
*/
|
*/
|
||||||
@AttrRes val drawableResourceAttr: Int
|
@AttrRes val drawableResourceAttr: Int
|
||||||
) {
|
) {
|
||||||
ALL(0, R.attr.ic_asterisk),
|
ALL(0, R.attr.ic_asterisk),
|
||||||
MUSIC(1, R.attr.ic_music_note),
|
MUSIC(1, R.attr.ic_music_note),
|
||||||
EDUCATION(2, R.attr.ic_school),
|
EDUCATION(2, R.attr.ic_school),
|
||||||
FITNESS(3, R.attr.ic_fitness),
|
FITNESS(3, R.attr.ic_fitness_center),
|
||||||
SPACE(4, R.attr.ic_telescope),
|
SPACE(4, R.attr.ic_telescope),
|
||||||
COMPUTER(5, R.attr.ic_computer),
|
COMPUTER(5, R.attr.ic_computer),
|
||||||
GAMING(6, R.attr.ic_videogame),
|
GAMING(6, R.attr.ic_videogame_asset),
|
||||||
SPORTS(7, R.attr.ic_sports),
|
SPORTS(7, R.attr.ic_sports),
|
||||||
NEWS(8, R.attr.ic_megaphone),
|
NEWS(8, R.attr.ic_megaphone),
|
||||||
FAVORITES(9, R.attr.ic_heart),
|
FAVORITES(9, R.attr.ic_heart),
|
||||||
|
@ -32,32 +32,32 @@ enum class FeedGroupIcon(
|
||||||
TREND(12, R.attr.ic_trending_up),
|
TREND(12, R.attr.ic_trending_up),
|
||||||
MOVIE(13, R.attr.ic_movie),
|
MOVIE(13, R.attr.ic_movie),
|
||||||
BACKUP(14, R.attr.ic_backup),
|
BACKUP(14, R.attr.ic_backup),
|
||||||
ART(15, R.attr.palette),
|
ART(15, R.attr.ic_palette),
|
||||||
PERSON(16, R.attr.ic_person),
|
PERSON(16, R.attr.ic_person),
|
||||||
PEOPLE(17, R.attr.ic_people),
|
PEOPLE(17, R.attr.ic_people),
|
||||||
MONEY(18, R.attr.ic_money),
|
MONEY(18, R.attr.ic_money),
|
||||||
KIDS(19, R.attr.ic_kids),
|
KIDS(19, R.attr.ic_child_care),
|
||||||
FOOD(20, R.attr.ic_fastfood),
|
FOOD(20, R.attr.ic_fastfood),
|
||||||
SMILE(21, R.attr.ic_smile),
|
SMILE(21, R.attr.ic_smile),
|
||||||
EXPLORE(22, R.attr.ic_explore),
|
EXPLORE(22, R.attr.ic_explore),
|
||||||
RESTAURANT(23, R.attr.ic_restaurant),
|
RESTAURANT(23, R.attr.ic_restaurant),
|
||||||
MIC(24, R.attr.ic_mic),
|
MIC(24, R.attr.ic_mic),
|
||||||
HEADSET(25, R.attr.audio),
|
HEADSET(25, R.attr.ic_headset),
|
||||||
RADIO(26, R.attr.ic_radio),
|
RADIO(26, R.attr.ic_radio),
|
||||||
SHOPPING_CART(27, R.attr.ic_shopping_cart),
|
SHOPPING_CART(27, R.attr.ic_shopping_cart),
|
||||||
WATCH_LATER(28, R.attr.ic_watch_later),
|
WATCH_LATER(28, R.attr.ic_watch_later),
|
||||||
WORK(29, R.attr.ic_work),
|
WORK(29, R.attr.ic_work),
|
||||||
HOT(30, R.attr.ic_hot),
|
HOT(30, R.attr.ic_kiosk_hot),
|
||||||
CHANNEL(31, R.attr.ic_channel),
|
CHANNEL(31, R.attr.ic_channel),
|
||||||
BOOKMARK(32, R.attr.ic_bookmark),
|
BOOKMARK(32, R.attr.ic_bookmark),
|
||||||
PETS(33, R.attr.ic_pets),
|
PETS(33, R.attr.ic_pets),
|
||||||
WORLD(34, R.attr.ic_world),
|
WORLD(34, R.attr.ic_world),
|
||||||
STAR(35, R.attr.ic_stars),
|
STAR(35, R.attr.ic_stars),
|
||||||
SUN(36, R.attr.ic_sunny),
|
SUN(36, R.attr.ic_sunny),
|
||||||
RSS(37, R.attr.rss);
|
RSS(37, R.attr.ic_rss);
|
||||||
|
|
||||||
@DrawableRes
|
@DrawableRes
|
||||||
fun getDrawableRes(context: Context): Int {
|
fun getDrawableRes(context: Context): Int {
|
||||||
return ThemeHelper.resolveResourceIdFromAttr(context, drawableResourceAttr)
|
return ThemeHelper.resolveResourceIdFromAttr(context, drawableResourceAttr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,13 +2,21 @@ package org.schabi.newpipe.local.subscription
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.app.AlertDialog
|
import android.app.AlertDialog
|
||||||
import android.content.*
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.DialogInterface
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.IntentFilter
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import android.preference.PreferenceManager
|
import android.preference.PreferenceManager
|
||||||
import android.view.*
|
import android.view.LayoutInflater
|
||||||
|
import android.view.Menu
|
||||||
|
import android.view.MenuInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.lifecycle.ViewModelProviders
|
import androidx.lifecycle.ViewModelProviders
|
||||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||||
|
@ -21,8 +29,15 @@ import com.xwray.groupie.Section
|
||||||
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
|
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
|
||||||
import icepick.State
|
import icepick.State
|
||||||
import io.reactivex.disposables.CompositeDisposable
|
import io.reactivex.disposables.CompositeDisposable
|
||||||
import kotlinx.android.synthetic.main.dialog_title.view.*
|
import java.io.File
|
||||||
import kotlinx.android.synthetic.main.fragment_subscription.*
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Date
|
||||||
|
import java.util.Locale
|
||||||
|
import kotlin.math.floor
|
||||||
|
import kotlin.math.max
|
||||||
|
import kotlinx.android.synthetic.main.dialog_title.view.itemAdditionalDetails
|
||||||
|
import kotlinx.android.synthetic.main.dialog_title.view.itemTitleView
|
||||||
|
import kotlinx.android.synthetic.main.fragment_subscription.items_list
|
||||||
import org.schabi.newpipe.R
|
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.extractor.channel.ChannelInfoItem
|
import org.schabi.newpipe.extractor.channel.ChannelInfoItem
|
||||||
|
@ -30,21 +45,29 @@ import org.schabi.newpipe.fragments.BaseStateFragment
|
||||||
import org.schabi.newpipe.local.subscription.SubscriptionViewModel.SubscriptionState
|
import org.schabi.newpipe.local.subscription.SubscriptionViewModel.SubscriptionState
|
||||||
import org.schabi.newpipe.local.subscription.dialog.FeedGroupDialog
|
import org.schabi.newpipe.local.subscription.dialog.FeedGroupDialog
|
||||||
import org.schabi.newpipe.local.subscription.dialog.FeedGroupReorderDialog
|
import org.schabi.newpipe.local.subscription.dialog.FeedGroupReorderDialog
|
||||||
import org.schabi.newpipe.local.subscription.item.*
|
import org.schabi.newpipe.local.subscription.item.ChannelItem
|
||||||
|
import org.schabi.newpipe.local.subscription.item.EmptyPlaceholderItem
|
||||||
|
import org.schabi.newpipe.local.subscription.item.FeedGroupAddItem
|
||||||
|
import org.schabi.newpipe.local.subscription.item.FeedGroupCardItem
|
||||||
|
import org.schabi.newpipe.local.subscription.item.FeedGroupCarouselItem
|
||||||
|
import org.schabi.newpipe.local.subscription.item.FeedImportExportItem
|
||||||
|
import org.schabi.newpipe.local.subscription.item.HeaderWithMenuItem
|
||||||
import org.schabi.newpipe.local.subscription.item.HeaderWithMenuItem.Companion.PAYLOAD_UPDATE_VISIBILITY_MENU_ITEM
|
import org.schabi.newpipe.local.subscription.item.HeaderWithMenuItem.Companion.PAYLOAD_UPDATE_VISIBILITY_MENU_ITEM
|
||||||
import org.schabi.newpipe.local.subscription.services.SubscriptionsExportService
|
import org.schabi.newpipe.local.subscription.services.SubscriptionsExportService
|
||||||
import org.schabi.newpipe.local.subscription.services.SubscriptionsExportService.EXPORT_COMPLETE_ACTION
|
import org.schabi.newpipe.local.subscription.services.SubscriptionsExportService.EXPORT_COMPLETE_ACTION
|
||||||
import org.schabi.newpipe.local.subscription.services.SubscriptionsExportService.KEY_FILE_PATH
|
import org.schabi.newpipe.local.subscription.services.SubscriptionsExportService.KEY_FILE_PATH
|
||||||
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService
|
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService
|
||||||
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.*
|
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.IMPORT_COMPLETE_ACTION
|
||||||
|
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.KEY_MODE
|
||||||
|
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.KEY_VALUE
|
||||||
|
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.PREVIOUS_EXPORT_MODE
|
||||||
import org.schabi.newpipe.report.UserAction
|
import org.schabi.newpipe.report.UserAction
|
||||||
import org.schabi.newpipe.util.*
|
|
||||||
import org.schabi.newpipe.util.AnimationUtils.animateView
|
import org.schabi.newpipe.util.AnimationUtils.animateView
|
||||||
import java.io.File
|
import org.schabi.newpipe.util.FilePickerActivityHelper
|
||||||
import java.text.SimpleDateFormat
|
import org.schabi.newpipe.util.NavigationHelper
|
||||||
import java.util.*
|
import org.schabi.newpipe.util.OnClickGesture
|
||||||
import kotlin.math.floor
|
import org.schabi.newpipe.util.ShareUtils
|
||||||
import kotlin.math.max
|
import org.schabi.newpipe.util.ThemeHelper
|
||||||
|
|
||||||
class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
|
class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
|
||||||
private lateinit var viewModel: SubscriptionViewModel
|
private lateinit var viewModel: SubscriptionViewModel
|
||||||
|
@ -74,9 +97,9 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
|
||||||
setHasOptionsMenu(true)
|
setHasOptionsMenu(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
// /////////////////////////////////////////////////////////////////////////
|
||||||
// Fragment LifeCycle
|
// Fragment LifeCycle
|
||||||
///////////////////////////////////////////////////////////////////////////
|
// /////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
@ -120,9 +143,9 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
|
||||||
disposables.dispose()
|
disposables.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////
|
// ////////////////////////////////////////////////////////////////////////
|
||||||
// Menu
|
// Menu
|
||||||
//////////////////////////////////////////////////////////////////////////
|
// ////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||||
super.onCreateOptionsMenu(menu, inflater)
|
super.onCreateOptionsMenu(menu, inflater)
|
||||||
|
@ -150,7 +173,6 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
|
||||||
importExportItem.isExpanded = false
|
importExportItem.isExpanded = false
|
||||||
importExportItem.notifyChanged(FeedImportExportItem.REFRESH_EXPANDED_STATUS)
|
importExportItem.notifyChanged(FeedImportExportItem.REFRESH_EXPANDED_STATUS)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -198,9 +220,9 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////
|
// ////////////////////////////////////////////////////////////////////////
|
||||||
// Fragment Views
|
// Fragment Views
|
||||||
//////////////////////////////////////////////////////////////////////////
|
// ////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
private fun setupInitialLayout() {
|
private fun setupInitialLayout() {
|
||||||
Section().apply {
|
Section().apply {
|
||||||
|
@ -243,7 +265,6 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
|
||||||
{ onExportSelected() },
|
{ onExportSelected() },
|
||||||
importExportItemExpandedState ?: false)
|
importExportItemExpandedState ?: false)
|
||||||
groupAdapter.add(Section(importExportItem, listOf(subscriptionsSection)))
|
groupAdapter.add(Section(importExportItem, listOf(subscriptionsSection)))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun initViews(rootView: View, savedInstanceState: Bundle?) {
|
override fun initViews(rootView: View, savedInstanceState: Bundle?) {
|
||||||
|
@ -366,9 +387,9 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
|
||||||
items_list.post { feedGroupsSortMenuItem.notifyChanged(PAYLOAD_UPDATE_VISIBILITY_MENU_ITEM) }
|
items_list.post { feedGroupsSortMenuItem.notifyChanged(PAYLOAD_UPDATE_VISIBILITY_MENU_ITEM) }
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
// /////////////////////////////////////////////////////////////////////////
|
||||||
// Contract
|
// Contract
|
||||||
///////////////////////////////////////////////////////////////////////////
|
// /////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
override fun showLoading() {
|
override fun showLoading() {
|
||||||
super.showLoading()
|
super.showLoading()
|
||||||
|
@ -380,9 +401,9 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
|
||||||
animateView(items_list, true, 200)
|
animateView(items_list, true, 200)
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
// /////////////////////////////////////////////////////////////////////////
|
||||||
// Fragment Error Handling
|
// Fragment Error Handling
|
||||||
///////////////////////////////////////////////////////////////////////////
|
// /////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
override fun onError(exception: Throwable): Boolean {
|
override fun onError(exception: Throwable): Boolean {
|
||||||
if (super.onError(exception)) return true
|
if (super.onError(exception)) return true
|
||||||
|
@ -391,9 +412,9 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
// /////////////////////////////////////////////////////////////////////////
|
||||||
// Grid Mode
|
// Grid Mode
|
||||||
///////////////////////////////////////////////////////////////////////////
|
// /////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
// TODO: Move these out of this class, as it can be reused
|
// TODO: Move these out of this class, as it can be reused
|
||||||
|
|
||||||
|
@ -405,8 +426,8 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
|
||||||
getString(R.string.list_view_mode_auto_key) -> {
|
getString(R.string.list_view_mode_auto_key) -> {
|
||||||
val configuration = resources.configuration
|
val configuration = resources.configuration
|
||||||
|
|
||||||
(configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
|
(configuration.orientation == Configuration.ORIENTATION_LANDSCAPE &&
|
||||||
&& configuration.isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_LARGE))
|
configuration.isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_LARGE))
|
||||||
}
|
}
|
||||||
getString(R.string.list_view_mode_grid_key) -> true
|
getString(R.string.list_view_mode_grid_key) -> true
|
||||||
else -> false
|
else -> false
|
||||||
|
|
|
@ -6,11 +6,11 @@ import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import com.xwray.groupie.Group
|
import com.xwray.groupie.Group
|
||||||
import io.reactivex.schedulers.Schedulers
|
import io.reactivex.schedulers.Schedulers
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
import org.schabi.newpipe.local.feed.FeedDatabaseManager
|
import org.schabi.newpipe.local.feed.FeedDatabaseManager
|
||||||
import org.schabi.newpipe.local.subscription.item.ChannelItem
|
import org.schabi.newpipe.local.subscription.item.ChannelItem
|
||||||
import org.schabi.newpipe.local.subscription.item.FeedGroupCardItem
|
import org.schabi.newpipe.local.subscription.item.FeedGroupCardItem
|
||||||
import org.schabi.newpipe.util.DEFAULT_THROTTLE_TIMEOUT
|
import org.schabi.newpipe.util.DEFAULT_THROTTLE_TIMEOUT
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
|
|
||||||
class SubscriptionViewModel(application: Application) : AndroidViewModel(application) {
|
class SubscriptionViewModel(application: Application) : AndroidViewModel(application) {
|
||||||
private var feedDatabaseManager: FeedDatabaseManager = FeedDatabaseManager(application)
|
private var feedDatabaseManager: FeedDatabaseManager = FeedDatabaseManager(application)
|
||||||
|
|
|
@ -32,4 +32,4 @@ class FeedGroupCarouselDecoration(context: Context) : RecyclerView.ItemDecoratio
|
||||||
outRect.right = marginStartEnd
|
outRect.right = marginStartEnd
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,19 +22,36 @@ import com.xwray.groupie.Section
|
||||||
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
|
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
|
||||||
import icepick.Icepick
|
import icepick.Icepick
|
||||||
import icepick.State
|
import icepick.State
|
||||||
import kotlinx.android.synthetic.main.dialog_feed_group_create.*
|
import java.io.Serializable
|
||||||
|
import kotlinx.android.synthetic.main.dialog_feed_group_create.cancel_button
|
||||||
|
import kotlinx.android.synthetic.main.dialog_feed_group_create.confirm_button
|
||||||
|
import kotlinx.android.synthetic.main.dialog_feed_group_create.delete_button
|
||||||
|
import kotlinx.android.synthetic.main.dialog_feed_group_create.delete_screen_message
|
||||||
|
import kotlinx.android.synthetic.main.dialog_feed_group_create.group_name_input
|
||||||
|
import kotlinx.android.synthetic.main.dialog_feed_group_create.group_name_input_container
|
||||||
|
import kotlinx.android.synthetic.main.dialog_feed_group_create.icon_preview
|
||||||
|
import kotlinx.android.synthetic.main.dialog_feed_group_create.icon_selector
|
||||||
|
import kotlinx.android.synthetic.main.dialog_feed_group_create.options_root
|
||||||
|
import kotlinx.android.synthetic.main.dialog_feed_group_create.select_channel_button
|
||||||
|
import kotlinx.android.synthetic.main.dialog_feed_group_create.selected_subscription_count_view
|
||||||
|
import kotlinx.android.synthetic.main.dialog_feed_group_create.separator
|
||||||
|
import kotlinx.android.synthetic.main.dialog_feed_group_create.subscriptions_selector
|
||||||
|
import kotlinx.android.synthetic.main.dialog_feed_group_create.subscriptions_selector_header_info
|
||||||
|
import kotlinx.android.synthetic.main.dialog_feed_group_create.subscriptions_selector_list
|
||||||
import org.schabi.newpipe.R
|
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.subscription.SubscriptionEntity
|
import org.schabi.newpipe.database.subscription.SubscriptionEntity
|
||||||
import org.schabi.newpipe.local.subscription.FeedGroupIcon
|
import org.schabi.newpipe.local.subscription.FeedGroupIcon
|
||||||
import org.schabi.newpipe.local.subscription.dialog.FeedGroupDialog.ScreenState.*
|
import org.schabi.newpipe.local.subscription.dialog.FeedGroupDialog.ScreenState.DeleteScreen
|
||||||
|
import org.schabi.newpipe.local.subscription.dialog.FeedGroupDialog.ScreenState.IconPickerScreen
|
||||||
|
import org.schabi.newpipe.local.subscription.dialog.FeedGroupDialog.ScreenState.InitialScreen
|
||||||
|
import org.schabi.newpipe.local.subscription.dialog.FeedGroupDialog.ScreenState.SubscriptionsPickerScreen
|
||||||
import org.schabi.newpipe.local.subscription.dialog.FeedGroupDialogViewModel.DialogEvent.ProcessingEvent
|
import org.schabi.newpipe.local.subscription.dialog.FeedGroupDialogViewModel.DialogEvent.ProcessingEvent
|
||||||
import org.schabi.newpipe.local.subscription.dialog.FeedGroupDialogViewModel.DialogEvent.SuccessEvent
|
import org.schabi.newpipe.local.subscription.dialog.FeedGroupDialogViewModel.DialogEvent.SuccessEvent
|
||||||
import org.schabi.newpipe.local.subscription.item.EmptyPlaceholderItem
|
import org.schabi.newpipe.local.subscription.item.EmptyPlaceholderItem
|
||||||
import org.schabi.newpipe.local.subscription.item.PickerIconItem
|
import org.schabi.newpipe.local.subscription.item.PickerIconItem
|
||||||
import org.schabi.newpipe.local.subscription.item.PickerSubscriptionItem
|
import org.schabi.newpipe.local.subscription.item.PickerSubscriptionItem
|
||||||
import org.schabi.newpipe.util.ThemeHelper
|
import org.schabi.newpipe.util.ThemeHelper
|
||||||
import java.io.Serializable
|
|
||||||
|
|
||||||
class FeedGroupDialog : DialogFragment() {
|
class FeedGroupDialog : DialogFragment() {
|
||||||
private lateinit var viewModel: FeedGroupDialogViewModel
|
private lateinit var viewModel: FeedGroupDialogViewModel
|
||||||
|
@ -120,9 +137,9 @@ class FeedGroupDialog : DialogFragment() {
|
||||||
showScreen(currentScreen)
|
showScreen(currentScreen)
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
// /////////////////////////////////////////////////////////////////////////
|
||||||
// Setup
|
// Setup
|
||||||
///////////////////////////////////////////////////////////////////////////
|
// /////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
private fun setupListeners() {
|
private fun setupListeners() {
|
||||||
delete_button.setOnClickListener { showScreen(DeleteScreen) }
|
delete_button.setOnClickListener { showScreen(DeleteScreen) }
|
||||||
|
@ -294,9 +311,9 @@ class FeedGroupDialog : DialogFragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
// /////////////////////////////////////////////////////////////////////////
|
||||||
// Screen Selector
|
// Screen Selector
|
||||||
///////////////////////////////////////////////////////////////////////////
|
// /////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
private fun showScreen(screen: ScreenState) {
|
private fun showScreen(screen: ScreenState) {
|
||||||
currentScreen = screen
|
currentScreen = screen
|
||||||
|
@ -330,9 +347,9 @@ class FeedGroupDialog : DialogFragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
// /////////////////////////////////////////////////////////////////////////
|
||||||
// Utils
|
// Utils
|
||||||
///////////////////////////////////////////////////////////////////////////
|
// /////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
private fun hideKeyboard() {
|
private fun hideKeyboard() {
|
||||||
val inputMethodManager = requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
val inputMethodManager = requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
|
@ -363,4 +380,4 @@ class FeedGroupDialog : DialogFragment() {
|
||||||
return dialog
|
return dialog
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,6 @@ import org.schabi.newpipe.local.feed.FeedDatabaseManager
|
||||||
import org.schabi.newpipe.local.subscription.FeedGroupIcon
|
import org.schabi.newpipe.local.subscription.FeedGroupIcon
|
||||||
import org.schabi.newpipe.local.subscription.SubscriptionManager
|
import org.schabi.newpipe.local.subscription.SubscriptionManager
|
||||||
|
|
||||||
|
|
||||||
class FeedGroupDialogViewModel(applicationContext: Context, val groupId: Long = FeedGroupEntity.GROUP_ALL_ID) : ViewModel() {
|
class FeedGroupDialogViewModel(applicationContext: Context, val groupId: Long = FeedGroupEntity.GROUP_ALL_ID) : ViewModel() {
|
||||||
class Factory(val context: Context, val groupId: Long = FeedGroupEntity.GROUP_ALL_ID) : ViewModelProvider.Factory {
|
class Factory(val context: Context, val groupId: Long = FeedGroupEntity.GROUP_ALL_ID) : ViewModelProvider.Factory {
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
@ -84,4 +83,4 @@ class FeedGroupDialogViewModel(applicationContext: Context, val groupId: Long =
|
||||||
object ProcessingEvent : DialogEvent()
|
object ProcessingEvent : DialogEvent()
|
||||||
object SuccessEvent : DialogEvent()
|
object SuccessEvent : DialogEvent()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,15 +16,15 @@ import com.xwray.groupie.TouchCallback
|
||||||
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
|
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
|
||||||
import icepick.Icepick
|
import icepick.Icepick
|
||||||
import icepick.State
|
import icepick.State
|
||||||
import kotlinx.android.synthetic.main.dialog_feed_group_reorder.*
|
import java.util.Collections
|
||||||
|
import kotlinx.android.synthetic.main.dialog_feed_group_reorder.confirm_button
|
||||||
|
import kotlinx.android.synthetic.main.dialog_feed_group_reorder.feed_groups_list
|
||||||
import org.schabi.newpipe.R
|
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.local.subscription.dialog.FeedGroupReorderDialogViewModel.DialogEvent.ProcessingEvent
|
import org.schabi.newpipe.local.subscription.dialog.FeedGroupReorderDialogViewModel.DialogEvent.ProcessingEvent
|
||||||
import org.schabi.newpipe.local.subscription.dialog.FeedGroupReorderDialogViewModel.DialogEvent.SuccessEvent
|
import org.schabi.newpipe.local.subscription.dialog.FeedGroupReorderDialogViewModel.DialogEvent.SuccessEvent
|
||||||
import org.schabi.newpipe.local.subscription.item.FeedGroupReorderItem
|
import org.schabi.newpipe.local.subscription.item.FeedGroupReorderItem
|
||||||
import org.schabi.newpipe.util.ThemeHelper
|
import org.schabi.newpipe.util.ThemeHelper
|
||||||
import java.util.*
|
|
||||||
import kotlin.collections.ArrayList
|
|
||||||
|
|
||||||
class FeedGroupReorderDialog : DialogFragment() {
|
class FeedGroupReorderDialog : DialogFragment() {
|
||||||
private lateinit var viewModel: FeedGroupReorderDialogViewModel
|
private lateinit var viewModel: FeedGroupReorderDialogViewModel
|
||||||
|
@ -93,8 +93,11 @@ class FeedGroupReorderDialog : DialogFragment() {
|
||||||
private fun getItemTouchCallback(): SimpleCallback {
|
private fun getItemTouchCallback(): SimpleCallback {
|
||||||
return object : TouchCallback() {
|
return object : TouchCallback() {
|
||||||
|
|
||||||
override fun onMove(recyclerView: RecyclerView, source: RecyclerView.ViewHolder,
|
override fun onMove(
|
||||||
target: RecyclerView.ViewHolder): Boolean {
|
recyclerView: RecyclerView,
|
||||||
|
source: RecyclerView.ViewHolder,
|
||||||
|
target: RecyclerView.ViewHolder
|
||||||
|
): Boolean {
|
||||||
val sourceIndex = source.adapterPosition
|
val sourceIndex = source.adapterPosition
|
||||||
val targetIndex = target.adapterPosition
|
val targetIndex = target.adapterPosition
|
||||||
|
|
||||||
|
@ -109,4 +112,4 @@ class FeedGroupReorderDialog : DialogFragment() {
|
||||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, swipeDir: Int) {}
|
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, swipeDir: Int) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,4 +49,4 @@ class FeedGroupReorderDialogViewModel(application: Application) : AndroidViewMod
|
||||||
object ProcessingEvent : DialogEvent()
|
object ProcessingEvent : DialogEvent()
|
||||||
object SuccessEvent : DialogEvent()
|
object SuccessEvent : DialogEvent()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,19 +4,21 @@ import android.content.Context
|
||||||
import com.nostra13.universalimageloader.core.ImageLoader
|
import com.nostra13.universalimageloader.core.ImageLoader
|
||||||
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
|
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
|
||||||
import com.xwray.groupie.kotlinandroidextensions.Item
|
import com.xwray.groupie.kotlinandroidextensions.Item
|
||||||
import kotlinx.android.synthetic.main.list_channel_item.*
|
import kotlinx.android.synthetic.main.list_channel_item.itemAdditionalDetails
|
||||||
|
import kotlinx.android.synthetic.main.list_channel_item.itemChannelDescriptionView
|
||||||
|
import kotlinx.android.synthetic.main.list_channel_item.itemThumbnailView
|
||||||
|
import kotlinx.android.synthetic.main.list_channel_item.itemTitleView
|
||||||
import org.schabi.newpipe.R
|
import org.schabi.newpipe.R
|
||||||
import org.schabi.newpipe.extractor.channel.ChannelInfoItem
|
import org.schabi.newpipe.extractor.channel.ChannelInfoItem
|
||||||
import org.schabi.newpipe.util.ImageDisplayConstants
|
import org.schabi.newpipe.util.ImageDisplayConstants
|
||||||
import org.schabi.newpipe.util.Localization
|
import org.schabi.newpipe.util.Localization
|
||||||
import org.schabi.newpipe.util.OnClickGesture
|
import org.schabi.newpipe.util.OnClickGesture
|
||||||
|
|
||||||
|
|
||||||
class ChannelItem(
|
class ChannelItem(
|
||||||
private val infoItem: ChannelInfoItem,
|
private val infoItem: ChannelInfoItem,
|
||||||
private val subscriptionId: Long = -1L,
|
private val subscriptionId: Long = -1L,
|
||||||
var itemVersion: ItemVersion = ItemVersion.NORMAL,
|
var itemVersion: ItemVersion = ItemVersion.NORMAL,
|
||||||
var gesturesListener: OnClickGesture<ChannelInfoItem>? = null
|
var gesturesListener: OnClickGesture<ChannelInfoItem>? = null
|
||||||
) : Item() {
|
) : Item() {
|
||||||
|
|
||||||
override fun getId(): Long = if (subscriptionId == -1L) super.getId() else subscriptionId
|
override fun getId(): Long = if (subscriptionId == -1L) super.getId() else subscriptionId
|
||||||
|
@ -62,4 +64,4 @@ class ChannelItem(
|
||||||
override fun getSpanSize(spanCount: Int, position: Int): Int {
|
override fun getSpanSize(spanCount: Int, position: Int): Int {
|
||||||
return if (itemVersion == ItemVersion.GRID) 1 else spanCount
|
return if (itemVersion == ItemVersion.GRID) 1 else spanCount
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,4 +7,4 @@ import org.schabi.newpipe.R
|
||||||
class EmptyPlaceholderItem : Item() {
|
class EmptyPlaceholderItem : Item() {
|
||||||
override fun getLayout(): Int = R.layout.list_empty_view
|
override fun getLayout(): Int = R.layout.list_empty_view
|
||||||
override fun bind(viewHolder: GroupieViewHolder, position: Int) {}
|
override fun bind(viewHolder: GroupieViewHolder, position: Int) {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,4 +7,4 @@ import org.schabi.newpipe.R
|
||||||
class FeedGroupAddItem : Item() {
|
class FeedGroupAddItem : Item() {
|
||||||
override fun getLayout(): Int = R.layout.feed_group_add_new_item
|
override fun getLayout(): Int = R.layout.feed_group_add_new_item
|
||||||
override fun bind(viewHolder: GroupieViewHolder, position: Int) {}
|
override fun bind(viewHolder: GroupieViewHolder, position: Int) {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,15 +2,16 @@ package org.schabi.newpipe.local.subscription.item
|
||||||
|
|
||||||
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
|
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
|
||||||
import com.xwray.groupie.kotlinandroidextensions.Item
|
import com.xwray.groupie.kotlinandroidextensions.Item
|
||||||
import kotlinx.android.synthetic.main.feed_group_card_item.*
|
import kotlinx.android.synthetic.main.feed_group_card_item.icon
|
||||||
|
import kotlinx.android.synthetic.main.feed_group_card_item.title
|
||||||
import org.schabi.newpipe.R
|
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.local.subscription.FeedGroupIcon
|
import org.schabi.newpipe.local.subscription.FeedGroupIcon
|
||||||
|
|
||||||
data class FeedGroupCardItem(
|
data class FeedGroupCardItem(
|
||||||
val groupId: Long = FeedGroupEntity.GROUP_ALL_ID,
|
val groupId: Long = FeedGroupEntity.GROUP_ALL_ID,
|
||||||
val name: String,
|
val name: String,
|
||||||
val icon: FeedGroupIcon
|
val icon: FeedGroupIcon
|
||||||
) : Item() {
|
) : Item() {
|
||||||
constructor (feedGroupEntity: FeedGroupEntity) : this(feedGroupEntity.uid, feedGroupEntity.name, feedGroupEntity.icon)
|
constructor (feedGroupEntity: FeedGroupEntity) : this(feedGroupEntity.uid, feedGroupEntity.name, feedGroupEntity.icon)
|
||||||
|
|
||||||
|
@ -27,4 +28,4 @@ data class FeedGroupCardItem(
|
||||||
viewHolder.title.text = name
|
viewHolder.title.text = name
|
||||||
viewHolder.icon.setImageResource(icon.getDrawableRes(viewHolder.containerView.context))
|
viewHolder.icon.setImageResource(icon.getDrawableRes(viewHolder.containerView.context))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.xwray.groupie.GroupAdapter
|
import com.xwray.groupie.GroupAdapter
|
||||||
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
|
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
|
||||||
import com.xwray.groupie.kotlinandroidextensions.Item
|
import com.xwray.groupie.kotlinandroidextensions.Item
|
||||||
import kotlinx.android.synthetic.main.feed_item_carousel.*
|
import kotlinx.android.synthetic.main.feed_item_carousel.recycler_view
|
||||||
import org.schabi.newpipe.R
|
import org.schabi.newpipe.R
|
||||||
import org.schabi.newpipe.local.subscription.decoration.FeedGroupCarouselDecoration
|
import org.schabi.newpipe.local.subscription.decoration.FeedGroupCarouselDecoration
|
||||||
|
|
||||||
|
@ -54,4 +54,4 @@ class FeedGroupCarouselItem(context: Context, private val carouselAdapter: Group
|
||||||
|
|
||||||
listState = linearLayoutManager?.onSaveInstanceState()
|
listState = linearLayoutManager?.onSaveInstanceState()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,19 +6,21 @@ import androidx.recyclerview.widget.ItemTouchHelper.DOWN
|
||||||
import androidx.recyclerview.widget.ItemTouchHelper.UP
|
import androidx.recyclerview.widget.ItemTouchHelper.UP
|
||||||
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
|
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
|
||||||
import com.xwray.groupie.kotlinandroidextensions.Item
|
import com.xwray.groupie.kotlinandroidextensions.Item
|
||||||
import kotlinx.android.synthetic.main.feed_group_reorder_item.*
|
import kotlinx.android.synthetic.main.feed_group_reorder_item.group_icon
|
||||||
|
import kotlinx.android.synthetic.main.feed_group_reorder_item.group_name
|
||||||
|
import kotlinx.android.synthetic.main.feed_group_reorder_item.handle
|
||||||
import org.schabi.newpipe.R
|
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.local.subscription.FeedGroupIcon
|
import org.schabi.newpipe.local.subscription.FeedGroupIcon
|
||||||
|
|
||||||
data class FeedGroupReorderItem(
|
data class FeedGroupReorderItem(
|
||||||
val groupId: Long = FeedGroupEntity.GROUP_ALL_ID,
|
val groupId: Long = FeedGroupEntity.GROUP_ALL_ID,
|
||||||
val name: String,
|
val name: String,
|
||||||
val icon: FeedGroupIcon,
|
val icon: FeedGroupIcon,
|
||||||
val dragCallback: ItemTouchHelper
|
val dragCallback: ItemTouchHelper
|
||||||
) : Item() {
|
) : Item() {
|
||||||
constructor (feedGroupEntity: FeedGroupEntity, dragCallback: ItemTouchHelper)
|
constructor (feedGroupEntity: FeedGroupEntity, dragCallback: ItemTouchHelper) :
|
||||||
: this(feedGroupEntity.uid, feedGroupEntity.name, feedGroupEntity.icon, dragCallback)
|
this(feedGroupEntity.uid, feedGroupEntity.name, feedGroupEntity.icon, dragCallback)
|
||||||
|
|
||||||
override fun getId(): Long {
|
override fun getId(): Long {
|
||||||
return when (groupId) {
|
return when (groupId) {
|
||||||
|
@ -45,4 +47,4 @@ data class FeedGroupReorderItem(
|
||||||
override fun getDragDirs(): Int {
|
override fun getDragDirs(): Int {
|
||||||
return UP or DOWN
|
return UP or DOWN
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,11 @@ import android.widget.TextView
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
|
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
|
||||||
import com.xwray.groupie.kotlinandroidextensions.Item
|
import com.xwray.groupie.kotlinandroidextensions.Item
|
||||||
import kotlinx.android.synthetic.main.feed_import_export_group.*
|
import kotlinx.android.synthetic.main.feed_import_export_group.export_to_options
|
||||||
|
import kotlinx.android.synthetic.main.feed_import_export_group.import_export
|
||||||
|
import kotlinx.android.synthetic.main.feed_import_export_group.import_export_expand_icon
|
||||||
|
import kotlinx.android.synthetic.main.feed_import_export_group.import_export_options
|
||||||
|
import kotlinx.android.synthetic.main.feed_import_export_group.import_from_options
|
||||||
import org.schabi.newpipe.R
|
import org.schabi.newpipe.R
|
||||||
import org.schabi.newpipe.extractor.NewPipe
|
import org.schabi.newpipe.extractor.NewPipe
|
||||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException
|
import org.schabi.newpipe.extractor.exceptions.ExtractionException
|
||||||
|
@ -19,10 +23,10 @@ import org.schabi.newpipe.util.ThemeHelper
|
||||||
import org.schabi.newpipe.views.CollapsibleView
|
import org.schabi.newpipe.views.CollapsibleView
|
||||||
|
|
||||||
class FeedImportExportItem(
|
class FeedImportExportItem(
|
||||||
val onImportPreviousSelected: () -> Unit,
|
val onImportPreviousSelected: () -> Unit,
|
||||||
val onImportFromServiceSelected: (Int) -> Unit,
|
val onImportFromServiceSelected: (Int) -> Unit,
|
||||||
val onExportSelected: () -> Unit,
|
val onExportSelected: () -> Unit,
|
||||||
var isExpanded: Boolean = false
|
var isExpanded: Boolean = false
|
||||||
) : Item() {
|
) : Item() {
|
||||||
companion object {
|
companion object {
|
||||||
const val REFRESH_EXPANDED_STATUS = 123
|
const val REFRESH_EXPANDED_STATUS = 123
|
||||||
|
@ -104,7 +108,6 @@ class FeedImportExportItem(
|
||||||
} catch (e: ExtractionException) {
|
} catch (e: ExtractionException) {
|
||||||
throw RuntimeException("Services array contains an entry that it's not a valid service name ($serviceName)", e)
|
throw RuntimeException("Services array contains an entry that it's not a valid service name ($serviceName)", e)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,4 +116,4 @@ class FeedImportExportItem(
|
||||||
ThemeHelper.resolveResourceIdFromAttr(listHolder.context, R.attr.ic_save), listHolder)
|
ThemeHelper.resolveResourceIdFromAttr(listHolder.context, R.attr.ic_save), listHolder)
|
||||||
previousBackupItem.setOnClickListener { onExportSelected() }
|
previousBackupItem.setOnClickListener { onExportSelected() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ package org.schabi.newpipe.local.subscription.item
|
||||||
import android.view.View.OnClickListener
|
import android.view.View.OnClickListener
|
||||||
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
|
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
|
||||||
import com.xwray.groupie.kotlinandroidextensions.Item
|
import com.xwray.groupie.kotlinandroidextensions.Item
|
||||||
import kotlinx.android.synthetic.main.header_item.*
|
import kotlinx.android.synthetic.main.header_item.header_title
|
||||||
import org.schabi.newpipe.R
|
import org.schabi.newpipe.R
|
||||||
|
|
||||||
class HeaderItem(val title: String, private val onClickListener: (() -> Unit)? = null) : Item() {
|
class HeaderItem(val title: String, private val onClickListener: (() -> Unit)? = null) : Item() {
|
||||||
|
@ -16,4 +16,4 @@ class HeaderItem(val title: String, private val onClickListener: (() -> Unit)? =
|
||||||
val listener: OnClickListener? = if (onClickListener != null) OnClickListener { onClickListener.invoke() } else null
|
val listener: OnClickListener? = if (onClickListener != null) OnClickListener { onClickListener.invoke() } else null
|
||||||
viewHolder.root.setOnClickListener(listener)
|
viewHolder.root.setOnClickListener(listener)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,21 @@
|
||||||
package org.schabi.newpipe.local.subscription.item
|
package org.schabi.newpipe.local.subscription.item
|
||||||
|
|
||||||
import android.view.View.*
|
import android.view.View.GONE
|
||||||
|
import android.view.View.OnClickListener
|
||||||
|
import android.view.View.VISIBLE
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
|
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
|
||||||
import com.xwray.groupie.kotlinandroidextensions.Item
|
import com.xwray.groupie.kotlinandroidextensions.Item
|
||||||
import kotlinx.android.synthetic.main.header_with_menu_item.*
|
import kotlinx.android.synthetic.main.header_with_menu_item.header_menu_item
|
||||||
|
import kotlinx.android.synthetic.main.header_with_menu_item.header_title
|
||||||
import org.schabi.newpipe.R
|
import org.schabi.newpipe.R
|
||||||
|
|
||||||
class HeaderWithMenuItem(
|
class HeaderWithMenuItem(
|
||||||
val title: String,
|
val title: String,
|
||||||
@DrawableRes val itemIcon: Int = 0,
|
@DrawableRes val itemIcon: Int = 0,
|
||||||
var showMenuItem: Boolean = true,
|
var showMenuItem: Boolean = true,
|
||||||
private val onClickListener: (() -> Unit)? = null,
|
private val onClickListener: (() -> Unit)? = null,
|
||||||
private val menuItemOnClickListener: (() -> Unit)? = null
|
private val menuItemOnClickListener: (() -> Unit)? = null
|
||||||
) : Item() {
|
) : Item() {
|
||||||
companion object {
|
companion object {
|
||||||
const val PAYLOAD_UPDATE_VISIBILITY_MENU_ITEM = 1
|
const val PAYLOAD_UPDATE_VISIBILITY_MENU_ITEM = 1
|
||||||
|
@ -46,4 +49,4 @@ class HeaderWithMenuItem(
|
||||||
private fun updateMenuItemVisibility(viewHolder: GroupieViewHolder) {
|
private fun updateMenuItemVisibility(viewHolder: GroupieViewHolder) {
|
||||||
viewHolder.header_menu_item.visibility = if (showMenuItem) VISIBLE else GONE
|
viewHolder.header_menu_item.visibility = if (showMenuItem) VISIBLE else GONE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import android.content.Context
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
|
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
|
||||||
import com.xwray.groupie.kotlinandroidextensions.Item
|
import com.xwray.groupie.kotlinandroidextensions.Item
|
||||||
import kotlinx.android.synthetic.main.picker_icon_item.*
|
import kotlinx.android.synthetic.main.picker_icon_item.icon_view
|
||||||
import org.schabi.newpipe.R
|
import org.schabi.newpipe.R
|
||||||
import org.schabi.newpipe.local.subscription.FeedGroupIcon
|
import org.schabi.newpipe.local.subscription.FeedGroupIcon
|
||||||
|
|
||||||
|
@ -17,4 +17,4 @@ class PickerIconItem(context: Context, val icon: FeedGroupIcon) : Item() {
|
||||||
override fun bind(viewHolder: GroupieViewHolder, position: Int) {
|
override fun bind(viewHolder: GroupieViewHolder, position: Int) {
|
||||||
viewHolder.icon_view.setImageResource(iconRes)
|
viewHolder.icon_view.setImageResource(iconRes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,9 @@ import com.nostra13.universalimageloader.core.DisplayImageOptions
|
||||||
import com.nostra13.universalimageloader.core.ImageLoader
|
import com.nostra13.universalimageloader.core.ImageLoader
|
||||||
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
|
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
|
||||||
import com.xwray.groupie.kotlinandroidextensions.Item
|
import com.xwray.groupie.kotlinandroidextensions.Item
|
||||||
import kotlinx.android.synthetic.main.picker_subscription_item.*
|
import kotlinx.android.synthetic.main.picker_subscription_item.selected_highlight
|
||||||
|
import kotlinx.android.synthetic.main.picker_subscription_item.thumbnail_view
|
||||||
|
import kotlinx.android.synthetic.main.picker_subscription_item.title_view
|
||||||
import org.schabi.newpipe.R
|
import org.schabi.newpipe.R
|
||||||
import org.schabi.newpipe.database.subscription.SubscriptionEntity
|
import org.schabi.newpipe.database.subscription.SubscriptionEntity
|
||||||
import org.schabi.newpipe.util.AnimationUtils
|
import org.schabi.newpipe.util.AnimationUtils
|
||||||
|
@ -48,4 +50,4 @@ data class PickerSubscriptionItem(val subscriptionEntity: SubscriptionEntity, va
|
||||||
override fun getId(): Long {
|
override fun getId(): Long {
|
||||||
return subscriptionEntity.uid
|
return subscriptionEntity.uid
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,7 +49,6 @@ import org.schabi.newpipe.BuildConfig;
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||||
import org.schabi.newpipe.player.event.PlayerEventListener;
|
import org.schabi.newpipe.player.event.PlayerEventListener;
|
||||||
import org.schabi.newpipe.player.helper.LockManager;
|
|
||||||
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
||||||
import org.schabi.newpipe.player.resolver.AudioPlaybackResolver;
|
import org.schabi.newpipe.player.resolver.AudioPlaybackResolver;
|
||||||
import org.schabi.newpipe.player.resolver.MediaSourceTag;
|
import org.schabi.newpipe.player.resolver.MediaSourceTag;
|
||||||
|
@ -91,7 +90,6 @@ public final class BackgroundPlayer extends Service {
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
// Service-Activity Binder
|
// Service-Activity Binder
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
private LockManager lockManager;
|
|
||||||
private SharedPreferences sharedPreferences;
|
private SharedPreferences sharedPreferences;
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -116,7 +114,6 @@ public final class BackgroundPlayer extends Service {
|
||||||
Log.d(TAG, "onCreate() called");
|
Log.d(TAG, "onCreate() called");
|
||||||
}
|
}
|
||||||
notificationManager = ((NotificationManager) getSystemService(NOTIFICATION_SERVICE));
|
notificationManager = ((NotificationManager) getSystemService(NOTIFICATION_SERVICE));
|
||||||
lockManager = new LockManager(this);
|
|
||||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
|
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
|
||||||
assureCorrectAppLanguage(this);
|
assureCorrectAppLanguage(this);
|
||||||
ThemeHelper.setTheme(this);
|
ThemeHelper.setTheme(this);
|
||||||
|
@ -166,9 +163,6 @@ public final class BackgroundPlayer extends Service {
|
||||||
Log.d(TAG, "onClose() called");
|
Log.d(TAG, "onClose() called");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lockManager != null) {
|
|
||||||
lockManager.releaseWifiAndCpu();
|
|
||||||
}
|
|
||||||
if (basePlayerImpl != null) {
|
if (basePlayerImpl != null) {
|
||||||
basePlayerImpl.savePlaybackState();
|
basePlayerImpl.savePlaybackState();
|
||||||
basePlayerImpl.stopActivityBinding();
|
basePlayerImpl.stopActivityBinding();
|
||||||
|
@ -179,7 +173,6 @@ public final class BackgroundPlayer extends Service {
|
||||||
}
|
}
|
||||||
mBinder = null;
|
mBinder = null;
|
||||||
basePlayerImpl = null;
|
basePlayerImpl = null;
|
||||||
lockManager = null;
|
|
||||||
|
|
||||||
stopForeground(true);
|
stopForeground(true);
|
||||||
stopSelf();
|
stopSelf();
|
||||||
|
@ -208,9 +201,10 @@ public final class BackgroundPlayer extends Service {
|
||||||
}
|
}
|
||||||
|
|
||||||
private NotificationCompat.Builder createNotification() {
|
private NotificationCompat.Builder createNotification() {
|
||||||
notRemoteView = new RemoteViews(BuildConfig.APPLICATION_ID, R.layout.player_notification);
|
notRemoteView = new RemoteViews(BuildConfig.APPLICATION_ID,
|
||||||
|
R.layout.player_background_notification);
|
||||||
bigNotRemoteView = new RemoteViews(BuildConfig.APPLICATION_ID,
|
bigNotRemoteView = new RemoteViews(BuildConfig.APPLICATION_ID,
|
||||||
R.layout.player_notification_expanded);
|
R.layout.player_background_notification_expanded);
|
||||||
|
|
||||||
setupNotification(notRemoteView);
|
setupNotification(notRemoteView);
|
||||||
setupNotification(bigNotRemoteView);
|
setupNotification(bigNotRemoteView);
|
||||||
|
@ -662,8 +656,7 @@ public final class BackgroundPlayer extends Service {
|
||||||
super.onPlaying();
|
super.onPlaying();
|
||||||
resetNotification();
|
resetNotification();
|
||||||
updateNotificationThumbnail();
|
updateNotificationThumbnail();
|
||||||
updateNotification(R.drawable.ic_pause_white);
|
updateNotification(R.drawable.exo_controls_pause);
|
||||||
lockManager.acquireWifiAndCpu();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -671,8 +664,7 @@ public final class BackgroundPlayer extends Service {
|
||||||
super.onPaused();
|
super.onPaused();
|
||||||
resetNotification();
|
resetNotification();
|
||||||
updateNotificationThumbnail();
|
updateNotificationThumbnail();
|
||||||
updateNotification(R.drawable.ic_play_arrow_white);
|
updateNotification(R.drawable.exo_controls_play);
|
||||||
lockManager.releaseWifiAndCpu();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -686,8 +678,7 @@ public final class BackgroundPlayer extends Service {
|
||||||
notRemoteView.setProgressBar(R.id.notificationProgressBar, 100, 100, false);
|
notRemoteView.setProgressBar(R.id.notificationProgressBar, 100, 100, false);
|
||||||
}
|
}
|
||||||
updateNotificationThumbnail();
|
updateNotificationThumbnail();
|
||||||
updateNotification(R.drawable.ic_replay_white);
|
updateNotification(R.drawable.ic_replay_white_24dp);
|
||||||
lockManager.releaseWifiAndCpu();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,9 +35,9 @@ import android.widget.Toast;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.DefaultRenderersFactory;
|
import com.google.android.exoplayer2.DefaultRenderersFactory;
|
||||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||||
import com.google.android.exoplayer2.ExoPlayerFactory;
|
|
||||||
import com.google.android.exoplayer2.LoadControl;
|
import com.google.android.exoplayer2.LoadControl;
|
||||||
import com.google.android.exoplayer2.PlaybackParameters;
|
import com.google.android.exoplayer2.PlaybackParameters;
|
||||||
import com.google.android.exoplayer2.Player;
|
import com.google.android.exoplayer2.Player;
|
||||||
|
@ -232,14 +232,18 @@ public abstract class BasePlayer implements
|
||||||
|
|
||||||
public void initPlayer(final boolean playOnReady) {
|
public void initPlayer(final boolean playOnReady) {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "initPlayer() called with: context = [" + context + "]");
|
Log.d(TAG, "initPlayer() called with: playOnReady = [" + playOnReady + "]");
|
||||||
}
|
}
|
||||||
|
|
||||||
simpleExoPlayer = ExoPlayerFactory
|
simpleExoPlayer = new SimpleExoPlayer.Builder(context, renderFactory)
|
||||||
.newSimpleInstance(context, renderFactory, trackSelector, loadControl);
|
.setTrackSelector(trackSelector)
|
||||||
|
.setLoadControl(loadControl)
|
||||||
|
.build();
|
||||||
simpleExoPlayer.addListener(this);
|
simpleExoPlayer.addListener(this);
|
||||||
simpleExoPlayer.setPlayWhenReady(playOnReady);
|
simpleExoPlayer.setPlayWhenReady(playOnReady);
|
||||||
simpleExoPlayer.setSeekParameters(PlayerHelper.getSeekParameters(context));
|
simpleExoPlayer.setSeekParameters(PlayerHelper.getSeekParameters(context));
|
||||||
|
simpleExoPlayer.setWakeMode(C.WAKE_MODE_NETWORK);
|
||||||
|
simpleExoPlayer.setHandleAudioBecomingNoisy(true);
|
||||||
|
|
||||||
audioReactor = new AudioReactor(context, simpleExoPlayer);
|
audioReactor = new AudioReactor(context, simpleExoPlayer);
|
||||||
mediaSessionManager = new MediaSessionManager(context, simpleExoPlayer,
|
mediaSessionManager = new MediaSessionManager(context, simpleExoPlayer,
|
||||||
|
@ -666,11 +670,9 @@ public abstract class BasePlayer implements
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTimelineChanged(final Timeline timeline, final Object manifest,
|
public void onTimelineChanged(final Timeline timeline, final int reason) {
|
||||||
@Player.TimelineChangeReason final int reason) {
|
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "ExoPlayer - onTimelineChanged() called with "
|
Log.d(TAG, "ExoPlayer - onTimelineChanged() called with "
|
||||||
+ (manifest == null ? "no manifest" : "available manifest") + ", "
|
|
||||||
+ "timeline size = [" + timeline.getWindowCount() + "], "
|
+ "timeline size = [" + timeline.getWindowCount() + "], "
|
||||||
+ "reason = [" + reason + "]");
|
+ "reason = [" + reason + "]");
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,8 +80,8 @@ import org.schabi.newpipe.player.playqueue.PlayQueueItemHolder;
|
||||||
import org.schabi.newpipe.player.playqueue.PlayQueueItemTouchCallback;
|
import org.schabi.newpipe.player.playqueue.PlayQueueItemTouchCallback;
|
||||||
import org.schabi.newpipe.player.resolver.MediaSourceTag;
|
import org.schabi.newpipe.player.resolver.MediaSourceTag;
|
||||||
import org.schabi.newpipe.player.resolver.VideoPlaybackResolver;
|
import org.schabi.newpipe.player.resolver.VideoPlaybackResolver;
|
||||||
import org.schabi.newpipe.util.AnimationUtils;
|
|
||||||
import org.schabi.newpipe.util.AndroidTvUtils;
|
import org.schabi.newpipe.util.AndroidTvUtils;
|
||||||
|
import org.schabi.newpipe.util.AnimationUtils;
|
||||||
import org.schabi.newpipe.util.KoreUtil;
|
import org.schabi.newpipe.util.KoreUtil;
|
||||||
import org.schabi.newpipe.util.ListHelper;
|
import org.schabi.newpipe.util.ListHelper;
|
||||||
import org.schabi.newpipe.util.NavigationHelper;
|
import org.schabi.newpipe.util.NavigationHelper;
|
||||||
|
@ -491,7 +491,7 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||||
|
|
||||||
protected void setMuteButton(final ImageButton muteButton, final boolean isMuted) {
|
protected void setMuteButton(final ImageButton muteButton, final boolean isMuted) {
|
||||||
muteButton.setImageDrawable(AppCompatResources.getDrawable(getApplicationContext(), isMuted
|
muteButton.setImageDrawable(AppCompatResources.getDrawable(getApplicationContext(), isMuted
|
||||||
? R.drawable.ic_volume_off_white_72dp : R.drawable.ic_volume_up_white_72dp));
|
? R.drawable.ic_volume_off_white_24dp : R.drawable.ic_volume_up_white_24dp));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -593,9 +593,6 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||||
|
|
||||||
titleTextView.setSelected(true);
|
titleTextView.setSelected(true);
|
||||||
channelTextView.setSelected(true);
|
channelTextView.setSelected(true);
|
||||||
boolean showKodiButton = PreferenceManager.getDefaultSharedPreferences(this.context)
|
|
||||||
.getBoolean(this.context.getString(R.string.show_play_with_kodi_key), false);
|
|
||||||
kodiButton.setVisibility(showKodiButton ? View.VISIBLE : View.GONE);
|
|
||||||
|
|
||||||
getRootView().setKeepScreenOn(true);
|
getRootView().setKeepScreenOn(true);
|
||||||
}
|
}
|
||||||
|
@ -712,6 +709,13 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||||
protected void onMetadataChanged(@NonNull final MediaSourceTag tag) {
|
protected void onMetadataChanged(@NonNull final MediaSourceTag tag) {
|
||||||
super.onMetadataChanged(tag);
|
super.onMetadataChanged(tag);
|
||||||
|
|
||||||
|
// show kodi button if it supports the current service and it is enabled in settings
|
||||||
|
final boolean showKodiButton =
|
||||||
|
KoreUtil.isServiceSupportedByKore(tag.getMetadata().getServiceId())
|
||||||
|
&& PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
|
.getBoolean(context.getString(R.string.show_play_with_kodi_key), false);
|
||||||
|
kodiButton.setVisibility(showKodiButton ? View.VISIBLE : View.GONE);
|
||||||
|
|
||||||
titleTextView.setText(tag.getMetadata().getName());
|
titleTextView.setText(tag.getMetadata().getName());
|
||||||
channelTextView.setText(tag.getMetadata().getUploaderName());
|
channelTextView.setText(tag.getMetadata().getUploaderName());
|
||||||
}
|
}
|
||||||
|
@ -725,13 +729,12 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||||
public void onKodiShare() {
|
public void onKodiShare() {
|
||||||
onPause();
|
onPause();
|
||||||
try {
|
try {
|
||||||
NavigationHelper.playWithKore(this.context,
|
NavigationHelper.playWithKore(context, Uri.parse(playerImpl.getVideoUrl()));
|
||||||
Uri.parse(playerImpl.getVideoUrl().replace("https", "http")));
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.i(TAG, "Failed to start kore", e);
|
Log.i(TAG, "Failed to start kore", e);
|
||||||
}
|
}
|
||||||
KoreUtil.showInstallKoreDialog(this.context);
|
KoreUtil.showInstallKoreDialog(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -986,7 +989,7 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||||
@Override
|
@Override
|
||||||
public void onBlocked() {
|
public void onBlocked() {
|
||||||
super.onBlocked();
|
super.onBlocked();
|
||||||
playPauseButton.setImageResource(R.drawable.ic_pause_white);
|
playPauseButton.setImageResource(R.drawable.ic_pause_white_24dp);
|
||||||
animatePlayButtons(false, 100);
|
animatePlayButtons(false, 100);
|
||||||
animateView(closeButton, false, DEFAULT_CONTROLS_DURATION);
|
animateView(closeButton, false, DEFAULT_CONTROLS_DURATION);
|
||||||
getRootView().setKeepScreenOn(true);
|
getRootView().setKeepScreenOn(true);
|
||||||
|
@ -1002,7 +1005,7 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||||
public void onPlaying() {
|
public void onPlaying() {
|
||||||
super.onPlaying();
|
super.onPlaying();
|
||||||
animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false, 80, 0, () -> {
|
animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false, 80, 0, () -> {
|
||||||
playPauseButton.setImageResource(R.drawable.ic_pause_white);
|
playPauseButton.setImageResource(R.drawable.ic_pause_white_24dp);
|
||||||
animatePlayButtons(true, 200);
|
animatePlayButtons(true, 200);
|
||||||
playPauseButton.requestFocus();
|
playPauseButton.requestFocus();
|
||||||
animateView(closeButton, false, DEFAULT_CONTROLS_DURATION);
|
animateView(closeButton, false, DEFAULT_CONTROLS_DURATION);
|
||||||
|
@ -1015,7 +1018,7 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||||
public void onPaused() {
|
public void onPaused() {
|
||||||
super.onPaused();
|
super.onPaused();
|
||||||
animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false, 80, 0, () -> {
|
animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false, 80, 0, () -> {
|
||||||
playPauseButton.setImageResource(R.drawable.ic_play_arrow_white);
|
playPauseButton.setImageResource(R.drawable.ic_play_arrow_white_24dp);
|
||||||
animatePlayButtons(true, 200);
|
animatePlayButtons(true, 200);
|
||||||
playPauseButton.requestFocus();
|
playPauseButton.requestFocus();
|
||||||
animateView(closeButton, false, DEFAULT_CONTROLS_DURATION);
|
animateView(closeButton, false, DEFAULT_CONTROLS_DURATION);
|
||||||
|
@ -1036,7 +1039,7 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||||
@Override
|
@Override
|
||||||
public void onCompleted() {
|
public void onCompleted() {
|
||||||
animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false, 0, 0, () -> {
|
animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false, 0, 0, () -> {
|
||||||
playPauseButton.setImageResource(R.drawable.ic_replay_white);
|
playPauseButton.setImageResource(R.drawable.ic_replay_white_24dp);
|
||||||
animatePlayButtons(true, DEFAULT_CONTROLS_DURATION);
|
animatePlayButtons(true, DEFAULT_CONTROLS_DURATION);
|
||||||
animateView(closeButton, true, DEFAULT_CONTROLS_DURATION);
|
animateView(closeButton, true, DEFAULT_CONTROLS_DURATION);
|
||||||
});
|
});
|
||||||
|
@ -1322,6 +1325,13 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final boolean isTouchingStatusBar = initialEvent.getY() < getStatusBarHeight();
|
||||||
|
final boolean isTouchingNavigationBar = initialEvent.getY()
|
||||||
|
> playerImpl.getRootView().getHeight() - getNavigationBarHeight();
|
||||||
|
if (isTouchingStatusBar || isTouchingNavigationBar) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// if (DEBUG) {
|
// if (DEBUG) {
|
||||||
// Log.d(TAG, "MainVideoPlayer.onScroll = " +
|
// Log.d(TAG, "MainVideoPlayer.onScroll = " +
|
||||||
// "e1.getRaw = [" + initialEvent.getRawX() + ", "
|
// "e1.getRaw = [" + initialEvent.getRawX() + ", "
|
||||||
|
@ -1358,12 +1368,12 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||||
}
|
}
|
||||||
|
|
||||||
final int resId = currentProgressPercent <= 0
|
final int resId = currentProgressPercent <= 0
|
||||||
? R.drawable.ic_volume_off_white_72dp
|
? R.drawable.ic_volume_off_white_24dp
|
||||||
: currentProgressPercent < 0.25
|
: currentProgressPercent < 0.25
|
||||||
? R.drawable.ic_volume_mute_white_72dp
|
? R.drawable.ic_volume_mute_white_24dp
|
||||||
: currentProgressPercent < 0.75
|
: currentProgressPercent < 0.75
|
||||||
? R.drawable.ic_volume_down_white_72dp
|
? R.drawable.ic_volume_down_white_24dp
|
||||||
: R.drawable.ic_volume_up_white_72dp;
|
: R.drawable.ic_volume_up_white_24dp;
|
||||||
|
|
||||||
playerImpl.getVolumeImageView().setImageDrawable(
|
playerImpl.getVolumeImageView().setImageDrawable(
|
||||||
AppCompatResources.getDrawable(getApplicationContext(), resId)
|
AppCompatResources.getDrawable(getApplicationContext(), resId)
|
||||||
|
@ -1390,10 +1400,10 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||||
}
|
}
|
||||||
|
|
||||||
final int resId = currentProgressPercent < 0.25
|
final int resId = currentProgressPercent < 0.25
|
||||||
? R.drawable.ic_brightness_low_white_72dp
|
? R.drawable.ic_brightness_low_white_24dp
|
||||||
: currentProgressPercent < 0.75
|
: currentProgressPercent < 0.75
|
||||||
? R.drawable.ic_brightness_medium_white_72dp
|
? R.drawable.ic_brightness_medium_white_24dp
|
||||||
: R.drawable.ic_brightness_high_white_72dp;
|
: R.drawable.ic_brightness_high_white_24dp;
|
||||||
|
|
||||||
playerImpl.getBrightnessImageView().setImageDrawable(
|
playerImpl.getBrightnessImageView().setImageDrawable(
|
||||||
AppCompatResources.getDrawable(getApplicationContext(), resId)
|
AppCompatResources.getDrawable(getApplicationContext(), resId)
|
||||||
|
@ -1410,6 +1420,22 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int getNavigationBarHeight() {
|
||||||
|
int resId = getResources().getIdentifier("navigation_bar_height", "dimen", "android");
|
||||||
|
if (resId > 0) {
|
||||||
|
return getResources().getDimensionPixelSize(resId);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getStatusBarHeight() {
|
||||||
|
int resId = getResources().getIdentifier("status_bar_height", "dimen", "android");
|
||||||
|
if (resId > 0) {
|
||||||
|
return getResources().getDimensionPixelSize(resId);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
private void onScrollEnd() {
|
private void onScrollEnd() {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "onScrollEnd() called");
|
Log.d(TAG, "onScrollEnd() called");
|
||||||
|
|
|
@ -68,7 +68,6 @@ import org.schabi.newpipe.BuildConfig;
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.extractor.stream.VideoStream;
|
import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||||
import org.schabi.newpipe.player.event.PlayerEventListener;
|
import org.schabi.newpipe.player.event.PlayerEventListener;
|
||||||
import org.schabi.newpipe.player.helper.LockManager;
|
|
||||||
import org.schabi.newpipe.player.helper.PlayerHelper;
|
import org.schabi.newpipe.player.helper.PlayerHelper;
|
||||||
import org.schabi.newpipe.player.resolver.MediaSourceTag;
|
import org.schabi.newpipe.player.resolver.MediaSourceTag;
|
||||||
import org.schabi.newpipe.player.resolver.VideoPlaybackResolver;
|
import org.schabi.newpipe.player.resolver.VideoPlaybackResolver;
|
||||||
|
@ -132,7 +131,6 @@ public final class PopupVideoPlayer extends Service {
|
||||||
private RemoteViews notRemoteView;
|
private RemoteViews notRemoteView;
|
||||||
|
|
||||||
private VideoPlayerImpl playerImpl;
|
private VideoPlayerImpl playerImpl;
|
||||||
private LockManager lockManager;
|
|
||||||
private boolean isPopupClosing = false;
|
private boolean isPopupClosing = false;
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -152,7 +150,6 @@ public final class PopupVideoPlayer extends Service {
|
||||||
windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
|
windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
|
||||||
notificationManager = ((NotificationManager) getSystemService(NOTIFICATION_SERVICE));
|
notificationManager = ((NotificationManager) getSystemService(NOTIFICATION_SERVICE));
|
||||||
|
|
||||||
lockManager = new LockManager(this);
|
|
||||||
playerImpl = new VideoPlayerImpl(this);
|
playerImpl = new VideoPlayerImpl(this);
|
||||||
ThemeHelper.setTheme(this);
|
ThemeHelper.setTheme(this);
|
||||||
|
|
||||||
|
@ -378,9 +375,6 @@ public final class PopupVideoPlayer extends Service {
|
||||||
}
|
}
|
||||||
|
|
||||||
mBinder = null;
|
mBinder = null;
|
||||||
if (lockManager != null) {
|
|
||||||
lockManager.releaseWifiAndCpu();
|
|
||||||
}
|
|
||||||
if (notificationManager != null) {
|
if (notificationManager != null) {
|
||||||
notificationManager.cancel(NOTIFICATION_ID);
|
notificationManager.cancel(NOTIFICATION_ID);
|
||||||
}
|
}
|
||||||
|
@ -898,7 +892,7 @@ public final class PopupVideoPlayer extends Service {
|
||||||
public void onBlocked() {
|
public void onBlocked() {
|
||||||
super.onBlocked();
|
super.onBlocked();
|
||||||
resetNotification();
|
resetNotification();
|
||||||
updateNotification(R.drawable.ic_play_arrow_white);
|
updateNotification(R.drawable.exo_controls_play);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -908,20 +902,19 @@ public final class PopupVideoPlayer extends Service {
|
||||||
updateWindowFlags(ONGOING_PLAYBACK_WINDOW_FLAGS);
|
updateWindowFlags(ONGOING_PLAYBACK_WINDOW_FLAGS);
|
||||||
|
|
||||||
resetNotification();
|
resetNotification();
|
||||||
updateNotification(R.drawable.ic_pause_white);
|
updateNotification(R.drawable.exo_controls_pause);
|
||||||
|
|
||||||
videoPlayPause.setBackgroundResource(R.drawable.ic_pause_white);
|
videoPlayPause.setBackgroundResource(R.drawable.exo_controls_pause);
|
||||||
hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME);
|
hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME);
|
||||||
|
|
||||||
startForeground(NOTIFICATION_ID, notBuilder.build());
|
startForeground(NOTIFICATION_ID, notBuilder.build());
|
||||||
lockManager.acquireWifiAndCpu();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBuffering() {
|
public void onBuffering() {
|
||||||
super.onBuffering();
|
super.onBuffering();
|
||||||
resetNotification();
|
resetNotification();
|
||||||
updateNotification(R.drawable.ic_play_arrow_white);
|
updateNotification(R.drawable.exo_controls_play);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -931,10 +924,8 @@ public final class PopupVideoPlayer extends Service {
|
||||||
updateWindowFlags(IDLE_WINDOW_FLAGS);
|
updateWindowFlags(IDLE_WINDOW_FLAGS);
|
||||||
|
|
||||||
resetNotification();
|
resetNotification();
|
||||||
updateNotification(R.drawable.ic_play_arrow_white);
|
updateNotification(R.drawable.exo_controls_play);
|
||||||
|
videoPlayPause.setBackgroundResource(R.drawable.exo_controls_play);
|
||||||
videoPlayPause.setBackgroundResource(R.drawable.ic_play_arrow_white);
|
|
||||||
lockManager.releaseWifiAndCpu();
|
|
||||||
|
|
||||||
stopForeground(false);
|
stopForeground(false);
|
||||||
}
|
}
|
||||||
|
@ -943,9 +934,9 @@ public final class PopupVideoPlayer extends Service {
|
||||||
public void onPausedSeek() {
|
public void onPausedSeek() {
|
||||||
super.onPausedSeek();
|
super.onPausedSeek();
|
||||||
resetNotification();
|
resetNotification();
|
||||||
updateNotification(R.drawable.ic_play_arrow_white);
|
updateNotification(R.drawable.exo_controls_play);
|
||||||
|
|
||||||
videoPlayPause.setBackgroundResource(R.drawable.ic_pause_white);
|
videoPlayPause.setBackgroundResource(R.drawable.exo_controls_play);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -955,10 +946,8 @@ public final class PopupVideoPlayer extends Service {
|
||||||
updateWindowFlags(IDLE_WINDOW_FLAGS);
|
updateWindowFlags(IDLE_WINDOW_FLAGS);
|
||||||
|
|
||||||
resetNotification();
|
resetNotification();
|
||||||
updateNotification(R.drawable.ic_replay_white);
|
updateNotification(R.drawable.ic_replay_white_24dp);
|
||||||
|
videoPlayPause.setBackgroundResource(R.drawable.ic_replay_white_24dp);
|
||||||
videoPlayPause.setBackgroundResource(R.drawable.ic_replay_white);
|
|
||||||
lockManager.releaseWifiAndCpu();
|
|
||||||
|
|
||||||
stopForeground(false);
|
stopForeground(false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -643,13 +643,13 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
||||||
private void onStateChanged(final int state) {
|
private void onStateChanged(final int state) {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case BasePlayer.STATE_PAUSED:
|
case BasePlayer.STATE_PAUSED:
|
||||||
playPauseButton.setImageResource(R.drawable.ic_play_arrow_white);
|
playPauseButton.setImageResource(R.drawable.ic_play_arrow_white_24dp);
|
||||||
break;
|
break;
|
||||||
case BasePlayer.STATE_PLAYING:
|
case BasePlayer.STATE_PLAYING:
|
||||||
playPauseButton.setImageResource(R.drawable.ic_pause_white);
|
playPauseButton.setImageResource(R.drawable.ic_pause_white_24dp);
|
||||||
break;
|
break;
|
||||||
case BasePlayer.STATE_COMPLETED:
|
case BasePlayer.STATE_COMPLETED:
|
||||||
playPauseButton.setImageResource(R.drawable.ic_replay_white);
|
playPauseButton.setImageResource(R.drawable.ic_replay_white_24dp);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
@ -717,9 +717,9 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
||||||
// using rootView.getContext() because getApplicationContext() didn't work
|
// using rootView.getContext() because getApplicationContext() didn't work
|
||||||
item.setIcon(player.isMuted()
|
item.setIcon(player.isMuted()
|
||||||
? ThemeHelper.resolveResourceIdFromAttr(rootView.getContext(),
|
? ThemeHelper.resolveResourceIdFromAttr(rootView.getContext(),
|
||||||
R.attr.volume_off)
|
R.attr.ic_volume_off)
|
||||||
: ThemeHelper.resolveResourceIdFromAttr(rootView.getContext(),
|
: ThemeHelper.resolveResourceIdFromAttr(rootView.getContext(),
|
||||||
R.attr.volume_on));
|
R.attr.ic_volume_up));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,7 +46,7 @@ import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.appcompat.content.res.AppCompatResources;
|
||||||
|
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.PlaybackParameters;
|
import com.google.android.exoplayer2.PlaybackParameters;
|
||||||
|
@ -682,13 +682,13 @@ public abstract class VideoPlayer extends BasePlayer
|
||||||
@Override
|
@Override
|
||||||
public void onFastRewind() {
|
public void onFastRewind() {
|
||||||
super.onFastRewind();
|
super.onFastRewind();
|
||||||
showAndAnimateControl(R.drawable.ic_action_av_fast_rewind, true);
|
showAndAnimateControl(R.drawable.ic_fast_rewind_white_24dp, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFastForward() {
|
public void onFastForward() {
|
||||||
super.onFastForward();
|
super.onFastForward();
|
||||||
showAndAnimateControl(R.drawable.ic_action_av_fast_forward, true);
|
showAndAnimateControl(R.drawable.ic_fast_forward_white_24dp, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -956,7 +956,7 @@ public abstract class VideoPlayer extends BasePlayer
|
||||||
|
|
||||||
|
|
||||||
controlAnimationView.setVisibility(View.VISIBLE);
|
controlAnimationView.setVisibility(View.VISIBLE);
|
||||||
controlAnimationView.setImageDrawable(ContextCompat.getDrawable(context, drawableId));
|
controlAnimationView.setImageDrawable(AppCompatResources.getDrawable(context, drawableId));
|
||||||
controlViewAnimator.start();
|
controlViewAnimator.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,9 +36,9 @@ public class LoadedMediaSource implements ManagedMediaSource {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void prepareSource(final SourceInfoRefreshListener listener,
|
public void prepareSource(final MediaSourceCaller mediaSourceCaller,
|
||||||
@Nullable final TransferListener mediaTransferListener) {
|
@Nullable final TransferListener mediaTransferListener) {
|
||||||
source.prepareSource(listener, mediaTransferListener);
|
source.prepareSource(mediaSourceCaller, mediaTransferListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -46,6 +46,11 @@ public class LoadedMediaSource implements ManagedMediaSource {
|
||||||
source.maybeThrowSourceInfoRefreshError();
|
source.maybeThrowSourceInfoRefreshError();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void enable(final MediaSourceCaller caller) {
|
||||||
|
source.enable(caller);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MediaPeriod createPeriod(final MediaPeriodId id, final Allocator allocator,
|
public MediaPeriod createPeriod(final MediaPeriodId id, final Allocator allocator,
|
||||||
final long startPositionUs) {
|
final long startPositionUs) {
|
||||||
|
@ -58,8 +63,13 @@ public class LoadedMediaSource implements ManagedMediaSource {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void releaseSource(final SourceInfoRefreshListener listener) {
|
public void disable(final MediaSourceCaller caller) {
|
||||||
source.releaseSource(listener);
|
source.disable(caller);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void releaseSource(final MediaSourceCaller mediaSourceCaller) {
|
||||||
|
source.releaseSource(mediaSourceCaller);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package org.schabi.newpipe.player.mediasource;
|
package org.schabi.newpipe.player.mediasource;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import com.google.android.exoplayer2.source.MediaSource;
|
import com.google.android.exoplayer2.source.MediaSource;
|
||||||
|
|
||||||
|
@ -27,4 +28,10 @@ public interface ManagedMediaSource extends MediaSource {
|
||||||
* @return whether this source is for the specified stream
|
* @return whether this source is for the specified stream
|
||||||
*/
|
*/
|
||||||
boolean isStreamEqual(@NonNull PlayQueueItem stream);
|
boolean isStreamEqual(@NonNull PlayQueueItem stream);
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
default Object getTag() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,7 @@ public class ManagedMediaSourcePlaylist {
|
||||||
@Nullable
|
@Nullable
|
||||||
public ManagedMediaSource get(final int index) {
|
public ManagedMediaSource get(final int index) {
|
||||||
return (index < 0 || index >= size())
|
return (index < 0 || index >= size())
|
||||||
? null : (ManagedMediaSource) internalSource.getMediaSource(index);
|
? null : (ManagedMediaSource) internalSource.getMediaSource(index).getTag();
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
|
|
|
@ -8,6 +8,7 @@ import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
|
import com.google.android.exoplayer2.RendererCapabilities.Capabilities;
|
||||||
import com.google.android.exoplayer2.source.TrackGroup;
|
import com.google.android.exoplayer2.source.TrackGroup;
|
||||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||||
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
|
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
|
||||||
|
@ -48,27 +49,31 @@ public class CustomTrackSelector extends DefaultTrackSelector {
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
protected Pair<TrackSelection.Definition, TextTrackScore> selectTextTrack(
|
protected Pair<TrackSelection.Definition, TextTrackScore> selectTextTrack(
|
||||||
final TrackGroupArray groups, final int[][] formatSupport, final Parameters params,
|
final TrackGroupArray groups,
|
||||||
|
@NonNull final int[][] formatSupport,
|
||||||
|
@NonNull final Parameters params,
|
||||||
@Nullable final String selectedAudioLanguage) {
|
@Nullable final String selectedAudioLanguage) {
|
||||||
TrackGroup selectedGroup = null;
|
TrackGroup selectedGroup = null;
|
||||||
int selectedTrackIndex = C.INDEX_UNSET;
|
int selectedTrackIndex = C.INDEX_UNSET;
|
||||||
int newPipeTrackScore = 0;
|
|
||||||
TextTrackScore selectedTrackScore = null;
|
TextTrackScore selectedTrackScore = null;
|
||||||
|
|
||||||
for (int groupIndex = 0; groupIndex < groups.length; groupIndex++) {
|
for (int groupIndex = 0; groupIndex < groups.length; groupIndex++) {
|
||||||
TrackGroup trackGroup = groups.get(groupIndex);
|
TrackGroup trackGroup = groups.get(groupIndex);
|
||||||
int[] trackFormatSupport = formatSupport[groupIndex];
|
@Capabilities int[] trackFormatSupport = formatSupport[groupIndex];
|
||||||
|
|
||||||
for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
|
for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
|
||||||
if (isSupported(trackFormatSupport[trackIndex],
|
if (isSupported(trackFormatSupport[trackIndex],
|
||||||
params.exceedRendererCapabilitiesIfNecessary)) {
|
params.exceedRendererCapabilitiesIfNecessary)) {
|
||||||
Format format = trackGroup.getFormat(trackIndex);
|
Format format = trackGroup.getFormat(trackIndex);
|
||||||
TextTrackScore trackScore = new TextTrackScore(format, params,
|
TextTrackScore trackScore = new TextTrackScore(format, params,
|
||||||
trackFormatSupport[trackIndex], selectedAudioLanguage);
|
trackFormatSupport[trackIndex], selectedAudioLanguage);
|
||||||
|
|
||||||
if (formatHasLanguage(format, preferredTextLanguage)) {
|
if (formatHasLanguage(format, preferredTextLanguage)) {
|
||||||
selectedGroup = trackGroup;
|
selectedGroup = trackGroup;
|
||||||
selectedTrackIndex = trackIndex;
|
selectedTrackIndex = trackIndex;
|
||||||
selectedTrackScore = trackScore;
|
selectedTrackScore = trackScore;
|
||||||
// found user selected match (perfect!)
|
break; // found user selected match (perfect!)
|
||||||
break;
|
|
||||||
} else if (trackScore.isWithinConstraints && (selectedTrackScore == null
|
} else if (trackScore.isWithinConstraints && (selectedTrackScore == null
|
||||||
|| trackScore.compareTo(selectedTrackScore) > 0)) {
|
|| trackScore.compareTo(selectedTrackScore) > 0)) {
|
||||||
selectedGroup = trackGroup;
|
selectedGroup = trackGroup;
|
||||||
|
|
|
@ -4,7 +4,7 @@ import android.content.Context;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import org.acra.collector.CrashReportData;
|
import org.acra.data.CrashReportData;
|
||||||
import org.acra.sender.ReportSender;
|
import org.acra.sender.ReportSender;
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import android.content.Context;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import org.acra.config.ACRAConfiguration;
|
import org.acra.config.CoreConfiguration;
|
||||||
import org.acra.sender.ReportSender;
|
import org.acra.sender.ReportSender;
|
||||||
import org.acra.sender.ReportSenderFactory;
|
import org.acra.sender.ReportSenderFactory;
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ import org.acra.sender.ReportSenderFactory;
|
||||||
public class AcraReportSenderFactory implements ReportSenderFactory {
|
public class AcraReportSenderFactory implements ReportSenderFactory {
|
||||||
@NonNull
|
@NonNull
|
||||||
public ReportSender create(@NonNull final Context context,
|
public ReportSender create(@NonNull final Context context,
|
||||||
@NonNull final ACRAConfiguration config) {
|
@NonNull final CoreConfiguration config) {
|
||||||
return new AcraReportSender();
|
return new AcraReportSender();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ import com.google.android.material.snackbar.Snackbar;
|
||||||
import com.grack.nanojson.JsonWriter;
|
import com.grack.nanojson.JsonWriter;
|
||||||
|
|
||||||
import org.acra.ReportField;
|
import org.acra.ReportField;
|
||||||
import org.acra.collector.CrashReportData;
|
import org.acra.data.CrashReportData;
|
||||||
import org.schabi.newpipe.ActivityCommunicator;
|
import org.schabi.newpipe.ActivityCommunicator;
|
||||||
import org.schabi.newpipe.BuildConfig;
|
import org.schabi.newpipe.BuildConfig;
|
||||||
import org.schabi.newpipe.MainActivity;
|
import org.schabi.newpipe.MainActivity;
|
||||||
|
@ -149,14 +149,7 @@ public class ErrorActivity extends AppCompatActivity {
|
||||||
|
|
||||||
public static void reportError(final Context context, final CrashReportData report,
|
public static void reportError(final Context context, final CrashReportData report,
|
||||||
final ErrorInfo errorInfo) {
|
final ErrorInfo errorInfo) {
|
||||||
// get key first (don't ask about this solution)
|
String[] el = new String[]{report.getString(ReportField.STACK_TRACE)};
|
||||||
ReportField key = null;
|
|
||||||
for (ReportField k : report.keySet()) {
|
|
||||||
if (k.toString().equals("STACK_TRACE")) {
|
|
||||||
key = k;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
String[] el = new String[]{report.get(key).toString()};
|
|
||||||
|
|
||||||
Intent intent = new Intent(context, ErrorActivity.class);
|
Intent intent = new Intent(context, ErrorActivity.class);
|
||||||
intent.putExtra(ERROR_INFO, errorInfo);
|
intent.putExtra(ERROR_INFO, errorInfo);
|
||||||
|
@ -229,12 +222,15 @@ public class ErrorActivity extends AppCompatActivity {
|
||||||
context.startActivity(webIntent);
|
context.startActivity(webIntent);
|
||||||
})
|
})
|
||||||
.setPositiveButton(R.string.accept, (dialog, which) -> {
|
.setPositiveButton(R.string.accept, (dialog, which) -> {
|
||||||
Intent i = new Intent(Intent.ACTION_SENDTO);
|
final Intent i = new Intent(Intent.ACTION_SENDTO)
|
||||||
i.setData(Uri.parse("mailto:" + ERROR_EMAIL_ADDRESS))
|
.setData(Uri.parse("mailto:")) // only email apps should handle this
|
||||||
|
.putExtra(Intent.EXTRA_EMAIL, new String[]{ERROR_EMAIL_ADDRESS})
|
||||||
.putExtra(Intent.EXTRA_SUBJECT, ERROR_EMAIL_SUBJECT)
|
.putExtra(Intent.EXTRA_SUBJECT, ERROR_EMAIL_SUBJECT)
|
||||||
.putExtra(Intent.EXTRA_TEXT, buildJson());
|
.putExtra(Intent.EXTRA_TEXT, buildJson());
|
||||||
|
if (i.resolveActivity(getPackageManager()) != null) {
|
||||||
|
startActivity(i);
|
||||||
|
}
|
||||||
|
|
||||||
startActivity(Intent.createChooser(i, "Send Email"));
|
|
||||||
})
|
})
|
||||||
.setNegativeButton(R.string.decline, (dialog, which) -> {
|
.setNegativeButton(R.string.decline, (dialog, which) -> {
|
||||||
// do nothing
|
// do nothing
|
||||||
|
|
|
@ -2,6 +2,7 @@ package org.schabi.newpipe.settings;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
@ -17,6 +18,7 @@ import androidx.preference.Preference;
|
||||||
import com.nononsenseapps.filepicker.Utils;
|
import com.nononsenseapps.filepicker.Utils;
|
||||||
import com.nostra13.universalimageloader.core.ImageLoader;
|
import com.nostra13.universalimageloader.core.ImageLoader;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.DownloaderImpl;
|
||||||
import org.schabi.newpipe.NewPipeDatabase;
|
import org.schabi.newpipe.NewPipeDatabase;
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.extractor.NewPipe;
|
import org.schabi.newpipe.extractor.NewPipe;
|
||||||
|
@ -56,6 +58,7 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
|
||||||
private File newpipeSettings;
|
private File newpipeSettings;
|
||||||
|
|
||||||
private String thumbnailLoadToggleKey;
|
private String thumbnailLoadToggleKey;
|
||||||
|
private String youtubeRestrictedModeEnabledKey;
|
||||||
|
|
||||||
private Localization initialSelectedLocalization;
|
private Localization initialSelectedLocalization;
|
||||||
private ContentCountry initialSelectedContentCountry;
|
private ContentCountry initialSelectedContentCountry;
|
||||||
|
@ -65,6 +68,7 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
|
||||||
public void onCreate(@Nullable final Bundle savedInstanceState) {
|
public void onCreate(@Nullable final Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
thumbnailLoadToggleKey = getString(R.string.download_thumbnail_key);
|
thumbnailLoadToggleKey = getString(R.string.download_thumbnail_key);
|
||||||
|
youtubeRestrictedModeEnabledKey = getString(R.string.youtube_restricted_mode_enabled);
|
||||||
|
|
||||||
initialSelectedLocalization = org.schabi.newpipe.util.Localization
|
initialSelectedLocalization = org.schabi.newpipe.util.Localization
|
||||||
.getPreferredLocalization(requireContext());
|
.getPreferredLocalization(requireContext());
|
||||||
|
@ -86,6 +90,15 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
|
||||||
Toast.LENGTH_SHORT).show();
|
Toast.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (preference.getKey().equals(youtubeRestrictedModeEnabledKey)) {
|
||||||
|
Context context = getContext();
|
||||||
|
if (context != null) {
|
||||||
|
DownloaderImpl.getInstance().updateYoutubeRestrictedModeCookies(context);
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "onPreferenceTreeClick: null context");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return super.onPreferenceTreeClick(preference);
|
return super.onPreferenceTreeClick(preference);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ import android.widget.ProgressBar;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.fragment.app.DialogFragment;
|
import androidx.fragment.app.DialogFragment;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
@ -22,6 +23,7 @@ import org.schabi.newpipe.database.subscription.SubscriptionEntity;
|
||||||
import org.schabi.newpipe.local.subscription.SubscriptionManager;
|
import org.schabi.newpipe.local.subscription.SubscriptionManager;
|
||||||
import org.schabi.newpipe.report.ErrorActivity;
|
import org.schabi.newpipe.report.ErrorActivity;
|
||||||
import org.schabi.newpipe.report.UserAction;
|
import org.schabi.newpipe.report.UserAction;
|
||||||
|
import org.schabi.newpipe.util.ThemeHelper;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Vector;
|
import java.util.Vector;
|
||||||
|
@ -83,6 +85,12 @@ public class SelectChannelFragment extends DialogFragment {
|
||||||
// Init
|
// Init
|
||||||
//////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(@Nullable final Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setStyle(STYLE_NO_TITLE, ThemeHelper.getMinWidthDialogTheme(requireContext()));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup container,
|
public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup container,
|
||||||
final Bundle savedInstanceState) {
|
final Bundle savedInstanceState) {
|
||||||
|
|
|
@ -9,7 +9,8 @@ import android.view.ViewGroup;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.appcompat.content.res.AppCompatResources;
|
||||||
import androidx.fragment.app.DialogFragment;
|
import androidx.fragment.app.DialogFragment;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
@ -21,6 +22,7 @@ import org.schabi.newpipe.report.ErrorActivity;
|
||||||
import org.schabi.newpipe.report.UserAction;
|
import org.schabi.newpipe.report.UserAction;
|
||||||
import org.schabi.newpipe.util.KioskTranslator;
|
import org.schabi.newpipe.util.KioskTranslator;
|
||||||
import org.schabi.newpipe.util.ServiceHelper;
|
import org.schabi.newpipe.util.ServiceHelper;
|
||||||
|
import org.schabi.newpipe.util.ThemeHelper;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Vector;
|
import java.util.Vector;
|
||||||
|
@ -61,6 +63,16 @@ public class SelectKioskFragment extends DialogFragment {
|
||||||
onCancelListener = listener;
|
onCancelListener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
// Init
|
||||||
|
//////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(@Nullable final Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setStyle(STYLE_NO_TITLE, ThemeHelper.getMinWidthDialogTheme(requireContext()));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
|
public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
|
||||||
final Bundle savedInstanceState) {
|
final Bundle savedInstanceState) {
|
||||||
|
@ -148,13 +160,8 @@ public class SelectKioskFragment extends DialogFragment {
|
||||||
final Entry entry = kioskList.get(position);
|
final Entry entry = kioskList.get(position);
|
||||||
holder.titleView.setText(entry.kioskName);
|
holder.titleView.setText(entry.kioskName);
|
||||||
holder.thumbnailView
|
holder.thumbnailView
|
||||||
.setImageDrawable(ContextCompat.getDrawable(getContext(), entry.icon));
|
.setImageDrawable(AppCompatResources.getDrawable(requireContext(), entry.icon));
|
||||||
holder.view.setOnClickListener(new View.OnClickListener() {
|
holder.view.setOnClickListener(view -> clickedItem(entry));
|
||||||
@Override
|
|
||||||
public void onClick(final View view) {
|
|
||||||
clickedItem(entry);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class Entry {
|
class Entry {
|
||||||
|
|
|
@ -43,4 +43,4 @@ class DurationListPreference : ListPreference {
|
||||||
|
|
||||||
entries = newEntryTitles
|
entries = newEntryTitles
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,7 +60,7 @@ public final class AddTabDialog {
|
||||||
private DialogListAdapter(final Context context, final ChooseTabListItem[] items) {
|
private DialogListAdapter(final Context context, final ChooseTabListItem[] items) {
|
||||||
this.inflater = LayoutInflater.from(context);
|
this.inflater = LayoutInflater.from(context);
|
||||||
this.items = items;
|
this.items = items;
|
||||||
this.fallbackIcon = ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_hot);
|
this.fallbackIcon = ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_kiosk_hot);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -233,7 +233,7 @@ public class ChooseTabsFragment extends Fragment {
|
||||||
case KIOSK:
|
case KIOSK:
|
||||||
returnList.add(new ChooseTabListItem(tab.getTabId(),
|
returnList.add(new ChooseTabListItem(tab.getTabId(),
|
||||||
getString(R.string.kiosk_page_summary),
|
getString(R.string.kiosk_page_summary),
|
||||||
ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_hot)));
|
ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_kiosk_hot)));
|
||||||
break;
|
break;
|
||||||
case CHANNEL:
|
case CHANNEL:
|
||||||
returnList.add(new ChooseTabListItem(tab.getTabId(),
|
returnList.add(new ChooseTabListItem(tab.getTabId(),
|
||||||
|
@ -244,7 +244,8 @@ public class ChooseTabsFragment extends Fragment {
|
||||||
if (!tabList.contains(tab)) {
|
if (!tabList.contains(tab)) {
|
||||||
returnList.add(new ChooseTabListItem(tab.getTabId(),
|
returnList.add(new ChooseTabListItem(tab.getTabId(),
|
||||||
getString(R.string.default_kiosk_page_summary),
|
getString(R.string.default_kiosk_page_summary),
|
||||||
ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_hot)));
|
ThemeHelper.resolveResourceIdFromAttr(context,
|
||||||
|
R.attr.ic_kiosk_hot)));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -231,7 +231,7 @@ public abstract class Tab {
|
||||||
@DrawableRes
|
@DrawableRes
|
||||||
@Override
|
@Override
|
||||||
public int getTabIconRes(final Context context) {
|
public int getTabIconRes(final Context context) {
|
||||||
return ThemeHelper.resolveResourceIdFromAttr(context, R.attr.rss);
|
return ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_rss);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -281,7 +281,7 @@ public abstract class Tab {
|
||||||
@DrawableRes
|
@DrawableRes
|
||||||
@Override
|
@Override
|
||||||
public int getTabIconRes(final Context context) {
|
public int getTabIconRes(final Context context) {
|
||||||
return ThemeHelper.resolveResourceIdFromAttr(context, R.attr.history);
|
return ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_history);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -323,7 +323,7 @@ public abstract class Tab {
|
||||||
@DrawableRes
|
@DrawableRes
|
||||||
@Override
|
@Override
|
||||||
public int getTabIconRes(final Context context) {
|
public int getTabIconRes(final Context context) {
|
||||||
final int kioskIcon = KioskTranslator.getKioskIcons(kioskId, context);
|
final int kioskIcon = KioskTranslator.getKioskIcon(kioskId, context);
|
||||||
|
|
||||||
if (kioskIcon <= 0) {
|
if (kioskIcon <= 0) {
|
||||||
throw new IllegalStateException("Kiosk ID is not valid: \"" + kioskId + "\"");
|
throw new IllegalStateException("Kiosk ID is not valid: \"" + kioskId + "\"");
|
||||||
|
@ -459,7 +459,7 @@ public abstract class Tab {
|
||||||
@DrawableRes
|
@DrawableRes
|
||||||
@Override
|
@Override
|
||||||
public int getTabIconRes(final Context context) {
|
public int getTabIconRes(final Context context) {
|
||||||
return KioskTranslator.getKioskIcons(getDefaultKioskId(context), context);
|
return KioskTranslator.getKioskIcon(getDefaultKioskId(context), context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -5,8 +5,8 @@ import org.schabi.newpipe.streams.Mp4DashReader.Mdia;
|
||||||
import org.schabi.newpipe.streams.Mp4DashReader.Mp4DashChunk;
|
import org.schabi.newpipe.streams.Mp4DashReader.Mp4DashChunk;
|
||||||
import org.schabi.newpipe.streams.Mp4DashReader.Mp4DashSample;
|
import org.schabi.newpipe.streams.Mp4DashReader.Mp4DashSample;
|
||||||
import org.schabi.newpipe.streams.Mp4DashReader.Mp4Track;
|
import org.schabi.newpipe.streams.Mp4DashReader.Mp4Track;
|
||||||
import org.schabi.newpipe.streams.Mp4DashReader.TrunEntry;
|
|
||||||
import org.schabi.newpipe.streams.Mp4DashReader.TrackKind;
|
import org.schabi.newpipe.streams.Mp4DashReader.TrackKind;
|
||||||
|
import org.schabi.newpipe.streams.Mp4DashReader.TrunEntry;
|
||||||
import org.schabi.newpipe.streams.io.SharpStream;
|
import org.schabi.newpipe.streams.io.SharpStream;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -711,7 +711,8 @@ public class Mp4FromDashWriter {
|
||||||
|
|
||||||
for (int i = 0; i < tracks.length; i++) {
|
for (int i = 0; i < tracks.length; i++) {
|
||||||
if (tracks[i].trak.tkhd.matrix.length != 36) {
|
if (tracks[i].trak.tkhd.matrix.length != 36) {
|
||||||
throw new RuntimeException("bad track matrix length (expected 36) in track n°" + i);
|
throw
|
||||||
|
new RuntimeException("bad track matrix length (expected 36) in track n°" + i);
|
||||||
}
|
}
|
||||||
makeTrak(i, durations[i], defaultMediaTime[i], tablesInfo[i], is64);
|
makeTrak(i, durations[i], defaultMediaTime[i], tablesInfo[i], is64);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
package org.schabi.newpipe.util;
|
||||||
|
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public final class CookieUtils {
|
||||||
|
private CookieUtils() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String concatCookies(final Collection<String> cookieStrings) {
|
||||||
|
Set<String> cookieSet = new HashSet<>();
|
||||||
|
for (String cookies : cookieStrings) {
|
||||||
|
cookieSet.addAll(splitCookies(cookies));
|
||||||
|
}
|
||||||
|
return TextUtils.join("; ", cookieSet).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Set<String> splitCookies(final String cookies) {
|
||||||
|
return new HashSet<>(Arrays.asList(cookies.split("; *")));
|
||||||
|
}
|
||||||
|
}
|
|
@ -79,4 +79,4 @@ class ExceptionUtils {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,19 +49,19 @@ public final class KioskTranslator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getKioskIcons(final String kioskId, final Context c) {
|
public static int getKioskIcon(final String kioskId, final Context c) {
|
||||||
switch (kioskId) {
|
switch (kioskId) {
|
||||||
case "Trending":
|
case "Trending":
|
||||||
case "Top 50":
|
case "Top 50":
|
||||||
case "New & hot":
|
case "New & hot":
|
||||||
case "conferences":
|
case "conferences":
|
||||||
return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.ic_hot);
|
return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.ic_kiosk_hot);
|
||||||
case "Local":
|
case "Local":
|
||||||
return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.ic_kiosk_local);
|
return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.ic_kiosk_local);
|
||||||
case "Recently added":
|
case "Recently added":
|
||||||
return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.ic_kiosk_recent);
|
return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.ic_kiosk_recent);
|
||||||
case "Most liked":
|
case "Most liked":
|
||||||
return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.thumbs_up);
|
return ThemeHelper.resolveResourceIdFromAttr(c, R.attr.ic_thumb_up);
|
||||||
default:
|
default:
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,10 +7,16 @@ import android.content.DialogInterface;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
|
import org.schabi.newpipe.extractor.ServiceList;
|
||||||
|
|
||||||
public final class KoreUtil {
|
public final class KoreUtil {
|
||||||
private KoreUtil() { }
|
private KoreUtil() { }
|
||||||
|
|
||||||
|
public static boolean isServiceSupportedByKore(final int serviceId) {
|
||||||
|
return (serviceId == ServiceList.YouTube.getServiceId()
|
||||||
|
|| serviceId == ServiceList.SoundCloud.getServiceId());
|
||||||
|
}
|
||||||
|
|
||||||
public static void showInstallKoreDialog(final Context context) {
|
public static void showInstallKoreDialog(final Context context) {
|
||||||
final AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
final AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
||||||
builder.setMessage(R.string.kore_not_found)
|
builder.setMessage(R.string.kore_not_found)
|
||||||
|
|
|
@ -5,6 +5,7 @@ import android.content.SharedPreferences;
|
||||||
import android.net.ConnectivityManager;
|
import android.net.ConnectivityManager;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.StringRes;
|
import androidx.annotation.StringRes;
|
||||||
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
|
@ -267,23 +268,22 @@ public final class ListHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the audio from the list with the highest quality. Format will be ignored if it yields
|
* Get the audio from the list with the highest quality.
|
||||||
* no results.
|
* Format will be ignored if it yields no results.
|
||||||
*
|
*
|
||||||
* @param format the format to look for
|
* @param format The target format type or null if it doesn't matter
|
||||||
* @param audioStreams list the audio streams
|
* @param audioStreams List of audio streams
|
||||||
* @return index of the audio with the highest average bitrate of the default format
|
* @return Index of audio stream that produces the most compact results or -1 if not found
|
||||||
*/
|
*/
|
||||||
static int getHighestQualityAudioIndex(final MediaFormat format,
|
static int getHighestQualityAudioIndex(@Nullable MediaFormat format,
|
||||||
final List<AudioStream> audioStreams) {
|
final List<AudioStream> audioStreams) {
|
||||||
int result = -1;
|
int result = -1;
|
||||||
boolean hasOneFormat = false;
|
|
||||||
if (audioStreams != null) {
|
if (audioStreams != null) {
|
||||||
while (result == -1) {
|
while (result == -1) {
|
||||||
AudioStream prevStream = null;
|
AudioStream prevStream = null;
|
||||||
for (int idx = 0; idx < audioStreams.size(); idx++) {
|
for (int idx = 0; idx < audioStreams.size(); idx++) {
|
||||||
AudioStream stream = audioStreams.get(idx);
|
AudioStream stream = audioStreams.get(idx);
|
||||||
if ((format == null || stream.getFormat() == format || hasOneFormat)
|
if ((format == null || stream.getFormat() == format)
|
||||||
&& (prevStream == null || compareAudioStreamBitrate(prevStream, stream,
|
&& (prevStream == null || compareAudioStreamBitrate(prevStream, stream,
|
||||||
AUDIO_FORMAT_QUALITY_RANKING) < 0)) {
|
AUDIO_FORMAT_QUALITY_RANKING) < 0)) {
|
||||||
prevStream = stream;
|
prevStream = stream;
|
||||||
|
@ -293,30 +293,29 @@ public final class ListHelper {
|
||||||
if (result == -1 && format == null) {
|
if (result == -1 && format == null) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
hasOneFormat = true;
|
format = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the audio from the list with the lowest bitrate and efficient format. Format will be
|
* Get the audio from the list with the lowest bitrate and most efficient format.
|
||||||
* ignored if it yields no results.
|
* Format will be ignored if it yields no results.
|
||||||
*
|
*
|
||||||
* @param format The target format type or null if it doesn't matter
|
* @param format The target format type or null if it doesn't matter
|
||||||
* @param audioStreams List of audio streams
|
* @param audioStreams List of audio streams
|
||||||
* @return Index of audio stream that can produce the most compact results or -1 if not found
|
* @return Index of audio stream that produces the most compact results or -1 if not found
|
||||||
*/
|
*/
|
||||||
static int getMostCompactAudioIndex(final MediaFormat format,
|
static int getMostCompactAudioIndex(@Nullable MediaFormat format,
|
||||||
final List<AudioStream> audioStreams) {
|
final List<AudioStream> audioStreams) {
|
||||||
int result = -1;
|
int result = -1;
|
||||||
boolean hasOneFormat = false;
|
|
||||||
if (audioStreams != null) {
|
if (audioStreams != null) {
|
||||||
while (result == -1) {
|
while (result == -1) {
|
||||||
AudioStream prevStream = null;
|
AudioStream prevStream = null;
|
||||||
for (int idx = 0; idx < audioStreams.size(); idx++) {
|
for (int idx = 0; idx < audioStreams.size(); idx++) {
|
||||||
AudioStream stream = audioStreams.get(idx);
|
AudioStream stream = audioStreams.get(idx);
|
||||||
if ((format == null || stream.getFormat() == format || hasOneFormat)
|
if ((format == null || stream.getFormat() == format)
|
||||||
&& (prevStream == null || compareAudioStreamBitrate(prevStream, stream,
|
&& (prevStream == null || compareAudioStreamBitrate(prevStream, stream,
|
||||||
AUDIO_FORMAT_EFFICIENCY_RANKING) > 0)) {
|
AUDIO_FORMAT_EFFICIENCY_RANKING) > 0)) {
|
||||||
prevStream = stream;
|
prevStream = stream;
|
||||||
|
@ -326,7 +325,7 @@ public final class ListHelper {
|
||||||
if (result == -1 && format == null) {
|
if (result == -1 && format == null) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
hasOneFormat = true;
|
format = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
|
|
@ -2,19 +2,74 @@ package org.schabi.newpipe.util;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.content.pm.ResolveInfo;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
|
|
||||||
public final class ShareUtils {
|
public final class ShareUtils {
|
||||||
private ShareUtils() { }
|
private ShareUtils() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open the url with the system default browser.
|
||||||
|
* <p>
|
||||||
|
* If no browser is set as default, fallbacks to
|
||||||
|
* {@link ShareUtils#openInDefaultApp(Context, String)}
|
||||||
|
*
|
||||||
|
* @param context the context to use
|
||||||
|
* @param url the url to browse
|
||||||
|
*/
|
||||||
public static void openUrlInBrowser(final Context context, final String url) {
|
public static void openUrlInBrowser(final Context context, final String url) {
|
||||||
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
|
final String defaultBrowserPackageName = getDefaultBrowserPackageName(context);
|
||||||
|
|
||||||
|
if (defaultBrowserPackageName.equals("android")) {
|
||||||
|
// no browser set as default
|
||||||
|
openInDefaultApp(context, url);
|
||||||
|
} else {
|
||||||
|
final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
|
||||||
|
intent.setPackage(defaultBrowserPackageName);
|
||||||
|
context.startActivity(intent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open the url in the default app set to open this type of link.
|
||||||
|
* <p>
|
||||||
|
* If no app is set as default, it will open a chooser
|
||||||
|
*
|
||||||
|
* @param context the context to use
|
||||||
|
* @param url the url to browse
|
||||||
|
*/
|
||||||
|
private static void openInDefaultApp(final Context context, final String url) {
|
||||||
|
final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
|
||||||
context.startActivity(Intent.createChooser(
|
context.startActivity(Intent.createChooser(
|
||||||
intent, context.getString(R.string.share_dialog_title)));
|
intent, context.getString(R.string.share_dialog_title)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the default browser package name.
|
||||||
|
* <p>
|
||||||
|
* If no browser is set as default, it will return "android"
|
||||||
|
*
|
||||||
|
* @param context the context to use
|
||||||
|
* @return the package name of the default browser, or "android" if there's no default
|
||||||
|
*/
|
||||||
|
private static String getDefaultBrowserPackageName(final Context context) {
|
||||||
|
final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://"));
|
||||||
|
final ResolveInfo resolveInfo = context.getPackageManager().resolveActivity(
|
||||||
|
intent, PackageManager.MATCH_DEFAULT_ONLY);
|
||||||
|
return resolveInfo.activityInfo.packageName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open the android share menu to share the current url.
|
||||||
|
*
|
||||||
|
* @param context the context to use
|
||||||
|
* @param subject the url subject, typically the title
|
||||||
|
* @param url the url to share
|
||||||
|
*/
|
||||||
public static void shareUrl(final Context context, final String subject, final String url) {
|
public static void shareUrl(final Context context, final String subject, final String url) {
|
||||||
Intent intent = new Intent(Intent.ACTION_SEND);
|
Intent intent = new Intent(Intent.ACTION_SEND);
|
||||||
intent.setType("text/plain");
|
intent.setType("text/plain");
|
||||||
|
|
|
@ -231,16 +231,4 @@ public final class ThemeHelper {
|
||||||
return PreferenceManager.getDefaultSharedPreferences(context)
|
return PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
.getString(themeKey, defaultTheme);
|
.getString(themeKey, defaultTheme);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* This will get the R.drawable.* resource to which attr is currently pointing to.
|
|
||||||
*
|
|
||||||
* @param attr a R.attribute.* resource value
|
|
||||||
* @param context the context to use
|
|
||||||
* @return a R.drawable.* resource value
|
|
||||||
*/
|
|
||||||
public static int getIconByAttr(final int attr, final Context context) {
|
|
||||||
return context.obtainStyledAttributes(new int[]{attr})
|
|
||||||
.getResourceId(0, -1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,4 +24,4 @@ class UrlFinder {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ import android.view.ViewTreeObserver;
|
||||||
import android.widget.SeekBar;
|
import android.widget.SeekBar;
|
||||||
|
|
||||||
import androidx.appcompat.widget.AppCompatSeekBar;
|
import androidx.appcompat.widget.AppCompatSeekBar;
|
||||||
|
|
||||||
import org.schabi.newpipe.util.AndroidTvUtils;
|
import org.schabi.newpipe.util.AndroidTvUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -6,8 +6,8 @@ import android.system.ErrnoException;
|
||||||
import android.system.OsConstants;
|
import android.system.OsConstants;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import org.schabi.newpipe.DownloaderImpl;
|
import org.schabi.newpipe.DownloaderImpl;
|
||||||
|
|
||||||
|
|
|
@ -6,9 +6,10 @@ import android.database.Cursor;
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
import android.database.sqlite.SQLiteOpenHelper;
|
import android.database.sqlite.SQLiteOpenHelper;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
|
|
@ -3,9 +3,10 @@ package us.shandian.giga.io;
|
||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.ParcelFileDescriptor;
|
import android.os.ParcelFileDescriptor;
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import org.schabi.newpipe.streams.io.SharpStream;
|
import org.schabi.newpipe.streams.io.SharpStream;
|
||||||
|
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
|
|
|
@ -8,6 +8,7 @@ import android.database.Cursor;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.provider.DocumentsContract;
|
import android.provider.DocumentsContract;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.documentfile.provider.DocumentFile;
|
import androidx.documentfile.provider.DocumentFile;
|
||||||
|
|
|
@ -7,10 +7,11 @@ import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.provider.DocumentsContract;
|
import android.provider.DocumentsContract;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.fragment.app.Fragment;
|
|
||||||
import androidx.documentfile.provider.DocumentFile;
|
import androidx.documentfile.provider.DocumentFile;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
|
||||||
import org.schabi.newpipe.streams.io.SharpStream;
|
import org.schabi.newpipe.streams.io.SharpStream;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package us.shandian.giga.ui.common;
|
package us.shandian.giga.ui.common;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.appcompat.widget.Toolbar;
|
import androidx.appcompat.widget.Toolbar;
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,6 @@ import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.CheckBox;
|
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
@ -225,15 +224,9 @@ public class MissionsFragment extends Fragment {
|
||||||
mList.setAdapter(mAdapter);
|
mList.setAdapter(mAdapter);
|
||||||
|
|
||||||
if (mSwitch != null) {
|
if (mSwitch != null) {
|
||||||
boolean isLight = ThemeHelper.isLightThemeSelected(mContext);
|
mSwitch.setIcon(mLinear
|
||||||
int icon;
|
? ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_grid)
|
||||||
|
: ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_list));
|
||||||
if (mLinear)
|
|
||||||
icon = isLight ? R.drawable.ic_grid_black_24dp : R.drawable.ic_grid_white_24dp;
|
|
||||||
else
|
|
||||||
icon = isLight ? R.drawable.ic_list_black_24dp : R.drawable.ic_list_white_24dp;
|
|
||||||
|
|
||||||
mSwitch.setIcon(icon);
|
|
||||||
mSwitch.setTitle(mLinear ? R.string.grid : R.string.list);
|
mSwitch.setTitle(mLinear ? R.string.grid : R.string.list);
|
||||||
mPrefs.edit().putBoolean("linear", mLinear).apply();
|
mPrefs.edit().putBoolean("linear", mLinear).apply();
|
||||||
}
|
}
|
||||||
|
|
|
@ -191,12 +191,12 @@ public class Utility {
|
||||||
public static int getIconForFileType(FileType type) {
|
public static int getIconForFileType(FileType type) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case MUSIC:
|
case MUSIC:
|
||||||
return R.drawable.music;
|
return R.drawable.ic_headset_white_24dp;
|
||||||
default:
|
default:
|
||||||
case VIDEO:
|
case VIDEO:
|
||||||
return R.drawable.video;
|
return R.drawable.ic_movie_white_24dp;
|
||||||
case SUBTITLE:
|
case SUBTITLE:
|
||||||
return R.drawable.subtitle;
|
return R.drawable.ic_subtitles_white_24dp;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 575 B |
Binary file not shown.
Before Width: | Height: | Size: 535 B |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue