Merge pull request #3669 from TeamNewPipe/release_0.19.4

Release 0.19.4
This commit is contained in:
Tobias Groza 2020-05-29 19:13:09 +02:00 committed by GitHub
commit b2a5ff5f9d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
927 changed files with 4407 additions and 3429 deletions

8
.gitignore vendored
View File

@ -10,3 +10,11 @@
*~ *~
.weblate .weblate
*.class *.class
# vscode / eclipse files
*.classpath
*.project
*.settings
bin/
.vscode/
*.code-workspace

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,8 +2,8 @@ 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

View File

@ -2,11 +2,11 @@ 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

View File

@ -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
} }
} }
} }
@ -132,5 +135,6 @@ abstract class StreamDAO : BasicDAO<StreamEntity> {
var isUploadDateApproximation: Boolean? = null, var isUploadDateApproximation: Boolean? = null,
@ColumnInfo(name = StreamEntity.STREAM_DURATION) @ColumnInfo(name = StreamEntity.STREAM_DURATION)
var duration: Long) var duration: Long
)
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,8 +1,8 @@
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(

View File

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

View File

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

View File

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

View File

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

View File

@ -20,10 +20,10 @@ enum class FeedGroupIcon(
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,29 +32,29 @@ 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 {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,14 +4,16 @@ 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,

View File

@ -2,7 +2,8 @@ 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

View File

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

View File

@ -6,7 +6,9 @@ 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
@ -17,8 +19,8 @@ data class FeedGroupReorderItem(
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) {

View File

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

View File

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

View File

@ -1,10 +1,13 @@
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(

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 223 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 554 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 546 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 337 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 345 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 180 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 185 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 258 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 267 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 384 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 394 B

Some files were not shown because too many files have changed in this diff Show More