SponsorBlock: Merge branch 'dev' into sponsorblock

# Conflicts:
#	app/src/main/res/layout-large-land/player.xml
#	app/src/main/res/layout/player.xml
#	app/src/main/res/values/attrs.xml
This commit is contained in:
polymorphicshade 2020-10-24 15:25:21 -06:00
commit 9313439d1a
678 changed files with 6012 additions and 4694 deletions

View file

@ -7,20 +7,20 @@ assignees: ''
--- ---
<!-- <!--
Oh no, a bug! It happens. Thanks for reporting an issue with NewPipe. If this is your first bug report, read the following information before proceeding: Oh no, a bug! It happens. Thanks for reporting an issue with NewPipe. To make it easier for us to help you please enter detailed information in the template we have provided below. If a section isn't relevant, just delete it, though it would be helpful to still provide as much detail as possible.
Please note, we only support the latest version of NewPipe. In order to check your app version, open the left drawer and click on "About". If you don't have the latest version, upgrade to it and reproduce the problem before opening the issue. The release page (https://github.com/TeamNewPipe/NewPipe/releases/latest) is where you can get it.
P.S.: Our contribution guidelines might be a nice document to read before you fill out the report :) You can find it at https://github.com/TeamNewPipe/NewPipe/blob/HEAD/.github/CONTRIBUTING.md
To make it easier for us to help you please enter detailed information in the template we have provided below. If a section isn't relevant, just delete it, though it would be helpful to still provide as much detail as possible.
--> -->
<!-- The comments between these brackets won't show up in the submitted issue (as you can see in the preview). --> <!-- IF YOU DON'T FILL IN THE TEMPLATE PROPERLY, YOUR ISSUE IS LIABLE TO BE CLOSED. If you feel tired/lazy right now, open your issue some other time. We'll wait. -->
### Version <!-- The comments between these brackets won't show up in the submitted issue (as you can see in the Preview). -->
<!-- Which version are you using? Hopefully the latest! We just told you that above! -->
- ### Checklist
<!-- The first box has been checked for you to show you how it is done. -->
- [x] I am using the latest version - x.xx.x <!-- Check https://github.com/TeamNewPipe/NewPipe/releases -->
- [ ] I checked, but didn't find any duplicates (open OR closed) of this issue in the repo. <!-- Seriously, check. O_O -->
- [ ] I have read the contribution guidelines given at https://github.com/TeamNewPipe/NewPipe/blob/HEAD/.github/CONTRIBUTING.md.
- [ ] This issue contains only one bug. I will open one issue for every bug report I want to file.
### Steps to reproduce the bug ### Steps to reproduce the bug
<!-- <!--
@ -31,16 +31,35 @@ To make it easier for us to help you please enter detailed information in the te
<!-- If you can't cause the bug to show up again reliably (and hence don't have a proper set of steps to give us), please still try to give as many details as possible on how you think you encountered the bug. --> <!-- If you can't cause the bug to show up again reliably (and hence don't have a proper set of steps to give us), please still try to give as many details as possible on how you think you encountered the bug. -->
### Actual behaviour
<!-- Tell us what happens with the steps given above. -->
### Expected behavior ### Expected behavior
<!-- Tell us what you expect to happen. --> <!-- Tell us what you expect to happen. -->
### Actual behaviour
<!-- Tell us what happens instead. -->
### Screenshots/Screen recordings ### Screenshots/Screen recordings
<!-- If applicable, add screenshots or a screen recording to help explain your problem. GitHub supports uploading them directly in the issue text box. If your file is too big for Github to accept, feel free to paste a link from an image/video hoster here instead. --> <!-- If applicable, add screenshots or a screen recording to help explain your problem. GitHub supports uploading them directly in the issue text box. If your file is too big for Github to accept, feel free to paste a link from an image/video hoster here instead. -->
<!-- DON'T POST SCREENSHOTS OF THE ERROR PAGE. Use the buttons given on the error page to paste the error as text in the Logs section below. -->
### Logs ### Logs
<!-- If your bug includes a crash (where you're shown the Error Report page with a bunch of info), copy it to the clipboard (there is a share button for this), head over to our bug report to markdown converter at https://teamnewpipe.github.io/CrashReportToMarkdown/ and paste it. Copy the converted text (it is MUCH easier to read this way) from there and paste it here: --> <!-- If your bug includes a crash (where you're shown the Error Report page with a bunch of info), tap on "Copy formatted report" at the bottom and paste it here: -->
<!-- That's right, here! --> <!-- That's right, here! -->
<!-- Please fill this out when you do not provide a log generate by NewPipe -->
### Device info
- Android version/Custom ROM version:
- Device model:

1
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View file

@ -0,0 +1 @@
blank_issues_enabled: false

View file

@ -5,35 +5,42 @@ labels: enhancement
assignees: '' assignees: ''
--- ---
<!-- Hey. Our contribution guidelines (https://github.com/TeamNewPipe/NewPipe/blob/HEAD/.github/CONTRIBUTING.md) might be an appropriate <!-- IF YOU DON'T FILL IN THE TEMPLATE PROPERLY, YOUR ISSUE IS LIABLE TO BE CLOSED. If you feel tired/lazy right now, open your issue some other time. We'll wait. -->
document to read before you fill out the request :) -->
<!-- The comments between these brackets won't show up in the submitted issue (as you can see in the Preview). -->
### Checklist
<!-- The first box has been checked for you to show you how it is done. -->
- [x] I checked, but didn't find any duplicates (open OR closed) of this issue in the repo. <!-- Seriously, check. O_O -->
- [ ] I have read the contribution guidelines given at https://github.com/TeamNewPipe/NewPipe/blob/HEAD/.github/CONTRIBUTING.md.
- [ ] This issue contains only one feature request. I will open one issue for every feature I want to request.
<!-- The comments between these brackets won't show up in the submitted issue (as you can see in the preview). -->
#### Describe the feature you want #### Describe the feature you want
<!-- A clear and concise description of what you want to happen. PLEASE MAKE SURE it is one feature ONLY. You should open separate issues for separate feature requests, because those issues will be used to track their status. <!-- A clear and concise description of what you wish should happen.
Example: *I think it would be nice if you add feature Y which makes X possible.* Example: *I think it would be nice if you add feature Y which makes X possible.*
Optionally, also describe alternatives you've considered. Optionally, also describe alternatives you've considered.
Example: *Z is also a good alternative. Not as good as Y, but at least...* or *I considered Z, but that didn't turn out to be a good idea because...* --> Example: *Z is also a good alternative. Not as good as Y, but at least...* or *I considered Z, but that didn't turn out to be a good idea because...* -->
<!-- Write below this -->
#### Is your feature request related to a problem? Please describe it #### Is your feature request related to a problem? Please describe it
<!-- A clear and concise description of what the problem is. Maybe the developers could brainstorm and come up with a better solution to your problem. If they exist, link to related Issues and/or PRs for developers to keep track easier. <!-- A clear and concise description of what the problem is. Maybe the developers and the community could brainstorm and come up with a better solution to your problem. If they exist, link to related Issues and/or PRs for developers to keep track easier.
Example: *I want to do X, but there is no way to do it.* --> Example: *I want to do X, but there is no way to do it.* -->
<!-- Write below this -->
#### Additional context #### Additional context
<!-- Add any other context, like screenshots, about the feature request here. <!-- Add any other context, like screenshots, about the feature request here.
Example: *Here's a photo of my cat!* --> Example: *Here's a photo of my cat!* -->
<!-- Write below this -->
#### How will you/everyone benefit from this feature? #### How will you/everyone benefit from this feature?
<!-- Convince us! How does it change your NewPipe experience and/or your life? <!-- Convince us! How does it change your NewPipe experience and/or your life?
The better this paragraph is, the more likely a developer will think about working on it. The better this paragraph is, the more likely a developer will think about working on it.
Example: *This feature will help us colonize the galaxy! --> Example: *This feature will help us colonize the galaxy! -->
<!-- Write below this -->

View file

@ -1,28 +1,28 @@
<!-- Hey there. Thank you so much for improving NewPipe. Please take a moment to fill out the following suggestion on how to structure this PR description. Having roughly the same layout helps everyone considerably :)--> <!-- Hey there. Thank you so much for improving NewPipe, and filling out the details. Having roughly the same layout helps everyone considerably :)-->
#### What is it? #### What is it?
- [ ] Bug fix (user facing) - [ ] Bugfix (user facing)
- [ ] Feature (user facing) - [ ] Feature (user facing)
- [ ] Code base improvement (dev facing) - [ ] Codebase improvement (dev facing)
- [ ] Meta improvement to the project (dev facing) - [ ] Meta improvement to the project (dev facing)
#### Description of the changes in your PR #### Description of the changes in your PR
<!-- While bullet points are the norm in this section, feel free to write a text instead if you can't fit it in a list --> <!-- While bullet points are the norm in this section, feel free to write free-form text instead of a list -->
- record videos - record videos
- create clones - create clones
- take over the world - take over the world
#### Fixes the following issue(s) #### Fixes the following issue(s)
<!-- Also add reddit or other links which are relevant to your change. --> <!-- Also add any other links relevant to your change. -->
- -
#### Relies on the following changes #### Relies on the following changes
<!-- Delete this if it doesn't apply to you. --> <!-- Delete this if it doesn't apply to you. -->
- -
#### Testing apk #### APK testing
<!-- Ensure that you have your changes on a new branch which has a meaningful name. This name will be used as a suffix for the app ID to allow installing and testing multiple versions of NewPipe. Do NOT name your branches like "patch-0" and "feature-1". For example, if your PR implements a bug fix for comments, an appropriate branch name would be "commentfix". --> <!-- Use a new, meaningfully named branch. The name is used as a suffix for the app ID to allow installing and testing multiple versions of NewPipe, e.g. "commentfix", if your PR implements a bugfix for comments. (No names like "patch-0" and "feature-1".) -->
debug.zip debug.zip
#### Agreement #### Due diligence
- [ ] I carefully read the [contribution guidelines](https://github.com/TeamNewPipe/NewPipe/blob/HEAD/.github/CONTRIBUTING.md) and agree to them. - [ ] I read the [contribution guidelines](https://github.com/TeamNewPipe/NewPipe/blob/HEAD/.github/CONTRIBUTING.md).

View file

@ -22,16 +22,16 @@
## Screenshots ## Screenshots
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_01.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_01.png) [<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_01.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_01.png)<!--
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_02.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_02.png) [<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_02.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_02.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_03.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_03.png) [<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_03.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_03.png)-->
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_04.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_04.png) [<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_04.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_04.png)<!--
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_05.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_05.png) [<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_05.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_05.png)-->
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_06.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_06.png) [<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_06.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_06.png)<!--
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_07.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_07.png) [<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_07.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_07.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_08.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_08.png) [<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_08.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_08.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_09.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_09.png) [<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_09.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_09.png)
[<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_10.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_10.png) [<img src="fastlane/metadata/android/en-US/images/phoneScreenshots/shot_10.png" width=160>](fastlane/metadata/android/en-US/images/phoneScreenshots/shot_10.png)-->
[<img src="fastlane/metadata/android/en-US/images/tenInchScreenshots/shot_11.png" width=405>](fastlane/metadata/android/en-US/images/tenInchScreenshots/shot_11.png) [<img src="fastlane/metadata/android/en-US/images/tenInchScreenshots/shot_11.png" width=405>](fastlane/metadata/android/en-US/images/tenInchScreenshots/shot_11.png)
[<img src="fastlane/metadata/android/en-US/images/tenInchScreenshots/shot_12.png" width=405>](fastlane/metadata/android/en-US/images/tenInchScreenshots/shot_12.png) [<img src="fastlane/metadata/android/en-US/images/tenInchScreenshots/shot_12.png" width=405>](fastlane/metadata/android/en-US/images/tenInchScreenshots/shot_12.png)
@ -69,11 +69,6 @@ NewPipe does not use any Google framework libraries, nor the YouTube API. Websit
* Livestream support * Livestream support
* Show comments * Show comments
### Coming Features
* Cast to UPnP and Cast
* … and many more
### Supported Services ### Supported Services
NewPipe supports multiple services. Our [docs](https://teamnewpipe.github.io/documentation/) provide more info on how a new service can be added to the app and the extractor. Please get in touch with us if you intend to add a new one. Currently supported services are: NewPipe supports multiple services. Our [docs](https://teamnewpipe.github.io/documentation/) provide more info on how a new service can be added to the app and the extractor. Please get in touch with us if you intend to add a new one. Currently supported services are:
@ -103,6 +98,10 @@ The more is done the better it gets!
If you'd like to get involved, check our [contribution notes](.github/CONTRIBUTING.md). If you'd like to get involved, check our [contribution notes](.github/CONTRIBUTING.md).
<a href="https://hosted.weblate.org/engage/newpipe/">
<img src="https://hosted.weblate.org/widgets/newpipe/-/287x66-grey.png" alt="Translation status" />
</a>
## Donate ## Donate
If you like NewPipe we'd be happy about a donation. You can either send bitcoin or donate via Bountysource or Liberapay. For further info on donating to NewPipe, please visit our [website](https://newpipe.schabi.org/donate). If you like NewPipe we'd be happy about a donation. You can either send bitcoin or donate via Bountysource or Liberapay. For further info on donating to NewPipe, please visit our [website](https://newpipe.schabi.org/donate).

View file

@ -13,8 +13,10 @@ android {
resValue "string", "app_name", "NewPipe" resValue "string", "app_name", "NewPipe"
minSdkVersion 19 minSdkVersion 19
targetSdkVersion 29 targetSdkVersion 29
versionCode 954 versionCode 955
versionName "0.20.0" versionName "0.20.1"
multiDexEnabled true
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true
@ -28,7 +30,6 @@ android {
buildTypes { buildTypes {
debug { debug {
multiDexEnabled true
debuggable true debuggable true
// suffix the app id and the app name with git branch name // suffix the app id and the app name with git branch name
@ -69,6 +70,10 @@ android {
encoding 'utf-8' encoding 'utf-8'
} }
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8
}
// Required and used only by groupie // Required and used only by groupie
androidExtensions { androidExtensions {
experimental = true experimental = true
@ -94,7 +99,7 @@ ext {
configurations { configurations {
checkstyle checkstyle
ktlint // ktlint
} }
checkstyle { checkstyle {
@ -122,20 +127,20 @@ task runCheckstyle(type: Checkstyle) {
} }
} }
task runKtlint(type: JavaExec) { //task runKtlint(type: JavaExec) {
main = "com.pinterest.ktlint.Main" // main = "com.pinterest.ktlint.Main"
classpath = configurations.ktlint // classpath = configurations.ktlint
args "src/**/*.kt" // args "src/**/*.kt"
} //}
//
task formatKtlint(type: JavaExec) { //task formatKtlint(type: JavaExec) {
main = "com.pinterest.ktlint.Main" // main = "com.pinterest.ktlint.Main"
classpath = configurations.ktlint // classpath = configurations.ktlint
args "-F", "src/**/*.kt" // args "-F", "src/**/*.kt"
} //}
afterEvaluate { afterEvaluate {
preDebugBuild.dependsOn runCheckstyle, runKtlint preDebugBuild.dependsOn runCheckstyle //, runKtlint
} }
dependencies { dependencies {
@ -145,7 +150,7 @@ dependencies {
kapt "frankiesardo:icepick-processor:${icepickVersion}" kapt "frankiesardo:icepick-processor:${icepickVersion}"
checkstyle "com.puppycrawl.tools:checkstyle:${checkstyleVersion}" checkstyle "com.puppycrawl.tools:checkstyle:${checkstyleVersion}"
ktlint "com.pinterest:ktlint:0.35.0" // ktlint "com.pinterest:ktlint:0.35.0"
debugImplementation "com.facebook.stetho:stetho:${stethoVersion}" debugImplementation "com.facebook.stetho:stetho:${stethoVersion}"
debugImplementation "com.facebook.stetho:stetho-okhttp3:${stethoVersion}" debugImplementation "com.facebook.stetho:stetho-okhttp3:${stethoVersion}"
@ -153,9 +158,9 @@ dependencies {
debugImplementation "com.squareup.leakcanary:leakcanary-android:${leakCanaryVersion}" debugImplementation "com.squareup.leakcanary:leakcanary-android:${leakCanaryVersion}"
implementation "com.squareup.leakcanary:leakcanary-object-watcher-android:${leakCanaryVersion}" implementation "com.squareup.leakcanary:leakcanary-object-watcher-android:${leakCanaryVersion}"
debugImplementation "androidx.multidex:multidex:2.0.1" implementation "androidx.multidex:multidex:2.0.1"
testImplementation 'junit:junit:4.13' testImplementation 'junit:junit:4.13.1'
testImplementation 'org.mockito:mockito-core:3.3.3' testImplementation 'org.mockito:mockito-core:3.3.3'
androidTestImplementation "androidx.test.ext:junit:1.1.1" androidTestImplementation "androidx.test.ext:junit:1.1.1"
@ -164,9 +169,11 @@ dependencies {
exclude module: 'support-annotations' exclude module: 'support-annotations'
} }
implementation 'com.github.TeamNewPipe:NewPipeExtractor:2463884aa8b696df5812f7feff553008bbd2f888' // NewPipe dependencies
// You can use a local version by uncommenting a few lines in settings.gradle
implementation 'com.github.TeamNewPipe:NewPipeExtractor:350eed6214b93255d788dfa208b1e9a5e5da91e6'
implementation "com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751" implementation "com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751"
implementation "org.jsoup:jsoup:1.13.1" implementation "org.jsoup:jsoup:1.13.1"
implementation "com.squareup.okhttp3:okhttp:3.12.12" implementation "com.squareup.okhttp3:okhttp:3.12.12"
@ -184,6 +191,7 @@ dependencies {
implementation "androidx.recyclerview:recyclerview:1.1.0" implementation "androidx.recyclerview:recyclerview:1.1.0"
implementation "androidx.cardview:cardview:1.0.0" implementation "androidx.cardview:cardview:1.0.0"
implementation "androidx.constraintlayout:constraintlayout:1.1.3" implementation "androidx.constraintlayout:constraintlayout:1.1.3"
implementation 'androidx.core:core-ktx:1.3.1'
implementation "androidx.lifecycle:lifecycle-livedata:${androidxLifecycleVersion}" implementation "androidx.lifecycle:lifecycle-livedata:${androidxLifecycleVersion}"
implementation "androidx.lifecycle:lifecycle-viewmodel:${androidxLifecycleVersion}" implementation "androidx.lifecycle:lifecycle-viewmodel:${androidxLifecycleVersion}"

View file

@ -1,6 +1,5 @@
package org.schabi.newpipe package org.schabi.newpipe
import androidx.multidex.MultiDex
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.facebook.stetho.Stetho import com.facebook.stetho.Stetho
import com.facebook.stetho.okhttp3.StethoInterceptor import com.facebook.stetho.okhttp3.StethoInterceptor
@ -28,12 +27,6 @@ class DebugApp : App() {
return downloader return downloader
} }
override fun initACRA() {
// install MultiDex before initializing ACRA
MultiDex.install(this)
super.initACRA()
}
private fun initStetho() { private fun initStetho() {
// Create an InitializerBuilder // Create an InitializerBuilder
val initializerBuilder = Stetho.newInitializerBuilder(this) val initializerBuilder = Stetho.newInitializerBuilder(this)

View file

@ -0,0 +1,27 @@
package org.schabi.newpipe.settings;
import android.os.Bundle;
import androidx.annotation.Nullable;
import org.schabi.newpipe.R;
import leakcanary.LeakCanary;
public class DebugSettingsFragment extends BasePreferenceFragment {
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
findPreference(getString(R.string.show_memory_leaks_key))
.setOnPreferenceClickListener(preference -> {
startActivity(LeakCanary.INSTANCE.newLeakDisplayActivityIntent());
return true;
});
}
@Override
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
addPreferencesFromResource(R.xml.debug_settings);
}
}

View file

@ -1,57 +1,56 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:key="general_preferences" android:key="general_preferences"
android:title="@string/settings"> android:title="@string/settings">
<PreferenceScreen <PreferenceScreen
app:iconSpaceReserved="false"
android:fragment="org.schabi.newpipe.settings.VideoAudioSettingsFragment" android:fragment="org.schabi.newpipe.settings.VideoAudioSettingsFragment"
android:icon="?attr/ic_headset" android:icon="?attr/ic_headset"
android:title="@string/settings_category_video_audio_title"/> android:title="@string/settings_category_video_audio_title"
app:iconSpaceReserved="false" />
<PreferenceScreen <PreferenceScreen
app:iconSpaceReserved="false"
android:fragment="org.schabi.newpipe.settings.DownloadSettingsFragment" android:fragment="org.schabi.newpipe.settings.DownloadSettingsFragment"
android:icon="?attr/ic_file_download" android:icon="?attr/ic_file_download"
android:title="@string/settings_category_downloads_title"/> android:title="@string/settings_category_downloads_title"
app:iconSpaceReserved="false" />
<PreferenceScreen <PreferenceScreen
app:iconSpaceReserved="false"
android:fragment="org.schabi.newpipe.settings.AppearanceSettingsFragment" android:fragment="org.schabi.newpipe.settings.AppearanceSettingsFragment"
android:icon="?attr/ic_palette" android:icon="?attr/ic_palette"
android:title="@string/settings_category_appearance_title"/> android:title="@string/settings_category_appearance_title"
app:iconSpaceReserved="false" />
<PreferenceScreen <PreferenceScreen
app:iconSpaceReserved="false"
android:fragment="org.schabi.newpipe.settings.HistorySettingsFragment" android:fragment="org.schabi.newpipe.settings.HistorySettingsFragment"
android:icon="?attr/ic_history" android:icon="?attr/ic_history"
android:title="@string/settings_category_history_title"/> android:title="@string/settings_category_history_title"
app:iconSpaceReserved="false" />
<PreferenceScreen <PreferenceScreen
app:iconSpaceReserved="false"
android:fragment="org.schabi.newpipe.settings.ContentSettingsFragment" android:fragment="org.schabi.newpipe.settings.ContentSettingsFragment"
android:icon="?attr/ic_language" android:icon="?attr/ic_language"
android:title="@string/content"/> android:title="@string/content"
app:iconSpaceReserved="false" />
<PreferenceScreen <PreferenceScreen
app:iconSpaceReserved="false"
android:fragment="org.schabi.newpipe.settings.NotificationSettingsFragment" android:fragment="org.schabi.newpipe.settings.NotificationSettingsFragment"
android:icon="?attr/ic_play_arrow" android:icon="?attr/ic_play_arrow"
android:title="@string/settings_category_notification_title"/> android:title="@string/settings_category_notification_title"
app:iconSpaceReserved="false" />
<PreferenceScreen <PreferenceScreen
app:iconSpaceReserved="false"
android:fragment="org.schabi.newpipe.settings.UpdateSettingsFragment" android:fragment="org.schabi.newpipe.settings.UpdateSettingsFragment"
android:icon="?attr/ic_settings_update" android:icon="?attr/ic_settings_update"
android:key="update_pref_screen_key"
android:title="@string/settings_category_updates_title" android:title="@string/settings_category_updates_title"
android:key="update_pref_screen_key"/> app:iconSpaceReserved="false" />
<PreferenceScreen <PreferenceScreen
app:iconSpaceReserved="false"
android:fragment="org.schabi.newpipe.settings.DebugSettingsFragment" android:fragment="org.schabi.newpipe.settings.DebugSettingsFragment"
android:icon="?attr/ic_bug_report" android:icon="?attr/ic_bug_report"
android:key="@string/debug_pref_screen_key"
android:title="@string/settings_category_debug_title" android:title="@string/settings_category_debug_title"
android:key="@string/debug_pref_screen_key"/> app:iconSpaceReserved="false" />
</PreferenceScreen> </PreferenceScreen>

View file

@ -1,7 +1,5 @@
package org.schabi.newpipe; package org.schabi.newpipe;
import android.annotation.TargetApi;
import android.app.Application;
import android.app.NotificationChannel; import android.app.NotificationChannel;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.content.Context; import android.content.Context;
@ -10,6 +8,7 @@ import android.os.Build;
import android.util.Log; import android.util.Log;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.multidex.MultiDexApplication;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
import com.nostra13.universalimageloader.cache.memory.impl.LRULimitedMemoryCache; import com.nostra13.universalimageloader.cache.memory.impl.LRULimitedMemoryCache;
@ -33,6 +32,7 @@ import org.schabi.newpipe.util.StateSaver;
import java.io.IOException; import java.io.IOException;
import java.io.InterruptedIOException; import java.io.InterruptedIOException;
import java.net.SocketException; import java.net.SocketException;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -61,7 +61,7 @@ import io.reactivex.plugins.RxJavaPlugins;
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>. * along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
*/ */
public class App extends Application { public class App extends MultiDexApplication {
protected static final String TAG = App.class.toString(); protected static final String TAG = App.class.toString();
private static App app; private static App app;
@ -90,7 +90,7 @@ public class App extends Application {
Localization.init(getApplicationContext()); Localization.init(getApplicationContext());
StateSaver.init(this); StateSaver.init(this);
initNotificationChannel(); initNotificationChannels();
ServiceHelper.initServices(this); ServiceHelper.initServices(this);
@ -219,49 +219,31 @@ public class App extends Application {
} }
} }
public void initNotificationChannel() { private void initNotificationChannels() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
return; return;
} }
final String id = getString(R.string.notification_channel_id); String id = getString(R.string.notification_channel_id);
final CharSequence name = getString(R.string.notification_channel_name); String name = getString(R.string.notification_channel_name);
final String description = getString(R.string.notification_channel_description); String description = getString(R.string.notification_channel_description);
// Keep this below DEFAULT to avoid making noise on every notification update // Keep this below DEFAULT to avoid making noise on every notification update
final int importance = NotificationManager.IMPORTANCE_LOW; final int importance = NotificationManager.IMPORTANCE_LOW;
final NotificationChannel mChannel = new NotificationChannel(id, name, importance); final NotificationChannel mainChannel = new NotificationChannel(id, name, importance);
mChannel.setDescription(description); mainChannel.setDescription(description);
final NotificationManager mNotificationManager = id = getString(R.string.app_update_notification_channel_id);
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); name = getString(R.string.app_update_notification_channel_name);
mNotificationManager.createNotificationChannel(mChannel); description = getString(R.string.app_update_notification_channel_description);
setUpUpdateNotificationChannel(importance); final NotificationChannel appUpdateChannel = new NotificationChannel(id, name, importance);
} appUpdateChannel.setDescription(description);
/** final NotificationManager notificationManager = getSystemService(NotificationManager.class);
* Set up notification channel for app update. notificationManager.createNotificationChannels(Arrays.asList(mainChannel,
* appUpdateChannel));
* @param importance
*/
@TargetApi(Build.VERSION_CODES.O)
private void setUpUpdateNotificationChannel(final int importance) {
final String appUpdateId
= getString(R.string.app_update_notification_channel_id);
final CharSequence appUpdateName
= getString(R.string.app_update_notification_channel_name);
final String appUpdateDescription
= getString(R.string.app_update_notification_channel_description);
final NotificationChannel appUpdateChannel
= new NotificationChannel(appUpdateId, appUpdateName, importance);
appUpdateChannel.setDescription(appUpdateDescription);
final NotificationManager appUpdateNotificationManager
= (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
appUpdateNotificationManager.createNotificationChannel(appUpdateChannel);
} }
protected boolean isDisposedRxExceptionsReported() { protected boolean isDisposedRxExceptionsReported() {

View file

@ -2,7 +2,6 @@ package org.schabi.newpipe;
import android.app.Application; import android.app.Application;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.pm.PackageInfo; import android.content.pm.PackageInfo;
@ -11,11 +10,12 @@ import android.content.pm.Signature;
import android.net.ConnectivityManager; import android.net.ConnectivityManager;
import android.net.Uri; import android.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
import androidx.preference.PreferenceManager;
import android.util.Log; import android.util.Log;
import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat; import androidx.core.app.NotificationManagerCompat;
import androidx.core.content.ContextCompat;
import androidx.preference.PreferenceManager;
import com.grack.nanojson.JsonObject; import com.grack.nanojson.JsonObject;
import com.grack.nanojson.JsonParser; import com.grack.nanojson.JsonParser;
@ -213,8 +213,8 @@ public class CheckForNewAppVersionTask extends AsyncTask<Void, Void, String> {
} }
private boolean isConnected() { private boolean isConnected() {
final ConnectivityManager cm = final ConnectivityManager cm = ContextCompat.getSystemService(APP,
(ConnectivityManager) APP.getSystemService(Context.CONNECTIVITY_SERVICE); ConnectivityManager.class);
return cm.getActiveNetworkInfo() != null return cm.getActiveNetworkInfo() != null
&& cm.getActiveNetworkInfo().isConnected(); && cm.getActiveNetworkInfo().isConnected();
} }

View file

@ -8,16 +8,18 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.Toolbar;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter; import androidx.lifecycle.Lifecycle;
import androidx.fragment.app.FragmentStatePagerAdapter; import androidx.recyclerview.widget.RecyclerView;
import androidx.viewpager.widget.PagerAdapter; import androidx.viewpager2.adapter.FragmentStateAdapter;
import androidx.viewpager.widget.ViewPager; import androidx.viewpager2.widget.ViewPager2;
import com.google.android.material.tabs.TabLayout; import com.google.android.material.tabs.TabLayout;
import com.google.android.material.tabs.TabLayoutMediator;
import org.schabi.newpipe.BuildConfig; import org.schabi.newpipe.BuildConfig;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
@ -64,20 +66,20 @@ public class AboutActivity extends AppCompatActivity {
"https://github.com/lisawray/groupie", StandardLicenses.MIT) "https://github.com/lisawray/groupie", StandardLicenses.MIT)
}; };
private static final int POS_ABOUT = 0;
private static final int POS_LICENSE = 1;
private static final int TOTAL_COUNT = 2;
/** /**
* The {@link PagerAdapter} that will provide * The {@link RecyclerView.Adapter} that will provide
* fragments for each of the sections. We use a * fragments for each of the sections. We use a
* {@link FragmentPagerAdapter} derivative, which will keep every * {@link FragmentStateAdapter} derivative, which will keep every
* loaded fragment in memory. If this becomes too memory intensive, it * loaded fragment in memory.
* may be best to switch to a
* {@link FragmentStatePagerAdapter}.
*/ */
private SectionsPagerAdapter mSectionsPagerAdapter; private SectionsPagerAdapter mSectionsPagerAdapter;
/** /**
* The {@link ViewPager} that will host the section contents. * The {@link ViewPager2} that will host the section contents.
*/ */
private ViewPager mViewPager; private ViewPager2 mViewPager;
@Override @Override
protected void onCreate(final Bundle savedInstanceState) { protected void onCreate(final Bundle savedInstanceState) {
@ -93,14 +95,25 @@ public class AboutActivity extends AppCompatActivity {
getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayHomeAsUpEnabled(true);
// Create the adapter that will return a fragment for each of the three // Create the adapter that will return a fragment for each of the three
// primary sections of the activity. // primary sections of the activity.
mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager()); mSectionsPagerAdapter =
new SectionsPagerAdapter(getSupportFragmentManager(), getLifecycle());
// Set up the ViewPager with the sections adapter. // Set up the ViewPager with the sections adapter.
mViewPager = findViewById(R.id.container); mViewPager = findViewById(R.id.container);
mViewPager.setAdapter(mSectionsPagerAdapter); mViewPager.setAdapter(mSectionsPagerAdapter);
final TabLayout tabLayout = findViewById(R.id.tabs); final TabLayout tabLayout = findViewById(R.id.tabs);
tabLayout.setupWithViewPager(mViewPager); new TabLayoutMediator(tabLayout, mViewPager, (tab, position) -> {
switch (position) {
default:
case POS_ABOUT:
tab.setText(R.string.tab_about);
break;
case POS_LICENSE:
tab.setText(R.string.tab_licenses);
break;
}
}).attach();
} }
@Override @Override
@ -162,40 +175,30 @@ public class AboutActivity extends AppCompatActivity {
} }
/** /**
* A {@link FragmentPagerAdapter} that returns a fragment corresponding to * A {@link FragmentStateAdapter} that returns a fragment corresponding to
* one of the sections/tabs/pages. * one of the sections/tabs/pages.
*/ */
public class SectionsPagerAdapter extends FragmentPagerAdapter { public static class SectionsPagerAdapter extends FragmentStateAdapter {
public SectionsPagerAdapter(final FragmentManager fm) { public SectionsPagerAdapter(final FragmentManager fm, final Lifecycle lifecycle) {
super(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT); super(fm, lifecycle);
} }
@NonNull
@Override @Override
public Fragment getItem(final int position) { public Fragment createFragment(final int position) {
switch (position) { switch (position) {
case 0: default:
case POS_ABOUT:
return AboutFragment.newInstance(); return AboutFragment.newInstance();
case 1: case POS_LICENSE:
return LicenseFragment.newInstance(SOFTWARE_COMPONENTS); return LicenseFragment.newInstance(SOFTWARE_COMPONENTS);
} }
return null;
} }
@Override @Override
public int getCount() { public int getItemCount() {
// Show 2 total pages. // Show 2 total pages.
return 2; return TOTAL_COUNT;
}
@Override
public CharSequence getPageTitle(final int position) {
switch (position) {
case 0:
return getString(R.string.tab_about);
case 1:
return getString(R.string.tab_licenses);
}
return null;
} }
} }
} }

View file

@ -33,4 +33,7 @@ public abstract class PlaylistDAO implements BasicDAO<PlaylistEntity> {
@Query("DELETE FROM " + PLAYLIST_TABLE + " WHERE " + PLAYLIST_ID + " = :playlistId") @Query("DELETE FROM " + PLAYLIST_TABLE + " WHERE " + PLAYLIST_ID + " = :playlistId")
public abstract int deletePlaylist(long playlistId); public abstract int deletePlaylist(long playlistId);
@Query("SELECT COUNT(*) FROM " + PLAYLIST_TABLE)
public abstract Flowable<Long> getCount();
} }

View file

@ -80,6 +80,7 @@ import org.schabi.newpipe.fragments.EmptyFragment;
import org.schabi.newpipe.fragments.list.comments.CommentsFragment; import org.schabi.newpipe.fragments.list.comments.CommentsFragment;
import org.schabi.newpipe.fragments.list.videos.RelatedVideosFragment; import org.schabi.newpipe.fragments.list.videos.RelatedVideosFragment;
import org.schabi.newpipe.local.dialog.PlaylistAppendDialog; import org.schabi.newpipe.local.dialog.PlaylistAppendDialog;
import org.schabi.newpipe.local.dialog.PlaylistCreationDialog;
import org.schabi.newpipe.local.history.HistoryRecordManager; import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.player.BasePlayer; import org.schabi.newpipe.player.BasePlayer;
import org.schabi.newpipe.player.MainPlayer; import org.schabi.newpipe.player.MainPlayer;
@ -482,8 +483,14 @@ public class VideoDetailFragment
break; break;
case R.id.detail_controls_playlist_append: case R.id.detail_controls_playlist_append:
if (getFM() != null && currentInfo != null) { if (getFM() != null && currentInfo != null) {
PlaylistAppendDialog.fromStreamInfo(currentInfo)
.show(getFM(), TAG); final PlaylistAppendDialog d = PlaylistAppendDialog.fromStreamInfo(currentInfo);
disposables.add(
PlaylistAppendDialog.onPlaylistFound(getContext(),
() -> d.show(getFM(), TAG),
() -> PlaylistCreationDialog.newInstance(d).show(getFM(), TAG)
)
);
} }
break; break;
case R.id.detail_controls_download: case R.id.detail_controls_download:
@ -1563,7 +1570,8 @@ public class VideoDetailFragment
} }
private void hideAgeRestrictedContent() { private void hideAgeRestrictedContent() {
showError(getString(R.string.restricted_video), false); showError(getString(R.string.restricted_video,
getString(R.string.show_age_restricted_content_title)), false);
if (relatedStreamsLayout != null) { // tablet if (relatedStreamsLayout != null) { // tablet
relatedStreamsLayout.setVisibility(View.INVISIBLE); relatedStreamsLayout.setVisibility(View.INVISIBLE);
@ -2074,8 +2082,7 @@ public class VideoDetailFragment
if (isClearingQueueConfirmationRequired(activity) if (isClearingQueueConfirmationRequired(activity)
&& playerIsNotStopped() && playerIsNotStopped()
&& activeQueue != null && activeQueue != null
&& !activeQueue.equals(playQueue) && !activeQueue.equals(playQueue)) {
&& activeQueue.getStreams().size() > 1) {
showClearingQueueConfirmation(onAllow); showClearingQueueConfirmation(onAllow);
} else { } else {
onAllow.run(); onAllow.run();

View file

@ -6,7 +6,6 @@ import android.content.SharedPreferences;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.content.res.Resources; import android.content.res.Resources;
import android.os.Bundle; import android.os.Bundle;
import androidx.preference.PreferenceManager;
import android.util.Log; import android.util.Log;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
@ -15,6 +14,7 @@ import android.view.View;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
@ -29,6 +29,7 @@ import org.schabi.newpipe.fragments.BaseStateFragment;
import org.schabi.newpipe.fragments.OnScrollBelowItemsListener; import org.schabi.newpipe.fragments.OnScrollBelowItemsListener;
import org.schabi.newpipe.info_list.InfoItemDialog; import org.schabi.newpipe.info_list.InfoItemDialog;
import org.schabi.newpipe.info_list.InfoListAdapter; import org.schabi.newpipe.info_list.InfoListAdapter;
import org.schabi.newpipe.player.helper.PlayerHolder;
import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.OnClickGesture; import org.schabi.newpipe.util.OnClickGesture;
@ -36,6 +37,8 @@ import org.schabi.newpipe.util.StateSaver;
import org.schabi.newpipe.util.StreamDialogEntry; import org.schabi.newpipe.util.StreamDialogEntry;
import org.schabi.newpipe.views.SuperScrollLayoutManager; import org.schabi.newpipe.views.SuperScrollLayoutManager;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Queue; import java.util.Queue;
@ -336,21 +339,26 @@ public abstract class BaseListFragment<I, N> extends BaseStateFragment<I>
return; return;
} }
final ArrayList<StreamDialogEntry> entries = new ArrayList<>();
if (PlayerHolder.getType() != null) {
entries.add(StreamDialogEntry.enqueue);
}
if (item.getStreamType() == StreamType.AUDIO_STREAM) { if (item.getStreamType() == StreamType.AUDIO_STREAM) {
StreamDialogEntry.setEnabledEntries( entries.addAll(Arrays.asList(
StreamDialogEntry.enqueue_on_background,
StreamDialogEntry.start_here_on_background, StreamDialogEntry.start_here_on_background,
StreamDialogEntry.append_playlist, StreamDialogEntry.append_playlist,
StreamDialogEntry.share); StreamDialogEntry.share
} else { ));
StreamDialogEntry.setEnabledEntries( } else {
StreamDialogEntry.enqueue_on_background, entries.addAll(Arrays.asList(
StreamDialogEntry.enqueue_on_popup,
StreamDialogEntry.start_here_on_background, StreamDialogEntry.start_here_on_background,
StreamDialogEntry.start_here_on_popup, StreamDialogEntry.start_here_on_popup,
StreamDialogEntry.append_playlist, StreamDialogEntry.append_playlist,
StreamDialogEntry.share); StreamDialogEntry.share
));
} }
StreamDialogEntry.setEnabledEntries(entries);
new InfoItemDialog(activity, item, StreamDialogEntry.getCommands(context), new InfoItemDialog(activity, item, StreamDialogEntry.getCommands(context),
(dialog, which) -> StreamDialogEntry.clickOn(which, this, item)).show(); (dialog, which) -> StreamDialogEntry.clickOn(which, this, item)).show();

View file

@ -33,6 +33,7 @@ import org.schabi.newpipe.extractor.stream.StreamType;
import org.schabi.newpipe.fragments.list.BaseListInfoFragment; import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
import org.schabi.newpipe.info_list.InfoItemDialog; import org.schabi.newpipe.info_list.InfoItemDialog;
import org.schabi.newpipe.local.playlist.RemotePlaylistManager; import org.schabi.newpipe.local.playlist.RemotePlaylistManager;
import org.schabi.newpipe.player.helper.PlayerHolder;
import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.player.playqueue.PlaylistPlayQueue; import org.schabi.newpipe.player.playqueue.PlaylistPlayQueue;
import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.report.ErrorActivity;
@ -46,6 +47,7 @@ import org.schabi.newpipe.util.StreamDialogEntry;
import org.schabi.newpipe.util.ThemeHelper; import org.schabi.newpipe.util.ThemeHelper;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
@ -151,25 +153,26 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
return; return;
} }
final ArrayList<StreamDialogEntry> entries = new ArrayList<>();
if (PlayerHolder.getType() != null) {
entries.add(StreamDialogEntry.enqueue);
}
if (item.getStreamType() == StreamType.AUDIO_STREAM) { if (item.getStreamType() == StreamType.AUDIO_STREAM) {
StreamDialogEntry.setEnabledEntries( entries.addAll(Arrays.asList(
StreamDialogEntry.enqueue_on_background,
StreamDialogEntry.start_here_on_background, StreamDialogEntry.start_here_on_background,
StreamDialogEntry.append_playlist, StreamDialogEntry.append_playlist,
StreamDialogEntry.share); StreamDialogEntry.share
} else { ));
StreamDialogEntry.setEnabledEntries( } else {
StreamDialogEntry.enqueue_on_background, entries.addAll(Arrays.asList(
StreamDialogEntry.enqueue_on_popup,
StreamDialogEntry.start_here_on_background, StreamDialogEntry.start_here_on_background,
StreamDialogEntry.start_here_on_popup, StreamDialogEntry.start_here_on_popup,
StreamDialogEntry.append_playlist, StreamDialogEntry.append_playlist,
StreamDialogEntry.share); StreamDialogEntry.share
));
StreamDialogEntry.start_here_on_popup.setCustomAction((fragment, infoItem) ->
NavigationHelper.playOnPopupPlayer(context,
getPlayQueueStartingAt(infoItem), true));
} }
StreamDialogEntry.setEnabledEntries(entries);
StreamDialogEntry.start_here_on_background.setCustomAction((fragment, infoItem) -> StreamDialogEntry.start_here_on_background.setCustomAction((fragment, infoItem) ->
NavigationHelper.playOnBackgroundPlayer(context, NavigationHelper.playOnBackgroundPlayer(context,

View file

@ -5,8 +5,6 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.Bundle; import android.os.Bundle;
import androidx.core.text.HtmlCompat;
import androidx.preference.PreferenceManager;
import android.text.Editable; import android.text.Editable;
import android.text.Html; import android.text.Html;
import android.text.TextUtils; import android.text.TextUtils;
@ -30,6 +28,9 @@ import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.TooltipCompat; import androidx.appcompat.widget.TooltipCompat;
import androidx.core.content.ContextCompat;
import androidx.core.text.HtmlCompat;
import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
@ -49,9 +50,9 @@ 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.DeviceUtils;
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.DeviceUtils;
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 org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.NavigationHelper;
@ -639,8 +640,8 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
} }
if (searchEditText.requestFocus()) { if (searchEditText.requestFocus()) {
final InputMethodManager imm = (InputMethodManager) activity.getSystemService( final InputMethodManager imm = ContextCompat.getSystemService(activity,
Context.INPUT_METHOD_SERVICE); InputMethodManager.class);
imm.showSoftInput(searchEditText, InputMethodManager.SHOW_FORCED); imm.showSoftInput(searchEditText, InputMethodManager.SHOW_FORCED);
} }
} }
@ -653,8 +654,8 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
return; return;
} }
final InputMethodManager imm = (InputMethodManager) activity final InputMethodManager imm = ContextCompat.getSystemService(activity,
.getSystemService(Context.INPUT_METHOD_SERVICE); InputMethodManager.class);
imm.hideSoftInputFromWindow(searchEditText.getWindowToken(), imm.hideSoftInputFromWindow(searchEditText.getWindowToken(),
InputMethodManager.RESULT_UNCHANGED_SHOWN); InputMethodManager.RESULT_UNCHANGED_SHOWN);

View file

@ -1,5 +1,6 @@
package org.schabi.newpipe.local.dialog; package org.schabi.newpipe.local.dialog;
import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
@ -29,6 +30,7 @@ import java.util.List;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable; import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
public final class PlaylistAppendDialog extends PlaylistDialog { public final class PlaylistAppendDialog extends PlaylistDialog {
private static final String TAG = PlaylistAppendDialog.class.getCanonicalName(); private static final String TAG = PlaylistAppendDialog.class.getCanonicalName();
@ -38,6 +40,23 @@ public final class PlaylistAppendDialog extends PlaylistDialog {
private CompositeDisposable playlistDisposables = new CompositeDisposable(); private CompositeDisposable playlistDisposables = new CompositeDisposable();
public static Disposable onPlaylistFound(
final Context context, final Runnable onSuccess, final Runnable onFailed
) {
final LocalPlaylistManager playlistManager =
new LocalPlaylistManager(NewPipeDatabase.getInstance(context));
return playlistManager.hasPlaylists()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(hasPlaylists -> {
if (hasPlaylists) {
onSuccess.run();
} else {
onFailed.run();
}
});
}
public static PlaylistAppendDialog fromStreamInfo(final StreamInfo info) { public static PlaylistAppendDialog fromStreamInfo(final StreamInfo info) {
final PlaylistAppendDialog dialog = new PlaylistAppendDialog(); final PlaylistAppendDialog dialog = new PlaylistAppendDialog();
dialog.setInfo(Collections.singletonList(new StreamEntity(info))); dialog.setInfo(Collections.singletonList(new StreamEntity(info)));
@ -136,11 +155,6 @@ public final class PlaylistAppendDialog extends PlaylistDialog {
} }
private void onPlaylistsReceived(@NonNull final List<PlaylistMetadataEntry> playlists) { private void onPlaylistsReceived(@NonNull final List<PlaylistMetadataEntry> playlists) {
if (playlists.isEmpty()) {
openCreatePlaylistDialog();
return;
}
if (playlistAdapter != null && playlistRecyclerView != null) { if (playlistAdapter != null && playlistRecyclerView != null) {
playlistAdapter.clearStreamItemList(); playlistAdapter.clearStreamItemList();
playlistAdapter.addItems(playlists); playlistAdapter.addItems(playlists);

View file

@ -26,6 +26,12 @@ public final class PlaylistCreationDialog extends PlaylistDialog {
return dialog; return dialog;
} }
public static PlaylistCreationDialog newInstance(final PlaylistAppendDialog appendDialog) {
final PlaylistCreationDialog dialog = new PlaylistCreationDialog();
dialog.setInfo(appendDialog.getStreams());
return dialog;
}
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
// Dialog // Dialog
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/

View file

@ -29,6 +29,8 @@ import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.os.bundleOf
import androidx.core.view.isVisible
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
@ -253,11 +255,9 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
oldestSubscriptionUpdate = loadedState.oldestUpdate oldestSubscriptionUpdate = loadedState.oldestUpdate
refresh_subtitle_text.isVisible = loadedState.notLoadedCount > 0
if (loadedState.notLoadedCount > 0) { if (loadedState.notLoadedCount > 0) {
refresh_subtitle_text.visibility = View.VISIBLE
refresh_subtitle_text.text = getString(R.string.feed_subscription_not_loaded_count, loadedState.notLoadedCount) refresh_subtitle_text.text = getString(R.string.feed_subscription_not_loaded_count, loadedState.notLoadedCount)
} else {
refresh_subtitle_text.visibility = View.GONE
} }
if (loadedState.itemsErrors.isNotEmpty()) { if (loadedState.itemsErrors.isNotEmpty()) {
@ -330,12 +330,7 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
@JvmStatic @JvmStatic
fun newInstance(groupId: Long = FeedGroupEntity.GROUP_ALL_ID, groupName: String? = null): FeedFragment { fun newInstance(groupId: Long = FeedGroupEntity.GROUP_ALL_ID, groupName: String? = null): FeedFragment {
val feedFragment = FeedFragment() val feedFragment = FeedFragment()
feedFragment.arguments = bundleOf(KEY_GROUP_ID to groupId, KEY_GROUP_NAME to groupName)
feedFragment.arguments = Bundle().apply {
putLong(KEY_GROUP_ID, groupId)
putString(KEY_GROUP_NAME, groupName)
}
return feedFragment return feedFragment
} }
} }

View file

@ -20,9 +20,9 @@ package org.schabi.newpipe.local.history;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import androidx.preference.PreferenceManager;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.preference.PreferenceManager;
import org.schabi.newpipe.NewPipeDatabase; import org.schabi.newpipe.NewPipeDatabase;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
@ -101,9 +101,11 @@ public class HistoryRecordManager {
})).subscribeOn(Schedulers.io()); })).subscribeOn(Schedulers.io());
} }
public Single<Integer> deleteStreamHistory(final long streamId) { public Completable deleteStreamHistoryAndState(final long streamId) {
return Single.fromCallable(() -> streamHistoryTable.deleteStreamHistory(streamId)) return Completable.fromAction(() -> {
.subscribeOn(Schedulers.io()); streamStateTable.deleteState(streamId);
streamHistoryTable.deleteStreamHistory(streamId);
}).subscribeOn(Schedulers.io());
} }
public Single<Integer> deleteWholeStreamHistory() { public Single<Integer> deleteWholeStreamHistory() {
@ -111,7 +113,7 @@ public class HistoryRecordManager {
.subscribeOn(Schedulers.io()); .subscribeOn(Schedulers.io());
} }
public Single<Integer> deleteCompelteStreamStateHistory() { public Single<Integer> deleteCompleteStreamStateHistory() {
return Single.fromCallable(streamStateTable::deleteAll) return Single.fromCallable(streamStateTable::deleteAll)
.subscribeOn(Schedulers.io()); .subscribeOn(Schedulers.io());
} }

View file

@ -29,6 +29,7 @@ import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.extractor.stream.StreamType; import org.schabi.newpipe.extractor.stream.StreamType;
import org.schabi.newpipe.info_list.InfoItemDialog; import org.schabi.newpipe.info_list.InfoItemDialog;
import org.schabi.newpipe.local.BaseLocalListFragment; import org.schabi.newpipe.local.BaseLocalListFragment;
import org.schabi.newpipe.player.helper.PlayerHolder;
import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.player.playqueue.SinglePlayQueue; import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.report.ErrorActivity;
@ -40,6 +41,7 @@ import org.schabi.newpipe.util.StreamDialogEntry;
import org.schabi.newpipe.util.ThemeHelper; import org.schabi.newpipe.util.ThemeHelper;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -387,27 +389,28 @@ public class StatisticsPlaylistFragment
} }
final StreamInfoItem infoItem = item.toStreamInfoItem(); final StreamInfoItem infoItem = item.toStreamInfoItem();
final ArrayList<StreamDialogEntry> entries = new ArrayList<>();
if (PlayerHolder.getType() != null) {
entries.add(StreamDialogEntry.enqueue);
}
if (infoItem.getStreamType() == StreamType.AUDIO_STREAM) { if (infoItem.getStreamType() == StreamType.AUDIO_STREAM) {
StreamDialogEntry.setEnabledEntries( entries.addAll(Arrays.asList(
StreamDialogEntry.enqueue_on_background,
StreamDialogEntry.start_here_on_background, StreamDialogEntry.start_here_on_background,
StreamDialogEntry.delete, StreamDialogEntry.delete,
StreamDialogEntry.append_playlist, StreamDialogEntry.append_playlist,
StreamDialogEntry.share); StreamDialogEntry.share
} else { ));
StreamDialogEntry.setEnabledEntries( } else {
StreamDialogEntry.enqueue_on_background, entries.addAll(Arrays.asList(
StreamDialogEntry.enqueue_on_popup,
StreamDialogEntry.start_here_on_background, StreamDialogEntry.start_here_on_background,
StreamDialogEntry.start_here_on_popup, StreamDialogEntry.start_here_on_popup,
StreamDialogEntry.delete, StreamDialogEntry.delete,
StreamDialogEntry.append_playlist, StreamDialogEntry.append_playlist,
StreamDialogEntry.share); StreamDialogEntry.share
));
StreamDialogEntry.start_here_on_popup.setCustomAction((fragment, infoItemDuplicate) ->
NavigationHelper
.playOnPopupPlayer(context, getPlayQueueStartingAt(item), true));
} }
StreamDialogEntry.setEnabledEntries(entries);
StreamDialogEntry.start_here_on_background.setCustomAction((fragment, infoItemDuplicate) -> StreamDialogEntry.start_here_on_background.setCustomAction((fragment, infoItemDuplicate) ->
NavigationHelper NavigationHelper
@ -420,14 +423,14 @@ public class StatisticsPlaylistFragment
} }
private void deleteEntry(final int index) { private void deleteEntry(final int index) {
final LocalItem infoItem = itemListAdapter.getItemsList() final LocalItem infoItem = itemListAdapter.getItemsList().get(index);
.get(index);
if (infoItem instanceof StreamStatisticsEntry) { if (infoItem instanceof StreamStatisticsEntry) {
final StreamStatisticsEntry entry = (StreamStatisticsEntry) infoItem; final StreamStatisticsEntry entry = (StreamStatisticsEntry) infoItem;
final Disposable onDelete = recordManager.deleteStreamHistory(entry.getStreamId()) final Disposable onDelete = recordManager
.deleteStreamHistoryAndState(entry.getStreamId())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe( .subscribe(
howManyDeleted -> { () -> {
if (getView() != null) { if (getView() != null) {
Snackbar.make(getView(), R.string.one_item_deleted, Snackbar.make(getView(), R.string.one_item_deleted,
Snackbar.LENGTH_SHORT).show(); Snackbar.LENGTH_SHORT).show();

View file

@ -104,7 +104,6 @@ public class LocalPlaylistStreamItemHolder extends LocalItemHolder {
return true; return true;
}); });
itemThumbnailView.setOnTouchListener(getOnTouchListener(item));
itemHandleView.setOnTouchListener(getOnTouchListener(item)); itemHandleView.setOnTouchListener(getOnTouchListener(item));
} }

View file

@ -36,6 +36,7 @@ import org.schabi.newpipe.extractor.stream.StreamType;
import org.schabi.newpipe.info_list.InfoItemDialog; import org.schabi.newpipe.info_list.InfoItemDialog;
import org.schabi.newpipe.local.BaseLocalListFragment; import org.schabi.newpipe.local.BaseLocalListFragment;
import org.schabi.newpipe.local.history.HistoryRecordManager; import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.player.helper.PlayerHolder;
import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.player.playqueue.SinglePlayQueue; import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.report.UserAction;
@ -45,6 +46,7 @@ import org.schabi.newpipe.util.OnClickGesture;
import org.schabi.newpipe.util.StreamDialogEntry; import org.schabi.newpipe.util.StreamDialogEntry;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
@ -756,29 +758,30 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
} }
final StreamInfoItem infoItem = item.toStreamInfoItem(); final StreamInfoItem infoItem = item.toStreamInfoItem();
final ArrayList<StreamDialogEntry> entries = new ArrayList<>();
if (PlayerHolder.getType() != null) {
entries.add(StreamDialogEntry.enqueue);
}
if (infoItem.getStreamType() == StreamType.AUDIO_STREAM) { if (infoItem.getStreamType() == StreamType.AUDIO_STREAM) {
StreamDialogEntry.setEnabledEntries( entries.addAll(Arrays.asList(
StreamDialogEntry.enqueue_on_background,
StreamDialogEntry.start_here_on_background, StreamDialogEntry.start_here_on_background,
StreamDialogEntry.set_as_playlist_thumbnail, StreamDialogEntry.set_as_playlist_thumbnail,
StreamDialogEntry.delete, StreamDialogEntry.delete,
StreamDialogEntry.append_playlist, StreamDialogEntry.append_playlist,
StreamDialogEntry.share); StreamDialogEntry.share
} else { ));
StreamDialogEntry.setEnabledEntries( } else {
StreamDialogEntry.enqueue_on_background, entries.addAll(Arrays.asList(
StreamDialogEntry.enqueue_on_popup,
StreamDialogEntry.start_here_on_background, StreamDialogEntry.start_here_on_background,
StreamDialogEntry.start_here_on_popup, StreamDialogEntry.start_here_on_popup,
StreamDialogEntry.set_as_playlist_thumbnail, StreamDialogEntry.set_as_playlist_thumbnail,
StreamDialogEntry.delete, StreamDialogEntry.delete,
StreamDialogEntry.append_playlist, StreamDialogEntry.append_playlist,
StreamDialogEntry.share); StreamDialogEntry.share
));
StreamDialogEntry.start_here_on_popup.setCustomAction(
(fragment, infoItemDuplicate) -> NavigationHelper.
playOnPopupPlayer(context, getPlayQueueStartingAt(item), true));
} }
StreamDialogEntry.setEnabledEntries(entries);
StreamDialogEntry.start_here_on_background.setCustomAction((fragment, infoItemDuplicate) -> StreamDialogEntry.start_here_on_background.setCustomAction((fragment, infoItemDuplicate) ->
NavigationHelper.playOnBackgroundPlayer(context, NavigationHelper.playOnBackgroundPlayer(context,

View file

@ -126,4 +126,10 @@ public class LocalPlaylistManager {
}).subscribeOn(Schedulers.io()); }).subscribeOn(Schedulers.io());
} }
public Maybe<Boolean> hasPlaylists() {
return playlistTable.getCount()
.firstElement()
.map(count -> count > 0)
.subscribeOn(Schedulers.io());
}
} }

View file

@ -1,17 +1,19 @@
package org.schabi.newpipe.local.subscription.dialog package org.schabi.newpipe.local.subscription.dialog
import android.app.Dialog import android.app.Dialog
import android.content.Context
import android.os.Bundle import android.os.Bundle
import android.os.Parcelable import android.os.Parcelable
import android.text.Editable
import android.text.TextUtils import android.text.TextUtils
import android.text.TextWatcher
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import android.widget.Toast import android.widget.Toast
import androidx.core.content.getSystemService
import androidx.core.os.bundleOf
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.core.widget.doOnTextChanged
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
@ -191,16 +193,11 @@ class FeedGroupDialog : DialogFragment(), BackPressable {
} }
group_name_input_container.error = null group_name_input_container.error = null
group_name_input.addTextChangedListener(object : TextWatcher { group_name_input.doOnTextChanged { text, _, _, _ ->
override fun afterTextChanged(s: Editable?) {} if (group_name_input_container.isErrorEnabled && !text.isNullOrBlank()) {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} group_name_input_container.error = null
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
if (group_name_input_container.isErrorEnabled && !s.isNullOrBlank()) {
group_name_input_container.error = null
}
} }
}) }
confirm_button.setOnClickListener { handlePositiveButton() } confirm_button.setOnClickListener { handlePositiveButton() }
@ -242,15 +239,11 @@ class FeedGroupDialog : DialogFragment(), BackPressable {
} }
} }
toolbar_search_edit_text.addTextChangedListener(object : TextWatcher { toolbar_search_edit_text.doOnTextChanged { _, _, _, _ ->
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) = Unit val newQuery: String = toolbar_search_edit_text.text.toString()
override fun afterTextChanged(s: Editable) = Unit subscriptionsCurrentSearchQuery = newQuery
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { viewModel.filterSubscriptionsBy(newQuery)
val newQuery: String = toolbar_search_edit_text.text.toString() }
subscriptionsCurrentSearchQuery = newQuery
viewModel.filterSubscriptionsBy(newQuery)
}
})
subscriptionGroupAdapter.setOnItemClickListener(subscriptionPickerItemListener) subscriptionGroupAdapter.setOnItemClickListener(subscriptionPickerItemListener)
} }
@ -414,21 +407,14 @@ class FeedGroupDialog : DialogFragment(), BackPressable {
else -> android.R.string.ok else -> android.R.string.ok
}) })
delete_button.visibility = when { delete_button.isGone = currentScreen != InitialScreen || groupId == NO_GROUP_SELECTED
currentScreen != InitialScreen -> View.GONE
groupId == NO_GROUP_SELECTED -> View.GONE
else -> View.VISIBLE
}
hideKeyboard() hideKeyboard()
hideSearch() hideSearch()
} }
private fun View.onlyVisibleIn(vararg screens: ScreenState) { private fun View.onlyVisibleIn(vararg screens: ScreenState) {
visibility = when (currentScreen) { isVisible = currentScreen in screens
in screens -> View.VISIBLE
else -> View.GONE
}
} }
/*/////////////////////////////////////////////////////////////////////////// /*///////////////////////////////////////////////////////////////////////////
@ -459,7 +445,7 @@ class FeedGroupDialog : DialogFragment(), BackPressable {
} }
private val inputMethodManager by lazy { private val inputMethodManager by lazy {
requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager requireActivity().getSystemService<InputMethodManager>()!!
} }
private fun showKeyboardSearch() { private fun showKeyboardSearch() {
@ -501,11 +487,7 @@ class FeedGroupDialog : DialogFragment(), BackPressable {
fun newInstance(groupId: Long = NO_GROUP_SELECTED): FeedGroupDialog { fun newInstance(groupId: Long = NO_GROUP_SELECTED): FeedGroupDialog {
val dialog = FeedGroupDialog() val dialog = FeedGroupDialog()
dialog.arguments = bundleOf(KEY_GROUP_ID to groupId)
dialog.arguments = Bundle().apply {
putLong(KEY_GROUP_ID, groupId)
}
return dialog return dialog
} }
} }

View file

@ -1,9 +1,8 @@
package org.schabi.newpipe.local.subscription.item package org.schabi.newpipe.local.subscription.item
import android.view.View.GONE
import android.view.View.OnClickListener import android.view.View.OnClickListener
import android.view.View.VISIBLE
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.core.view.isVisible
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.header_menu_item import kotlinx.android.synthetic.main.header_with_menu_item.header_menu_item
@ -47,6 +46,6 @@ class HeaderWithMenuItem(
} }
private fun updateMenuItemVisibility(viewHolder: GroupieViewHolder) { private fun updateMenuItemVisibility(viewHolder: GroupieViewHolder) {
viewHolder.header_menu_item.visibility = if (showMenuItem) VISIBLE else GONE viewHolder.header_menu_item.isVisible = showMenuItem
} }
} }

View file

@ -1,6 +1,7 @@
package org.schabi.newpipe.local.subscription.item package org.schabi.newpipe.local.subscription.item
import android.view.View import android.view.View
import androidx.core.view.isVisible
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
@ -25,7 +26,7 @@ data class PickerSubscriptionItem(
viewHolder.thumbnail_view, ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS) viewHolder.thumbnail_view, ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS)
viewHolder.title_view.text = subscriptionEntity.name viewHolder.title_view.text = subscriptionEntity.name
viewHolder.selected_highlight.visibility = if (isSelected) View.VISIBLE else View.GONE viewHolder.selected_highlight.isVisible = isSelected
} }
override fun unbind(viewHolder: GroupieViewHolder) { override fun unbind(viewHolder: GroupieViewHolder) {

View file

@ -30,6 +30,8 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.WindowManager; import android.view.WindowManager;
import androidx.core.content.ContextCompat;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.util.ThemeHelper; import org.schabi.newpipe.util.ThemeHelper;
@ -91,7 +93,7 @@ public final class MainPlayer extends Service {
Log.d(TAG, "onCreate() called"); Log.d(TAG, "onCreate() called");
} }
assureCorrectAppLanguage(this); assureCorrectAppLanguage(this);
windowManager = (WindowManager) getSystemService(WINDOW_SERVICE); windowManager = ContextCompat.getSystemService(this, WindowManager.class);
ThemeHelper.setTheme(this); ThemeHelper.setTheme(this);
createView(); createView();

View file

@ -116,10 +116,11 @@ public final class NotificationUtil {
.setMediaSession(player.mediaSessionManager.getSessionToken()) .setMediaSession(player.mediaSessionManager.getSessionToken())
.setShowActionsInCompactView(compactSlots)) .setShowActionsInCompactView(compactSlots))
.setPriority(NotificationCompat.PRIORITY_HIGH) .setPriority(NotificationCompat.PRIORITY_HIGH)
.setSmallIcon(R.drawable.ic_newpipe_triangle_white)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setColor(ContextCompat.getColor(player.context, R.color.gray))
.setCategory(NotificationCompat.CATEGORY_TRANSPORT) .setCategory(NotificationCompat.CATEGORY_TRANSPORT)
.setShowWhen(false)
.setSmallIcon(R.drawable.ic_newpipe_triangle_white)
.setColor(ContextCompat.getColor(player.context, R.color.gray))
.setDeleteIntent(PendingIntent.getBroadcast(player.context, NOTIFICATION_ID, .setDeleteIntent(PendingIntent.getBroadcast(player.context, NOTIFICATION_ID,
new Intent(ACTION_CLOSE), FLAG_UPDATE_CURRENT)); new Intent(ACTION_CLOSE), FLAG_UPDATE_CURRENT));
@ -148,7 +149,10 @@ public final class NotificationUtil {
@SuppressLint("RestrictedApi") @SuppressLint("RestrictedApi")
boolean shouldUpdateBufferingSlot() { boolean shouldUpdateBufferingSlot() {
if (notificationBuilder.mActions.size() < 3) { if (notificationBuilder == null) {
// if there is no notification active, there is no point in updating it
return false;
} else if (notificationBuilder.mActions.size() < 3) {
// this should never happen, but let's make sure notification actions are populated // this should never happen, but let's make sure notification actions are populated
return true; return true;
} }

View file

@ -33,6 +33,7 @@ import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.fragments.OnScrollBelowItemsListener; import org.schabi.newpipe.fragments.OnScrollBelowItemsListener;
import org.schabi.newpipe.local.dialog.PlaylistAppendDialog; import org.schabi.newpipe.local.dialog.PlaylistAppendDialog;
import org.schabi.newpipe.local.dialog.PlaylistCreationDialog;
import org.schabi.newpipe.player.event.PlayerEventListener; import org.schabi.newpipe.player.event.PlayerEventListener;
import org.schabi.newpipe.player.helper.PlaybackParameterDialog; import org.schabi.newpipe.player.helper.PlaybackParameterDialog;
import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.PlayQueue;
@ -571,8 +572,13 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
} }
private void openPlaylistAppendDialog(final List<PlayQueueItem> playlist) { private void openPlaylistAppendDialog(final List<PlayQueueItem> playlist) {
PlaylistAppendDialog.fromPlayQueueItems(playlist) final PlaylistAppendDialog d = PlaylistAppendDialog.fromPlayQueueItems(playlist);
.show(getSupportFragmentManager(), getTag());
PlaylistAppendDialog.onPlaylistFound(getApplicationContext(),
() -> d.show(getSupportFragmentManager(), getTag()),
() -> PlaylistCreationDialog.newInstance(d)
.show(getSupportFragmentManager(), getTag()
));
} }
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////

View file

@ -32,8 +32,6 @@ import android.graphics.PixelFormat;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Handler; import android.os.Handler;
import android.view.DisplayCutout;
import androidx.preference.PreferenceManager;
import android.provider.Settings; import android.provider.Settings;
import android.util.DisplayMetrics; import android.util.DisplayMetrics;
import android.util.Log; import android.util.Log;
@ -60,8 +58,13 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.content.res.AppCompatResources; import androidx.appcompat.content.res.AppCompatResources;
import androidx.core.content.ContextCompat;
import androidx.core.view.DisplayCutoutCompat;
import androidx.core.view.ViewCompat;
import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.SimpleExoPlayer;
@ -72,6 +75,7 @@ import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
import com.google.android.exoplayer2.ui.SubtitleView; import com.google.android.exoplayer2.ui.SubtitleView;
import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.nostra13.universalimageloader.core.assist.FailReason; import com.nostra13.universalimageloader.core.assist.FailReason;
import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.StreamingService; import org.schabi.newpipe.extractor.StreamingService;
@ -92,9 +96,9 @@ import org.schabi.newpipe.player.playqueue.PlayQueueItemTouchCallback;
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;
import org.schabi.newpipe.player.resolver.VideoPlaybackResolver; import org.schabi.newpipe.player.resolver.VideoPlaybackResolver;
import org.schabi.newpipe.util.DeviceUtils;
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.DeviceUtils;
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;
@ -105,7 +109,6 @@ import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import static android.content.Context.WINDOW_SERVICE;
import static org.schabi.newpipe.player.MainPlayer.ACTION_CLOSE; import static org.schabi.newpipe.player.MainPlayer.ACTION_CLOSE;
import static org.schabi.newpipe.player.MainPlayer.ACTION_FAST_FORWARD; import static org.schabi.newpipe.player.MainPlayer.ACTION_FAST_FORWARD;
import static org.schabi.newpipe.player.MainPlayer.ACTION_FAST_REWIND; import static org.schabi.newpipe.player.MainPlayer.ACTION_FAST_REWIND;
@ -270,7 +273,7 @@ public class VideoPlayerImpl extends VideoPlayer
super("MainPlayer" + TAG, service); super("MainPlayer" + TAG, service);
this.service = service; this.service = service;
this.shouldUpdateOnProgress = true; this.shouldUpdateOnProgress = true;
this.windowManager = (WindowManager) service.getSystemService(WINDOW_SERVICE); this.windowManager = ContextCompat.getSystemService(service, WindowManager.class);
this.defaultPreferences = PreferenceManager.getDefaultSharedPreferences(service); this.defaultPreferences = PreferenceManager.getDefaultSharedPreferences(service);
this.resolver = new AudioPlaybackResolver(context, dataSource); this.resolver = new AudioPlaybackResolver(context, dataSource);
} }
@ -498,16 +501,14 @@ public class VideoPlayerImpl extends VideoPlayer
settingsContentObserver); settingsContentObserver);
getRootView().addOnLayoutChangeListener(this); getRootView().addOnLayoutChangeListener(this);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { ViewCompat.setOnApplyWindowInsetsListener(queueLayout, (view, windowInsets) -> {
queueLayout.setOnApplyWindowInsetsListener((view, windowInsets) -> { final DisplayCutoutCompat cutout = windowInsets.getDisplayCutout();
final DisplayCutout cutout = windowInsets.getDisplayCutout(); if (cutout != null) {
if (cutout != null) { view.setPadding(cutout.getSafeInsetLeft(), cutout.getSafeInsetTop(),
view.setPadding(cutout.getSafeInsetLeft(), cutout.getSafeInsetTop(), cutout.getSafeInsetRight(), cutout.getSafeInsetBottom());
cutout.getSafeInsetRight(), cutout.getSafeInsetBottom()); }
} return windowInsets;
return windowInsets; });
});
}
// PlaybackControlRoot already consumed window insets but we should pass them to // PlaybackControlRoot already consumed window insets but we should pass them to
// player_overlays too. Without it they will be off-centered // player_overlays too. Without it they will be off-centered
@ -1765,13 +1766,10 @@ public class VideoPlayerImpl extends VideoPlayer
updateScreenSize(); updateScreenSize();
final boolean popupRememberSizeAndPos = PlayerHelper.isRememberingPopupDimensions(service);
final float defaultSize = service.getResources().getDimension(R.dimen.popup_default_width); final float defaultSize = service.getResources().getDimension(R.dimen.popup_default_width);
final SharedPreferences sharedPreferences = final SharedPreferences sharedPreferences =
PreferenceManager.getDefaultSharedPreferences(service); PreferenceManager.getDefaultSharedPreferences(service);
popupWidth = popupRememberSizeAndPos popupWidth = sharedPreferences.getFloat(POPUP_SAVED_WIDTH, defaultSize);
? sharedPreferences.getFloat(POPUP_SAVED_WIDTH, defaultSize)
: defaultSize;
popupHeight = getMinimumVideoHeight(popupWidth); popupHeight = getMinimumVideoHeight(popupWidth);
popupLayoutParams = new WindowManager.LayoutParams( popupLayoutParams = new WindowManager.LayoutParams(
@ -1785,10 +1783,8 @@ public class VideoPlayerImpl extends VideoPlayer
final int centerX = (int) (screenWidth / 2f - popupWidth / 2f); final int centerX = (int) (screenWidth / 2f - popupWidth / 2f);
final int centerY = (int) (screenHeight / 2f - popupHeight / 2f); final int centerY = (int) (screenHeight / 2f - popupHeight / 2f);
popupLayoutParams.x = popupRememberSizeAndPos popupLayoutParams.x = sharedPreferences.getInt(POPUP_SAVED_X, centerX);
? sharedPreferences.getInt(POPUP_SAVED_X, centerX) : centerX; popupLayoutParams.y = sharedPreferences.getInt(POPUP_SAVED_Y, centerY);
popupLayoutParams.y = popupRememberSizeAndPos
? sharedPreferences.getInt(POPUP_SAVED_Y, centerY) : centerY;
checkPopupPositionBounds(); checkPopupPositionBounds();
@ -2203,6 +2199,10 @@ public class VideoPlayerImpl extends VideoPlayer
return popupLayoutParams; return popupLayoutParams;
} }
public MainPlayer.PlayerType getPlayerType() {
return playerType;
}
public float getScreenWidth() { public float getScreenWidth() {
return screenWidth; return screenWidth;
} }

View file

@ -42,6 +42,14 @@ public class CustomBottomSheetBehavior extends BottomSheetBehavior<FrameLayout>
return false; return false;
} }
// The interception listens for the child view with the id "fragment_player_holder",
// so the following two-finger gesture will be triggered only for the player view on
// portrait and for the top controls (visible) on landscape.
setSkipCollapsed(event.getPointerCount() == 2);
if (event.getPointerCount() == 2) {
return super.onInterceptTouchEvent(parent, child, event);
}
// Don't need to do anything if bottomSheet isn't expanded // Don't need to do anything if bottomSheet isn't expanded
if (getState() == BottomSheetBehavior.STATE_EXPANDED if (getState() == BottomSheetBehavior.STATE_EXPANDED
&& event.getAction() == MotionEvent.ACTION_DOWN) { && event.getAction() == MotionEvent.ACTION_DOWN) {

View file

@ -12,6 +12,7 @@ import android.os.Build;
import android.util.Log; import android.util.Log;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.analytics.AnalyticsListener; import com.google.android.exoplayer2.analytics.AnalyticsListener;
@ -39,7 +40,7 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener, An
@NonNull final SimpleExoPlayer player) { @NonNull final SimpleExoPlayer player) {
this.player = player; this.player = player;
this.context = context; this.context = context;
this.audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); this.audioManager = ContextCompat.getSystemService(context, AudioManager.class);
player.addAnalyticsListener(this); player.addAnalyticsListener(this);
if (SHOULD_BUILD_FOCUS_REQUEST) { if (SHOULD_BUILD_FOCUS_REQUEST) {

View file

@ -5,8 +5,7 @@ import android.net.wifi.WifiManager;
import android.os.PowerManager; import android.os.PowerManager;
import android.util.Log; import android.util.Log;
import static android.content.Context.POWER_SERVICE; import androidx.core.content.ContextCompat;
import static android.content.Context.WIFI_SERVICE;
public class LockManager { public class LockManager {
private final String TAG = "LockManager@" + hashCode(); private final String TAG = "LockManager@" + hashCode();
@ -18,10 +17,9 @@ public class LockManager {
private WifiManager.WifiLock wifiLock; private WifiManager.WifiLock wifiLock;
public LockManager(final Context context) { public LockManager(final Context context) {
powerManager = ((PowerManager) context.getApplicationContext() powerManager = ContextCompat.getSystemService(context.getApplicationContext(),
.getSystemService(POWER_SERVICE)); PowerManager.class);
wifiManager = ((WifiManager) context.getApplicationContext() wifiManager = ContextCompat.getSystemService(context, WifiManager.class);
.getSystemService(WIFI_SERVICE));
} }
public void acquireWifiAndCpu() { public void acquireWifiAndCpu() {

View file

@ -8,6 +8,7 @@ import android.view.accessibility.CaptioningManager;
import androidx.annotation.IntDef; import androidx.annotation.IntDef;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
import com.google.android.exoplayer2.SeekParameters; import com.google.android.exoplayer2.SeekParameters;
@ -209,10 +210,6 @@ public final class PlayerHelper {
return isBrightnessGestureEnabled(context, true); return isBrightnessGestureEnabled(context, true);
} }
public static boolean isRememberingPopupDimensions(@NonNull final Context context) {
return isRememberingPopupDimensions(context, true);
}
public static boolean isAutoQueueEnabled(@NonNull final Context context) { public static boolean isAutoQueueEnabled(@NonNull final Context context) {
return isAutoQueueEnabled(context, false); return isAutoQueueEnabled(context, false);
} }
@ -316,8 +313,8 @@ public final class PlayerHelper {
@NonNull @NonNull
public static CaptionStyleCompat getCaptionStyle(@NonNull final Context context) { public static CaptionStyleCompat getCaptionStyle(@NonNull final Context context) {
final CaptioningManager captioningManager = (CaptioningManager) final CaptioningManager captioningManager = ContextCompat.getSystemService(context,
context.getSystemService(Context.CAPTIONING_SERVICE); CaptioningManager.class);
if (captioningManager == null || !captioningManager.isEnabled()) { if (captioningManager == null || !captioningManager.isEnabled()) {
return CaptionStyleCompat.DEFAULT; return CaptionStyleCompat.DEFAULT;
} }
@ -340,8 +337,8 @@ public final class PlayerHelper {
* @return caption scaling * @return caption scaling
*/ */
public static float getCaptionScale(@NonNull final Context context) { public static float getCaptionScale(@NonNull final Context context) {
final CaptioningManager captioningManager final CaptioningManager captioningManager = ContextCompat.getSystemService(context,
= (CaptioningManager) context.getSystemService(Context.CAPTIONING_SERVICE); CaptioningManager.class);
if (captioningManager == null || !captioningManager.isEnabled()) { if (captioningManager == null || !captioningManager.isEnabled()) {
return 1.0f; return 1.0f;
} }
@ -393,12 +390,6 @@ public final class PlayerHelper {
.getBoolean(context.getString(R.string.brightness_gesture_control_key), b); .getBoolean(context.getString(R.string.brightness_gesture_control_key), b);
} }
private static boolean isRememberingPopupDimensions(@NonNull final Context context,
final boolean b) {
return getPreferences(context)
.getBoolean(context.getString(R.string.popup_remember_size_pos_key), b);
}
private static boolean isUsingInexactSeek(@NonNull final Context context) { private static boolean isUsingInexactSeek(@NonNull final Context context) {
return getPreferences(context) return getPreferences(context)
.getBoolean(context.getString(R.string.use_inexact_seek_key), false); .getBoolean(context.getString(R.string.use_inexact_seek_key), false);

View file

@ -6,8 +6,12 @@ import android.content.Intent;
import android.content.ServiceConnection; import android.content.ServiceConnection;
import android.os.IBinder; import android.os.IBinder;
import android.util.Log; import android.util.Log;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.PlaybackParameters;
import org.schabi.newpipe.App; import org.schabi.newpipe.App;
import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.StreamInfo;
@ -31,6 +35,20 @@ public final class PlayerHolder {
private static MainPlayer playerService; private static MainPlayer playerService;
private static VideoPlayerImpl player; private static VideoPlayerImpl player;
/**
* Returns the current {@link MainPlayer.PlayerType} of the {@link MainPlayer} service,
* otherwise `null` if no service running.
*
* @return Current PlayerType
*/
@Nullable
public static MainPlayer.PlayerType getType() {
if (player == null) {
return null;
}
return player.getPlayerType();
}
public static void setListener(final PlayerServiceExtendedEventListener newListener) { public static void setListener(final PlayerServiceExtendedEventListener newListener) {
listener = newListener; listener = newListener;
// Force reload data from service // Force reload data from service

View file

@ -52,7 +52,6 @@ public class PlayQueueItemBuilder {
return false; return false;
}); });
holder.itemThumbnailView.setOnTouchListener(getOnTouchListener(holder));
holder.itemHandle.setOnTouchListener(getOnTouchListener(holder)); holder.itemHandle.setOnTouchListener(getOnTouchListener(holder));
} }

View file

@ -8,7 +8,6 @@ import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import androidx.preference.PreferenceManager;
import android.util.Log; import android.util.Log;
import android.view.View; import android.view.View;
import android.widget.Toast; import android.widget.Toast;
@ -16,6 +15,7 @@ import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.preference.Preference; import androidx.preference.Preference;
import androidx.preference.PreferenceManager;
import com.nononsenseapps.filepicker.Utils; import com.nononsenseapps.filepicker.Utils;
import com.nostra13.universalimageloader.core.ImageLoader; import com.nostra13.universalimageloader.core.ImageLoader;
@ -23,6 +23,7 @@ import com.nostra13.universalimageloader.core.ImageLoader;
import org.schabi.newpipe.DownloaderImpl; 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.ReCaptchaActivity;
import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.localization.ContentCountry; import org.schabi.newpipe.extractor.localization.ContentCountry;
import org.schabi.newpipe.extractor.localization.Localization; import org.schabi.newpipe.extractor.localization.Localization;
@ -78,6 +79,22 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
.getPreferredContentCountry(requireContext()); .getPreferredContentCountry(requireContext());
initialLanguage = PreferenceManager initialLanguage = PreferenceManager
.getDefaultSharedPreferences(requireContext()).getString("app_language_key", "en"); .getDefaultSharedPreferences(requireContext()).getString("app_language_key", "en");
final Preference clearCookiePref = findPreference(getString(R.string.clear_cookie_key));
clearCookiePref.setOnPreferenceClickListener(preference -> {
defaultPreferences.edit()
.putString(getString(R.string.recaptcha_cookies_key), "").apply();
DownloaderImpl.getInstance().setCookie(ReCaptchaActivity.RECAPTCHA_COOKIES_KEY, "");
Toast.makeText(getActivity(), R.string.recaptcha_cookies_cleared,
Toast.LENGTH_SHORT).show();
clearCookiePref.setVisible(false);
return true;
});
if (defaultPreferences.getString(getString(R.string.recaptcha_cookies_key), "").isEmpty()) {
clearCookiePref.setVisible(false);
}
} }
@Override @Override

View file

@ -1,12 +0,0 @@
package org.schabi.newpipe.settings;
import android.os.Bundle;
import org.schabi.newpipe.R;
public class DebugSettingsFragment extends BasePreferenceFragment {
@Override
public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
addPreferencesFromResource(R.xml.debug_settings);
}
}

View file

@ -55,7 +55,7 @@ public class HistorySettingsFragment extends BasePreferenceFragment {
.setNegativeButton(R.string.cancel, ((dialog, which) -> dialog.dismiss())) .setNegativeButton(R.string.cancel, ((dialog, which) -> dialog.dismiss()))
.setPositiveButton(R.string.delete, ((dialog, which) -> { .setPositiveButton(R.string.delete, ((dialog, which) -> {
final Disposable onDeletePlaybackStates final Disposable onDeletePlaybackStates
= recordManager.deleteCompelteStreamStateHistory() = recordManager.deleteCompleteStreamStateHistory()
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe( .subscribe(
howManyDeleted -> Toast.makeText(getActivity(), howManyDeleted -> Toast.makeText(getActivity(),
@ -113,7 +113,7 @@ public class HistorySettingsFragment extends BasePreferenceFragment {
.setPositiveButton(R.string.delete, ((dialog, which) -> { .setPositiveButton(R.string.delete, ((dialog, which) -> {
final Disposable onDeletePlaybackStates final Disposable onDeletePlaybackStates
= recordManager.deleteCompelteStreamStateHistory() = recordManager.deleteCompleteStreamStateHistory()
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe( .subscribe(
howManyDeleted -> Toast.makeText(getActivity(), howManyDeleted -> Toast.makeText(getActivity(),

View file

@ -21,10 +21,5 @@ public class MainSettingsFragment extends BasePreferenceFragment {
defaultPreferences.edit().putBoolean(getString(R.string.update_app_key), false).apply(); defaultPreferences.edit().putBoolean(getString(R.string.update_app_key), false).apply();
} }
if (!DEBUG) {
final Preference debug = findPreference(getString(R.string.debug_pref_screen_key));
getPreferenceScreen().removePreference(debug);
}
} }
} }

View file

@ -9,10 +9,9 @@ import android.os.Build;
import android.view.KeyEvent; import android.view.KeyEvent;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import org.schabi.newpipe.App; import androidx.core.content.ContextCompat;
import static android.content.Context.BATTERY_SERVICE; import org.schabi.newpipe.App;
import static android.content.Context.UI_MODE_SERVICE;
public final class DeviceUtils { public final class DeviceUtils {
@ -30,15 +29,14 @@ public final class DeviceUtils {
final PackageManager pm = App.getApp().getPackageManager(); final PackageManager pm = App.getApp().getPackageManager();
// from doc: https://developer.android.com/training/tv/start/hardware.html#runtime-check // from doc: https://developer.android.com/training/tv/start/hardware.html#runtime-check
boolean isTv = ((UiModeManager) context.getSystemService(UI_MODE_SERVICE)) boolean isTv = ContextCompat.getSystemService(context, UiModeManager.class)
.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION .getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION
|| pm.hasSystemFeature(AMAZON_FEATURE_FIRE_TV) || pm.hasSystemFeature(AMAZON_FEATURE_FIRE_TV)
|| pm.hasSystemFeature(PackageManager.FEATURE_TELEVISION); || pm.hasSystemFeature(PackageManager.FEATURE_TELEVISION);
// from https://stackoverflow.com/a/58932366 // from https://stackoverflow.com/a/58932366
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
final boolean isBatteryAbsent final boolean isBatteryAbsent = context.getSystemService(BatteryManager.class)
= ((BatteryManager) context.getSystemService(BATTERY_SERVICE))
.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY) == 0; .getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY) == 0;
isTv = isTv || (isBatteryAbsent isTv = isTv || (isBatteryAbsent
&& !pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN) && !pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN)

View file

@ -3,6 +3,8 @@ package org.schabi.newpipe.util;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.net.ConnectivityManager; import android.net.ConnectivityManager;
import androidx.core.content.ContextCompat;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@ -543,7 +545,7 @@ public final class ListHelper {
*/ */
public static boolean isMeteredNetwork(final Context context) { public static boolean isMeteredNetwork(final Context context) {
final ConnectivityManager manager final ConnectivityManager manager
= (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); = ContextCompat.getSystemService(context, ConnectivityManager.class);
if (manager == null || manager.getActiveNetworkInfo() == null) { if (manager == null || manager.getActiveNetworkInfo() == null) {
return false; return false;
} }

View file

@ -5,13 +5,15 @@ import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.content.res.Resources; import android.content.res.Resources;
import androidx.preference.PreferenceManager; import android.icu.text.CompactDecimalFormat;
import android.os.Build;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.DisplayMetrics; import android.util.DisplayMetrics;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.PluralsRes; import androidx.annotation.PluralsRes;
import androidx.annotation.StringRes; import androidx.annotation.StringRes;
import androidx.preference.PreferenceManager;
import org.ocpsoft.prettytime.PrettyTime; import org.ocpsoft.prettytime.PrettyTime;
import org.ocpsoft.prettytime.units.Decade; import org.ocpsoft.prettytime.units.Decade;
@ -184,6 +186,11 @@ public final class Localization {
} }
public static String shortCount(final Context context, final long count) { public static String shortCount(final Context context, final long count) {
if (Build.VERSION.SDK_INT >= 24) {
return CompactDecimalFormat.getInstance(getAppLocale(context),
CompactDecimalFormat.CompactStyle.SHORT).format(count);
}
final double value = (double) count; final double value = (double) count;
if (count >= 1000000000) { if (count >= 1000000000) {
return localizeNumber(context, round(value / 1000000000, 1)) return localizeNumber(context, round(value / 1000000000, 1))

View file

@ -7,17 +7,18 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import androidx.preference.PreferenceManager;
import android.util.Log; import android.util.Log;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction; import androidx.fragment.app.FragmentTransaction;
import androidx.preference.PreferenceManager;
import com.nostra13.universalimageloader.core.ImageLoader; import com.nostra13.universalimageloader.core.ImageLoader;
@ -174,7 +175,7 @@ public final class NavigationHelper {
Toast.makeText(context, R.string.popup_playing_toast, Toast.LENGTH_SHORT).show(); Toast.makeText(context, R.string.popup_playing_toast, Toast.LENGTH_SHORT).show();
final Intent intent = getPlayerIntent(context, MainPlayer.class, queue, resumePlayback); final Intent intent = getPlayerIntent(context, MainPlayer.class, queue, resumePlayback);
intent.putExtra(VideoPlayer.PLAYER_TYPE, VideoPlayer.PLAYER_TYPE_POPUP); intent.putExtra(VideoPlayer.PLAYER_TYPE, VideoPlayer.PLAYER_TYPE_POPUP);
startService(context, intent); ContextCompat.startForegroundService(context, intent);
} }
public static void playOnBackgroundPlayer(final Context context, public static void playOnBackgroundPlayer(final Context context,
@ -184,7 +185,24 @@ public final class NavigationHelper {
.show(); .show();
final Intent intent = getPlayerIntent(context, MainPlayer.class, queue, resumePlayback); final Intent intent = getPlayerIntent(context, MainPlayer.class, queue, resumePlayback);
intent.putExtra(VideoPlayer.PLAYER_TYPE, VideoPlayer.PLAYER_TYPE_AUDIO); intent.putExtra(VideoPlayer.PLAYER_TYPE, VideoPlayer.PLAYER_TYPE_AUDIO);
startService(context, intent); ContextCompat.startForegroundService(context, intent);
}
public static void enqueueOnVideoPlayer(final Context context, final PlayQueue queue,
final boolean resumePlayback) {
enqueueOnVideoPlayer(context, queue, false, resumePlayback);
}
public static void enqueueOnVideoPlayer(final Context context, final PlayQueue queue,
final boolean selectOnAppend,
final boolean resumePlayback) {
Toast.makeText(context, R.string.enqueued, Toast.LENGTH_SHORT).show();
final Intent intent = getPlayerEnqueueIntent(
context, MainPlayer.class, queue, selectOnAppend, resumePlayback);
intent.putExtra(VideoPlayer.PLAYER_TYPE, VideoPlayer.PLAYER_TYPE_VIDEO);
ContextCompat.startForegroundService(context, intent);
} }
public static void enqueueOnPopupPlayer(final Context context, final PlayQueue queue, public static void enqueueOnPopupPlayer(final Context context, final PlayQueue queue,
@ -200,11 +218,11 @@ public final class NavigationHelper {
return; return;
} }
Toast.makeText(context, R.string.popup_playing_append, Toast.LENGTH_SHORT).show(); Toast.makeText(context, R.string.enqueued, Toast.LENGTH_SHORT).show();
final Intent intent = getPlayerEnqueueIntent( final Intent intent = getPlayerEnqueueIntent(
context, MainPlayer.class, queue, selectOnAppend, resumePlayback); context, MainPlayer.class, queue, selectOnAppend, resumePlayback);
intent.putExtra(VideoPlayer.PLAYER_TYPE, VideoPlayer.PLAYER_TYPE_POPUP); intent.putExtra(VideoPlayer.PLAYER_TYPE, VideoPlayer.PLAYER_TYPE_POPUP);
startService(context, intent); ContextCompat.startForegroundService(context, intent);
} }
public static void enqueueOnBackgroundPlayer(final Context context, final PlayQueue queue, public static void enqueueOnBackgroundPlayer(final Context context, final PlayQueue queue,
@ -216,19 +234,11 @@ public final class NavigationHelper {
final PlayQueue queue, final PlayQueue queue,
final boolean selectOnAppend, final boolean selectOnAppend,
final boolean resumePlayback) { final boolean resumePlayback) {
Toast.makeText(context, R.string.background_player_append, Toast.LENGTH_SHORT).show(); Toast.makeText(context, R.string.enqueued, Toast.LENGTH_SHORT).show();
final Intent intent = getPlayerEnqueueIntent( final Intent intent = getPlayerEnqueueIntent(
context, MainPlayer.class, queue, selectOnAppend, resumePlayback); context, MainPlayer.class, queue, selectOnAppend, resumePlayback);
intent.putExtra(VideoPlayer.PLAYER_TYPE, VideoPlayer.PLAYER_TYPE_AUDIO); intent.putExtra(VideoPlayer.PLAYER_TYPE, VideoPlayer.PLAYER_TYPE_AUDIO);
startService(context, intent); ContextCompat.startForegroundService(context, intent);
}
public static void startService(@NonNull final Context context, @NonNull final Intent intent) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(intent);
} else {
context.startService(intent);
}
} }
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
@ -396,7 +406,7 @@ public final class NavigationHelper {
defaultTransaction(fragmentManager) defaultTransaction(fragmentManager)
.replace(R.id.fragment_player_holder, instance) .replace(R.id.fragment_player_holder, instance)
.runOnCommit(() -> sendPlayerStartedEvent(instance.requireActivity())) .runOnCommit(() -> sendPlayerStartedEvent(instance.requireActivity()))
.commit(); .commitAllowingStateLoss();
} }
public static void openChannelFragment(final FragmentManager fragmentManager, public static void openChannelFragment(final FragmentManager fragmentManager,

View file

@ -9,6 +9,8 @@ import android.content.pm.ResolveInfo;
import android.net.Uri; import android.net.Uri;
import android.widget.Toast; import android.widget.Toast;
import androidx.core.content.ContextCompat;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
public final class ShareUtils { public final class ShareUtils {
@ -95,7 +97,7 @@ public final class ShareUtils {
*/ */
public static void copyToClipboard(final Context context, final String text) { public static void copyToClipboard(final Context context, final String text) {
final ClipboardManager clipboardManager = final ClipboardManager clipboardManager =
(ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); ContextCompat.getSystemService(context, ClipboardManager.class);
if (clipboardManager == null) { if (clipboardManager == null) {
Toast.makeText(context, Toast.makeText(context,

View file

@ -7,22 +7,41 @@ import androidx.fragment.app.Fragment;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.local.dialog.PlaylistAppendDialog; import org.schabi.newpipe.local.dialog.PlaylistAppendDialog;
import org.schabi.newpipe.local.dialog.PlaylistCreationDialog;
import org.schabi.newpipe.player.MainPlayer;
import org.schabi.newpipe.player.helper.PlayerHolder;
import org.schabi.newpipe.player.playqueue.SinglePlayQueue; import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
import java.util.Collections; import java.util.Collections;
import java.util.List;
import static org.schabi.newpipe.player.MainPlayer.PlayerType.AUDIO;
import static org.schabi.newpipe.player.MainPlayer.PlayerType.POPUP;
public enum StreamDialogEntry { public enum StreamDialogEntry {
////////////////////////////////////// //////////////////////////////////////
// enum values with DEFAULT actions // // enum values with DEFAULT actions //
////////////////////////////////////// //////////////////////////////////////
enqueue_on_background(R.string.enqueue_on_background, (fragment, item) -> /**
NavigationHelper.enqueueOnBackgroundPlayer(fragment.getContext(), * Enqueues the stream automatically to the current PlayerType.<br>
new SinglePlayQueue(item), false)), * <br>
* Info: Add this entry within showStreamDialog.
*/
enqueue(R.string.enqueue_stream, (fragment, item) -> {
final MainPlayer.PlayerType type = PlayerHolder.getType();
enqueue_on_popup(R.string.enqueue_on_popup, (fragment, item) -> if (type == AUDIO) {
NavigationHelper.enqueueOnBackgroundPlayer(fragment.getContext(),
new SinglePlayQueue(item), false);
} else if (type == POPUP) {
NavigationHelper.enqueueOnPopupPlayer(fragment.getContext(), NavigationHelper.enqueueOnPopupPlayer(fragment.getContext(),
new SinglePlayQueue(item), false)), new SinglePlayQueue(item), false);
} else /* type == VIDEO */ {
NavigationHelper.enqueueOnVideoPlayer(fragment.getContext(),
new SinglePlayQueue(item), false);
}
}),
start_here_on_background(R.string.start_here_on_background, (fragment, item) -> start_here_on_background(R.string.start_here_on_background, (fragment, item) ->
NavigationHelper.playOnBackgroundPlayer(fragment.getContext(), NavigationHelper.playOnBackgroundPlayer(fragment.getContext(),
@ -40,8 +59,14 @@ public enum StreamDialogEntry {
append_playlist(R.string.append_playlist, (fragment, item) -> { append_playlist(R.string.append_playlist, (fragment, item) -> {
if (fragment.getFragmentManager() != null) { if (fragment.getFragmentManager() != null) {
PlaylistAppendDialog.fromStreamInfoItems(Collections.singletonList(item)) final PlaylistAppendDialog d = PlaylistAppendDialog
.show(fragment.getFragmentManager(), "StreamDialogEntry@append_playlist"); .fromStreamInfoItems(Collections.singletonList(item));
PlaylistAppendDialog.onPlaylistFound(fragment.getContext(),
() -> d.show(fragment.getFragmentManager(), "StreamDialogEntry@append_playlist"),
() -> PlaylistCreationDialog.newInstance(d)
.show(fragment.getFragmentManager(), "StreamDialogEntry@create_playlist")
);
} }
}), }),
@ -69,6 +94,10 @@ public enum StreamDialogEntry {
// non-static methods to initialize and edit entries // // non-static methods to initialize and edit entries //
/////////////////////////////////////////////////////// ///////////////////////////////////////////////////////
public static void setEnabledEntries(final List<StreamDialogEntry> entries) {
setEnabledEntries(entries.toArray(new StreamDialogEntry[0]));
}
/** /**
* To be called before using {@link #setCustomAction(StreamDialogEntryAction)}. * To be called before using {@link #setCustomAction(StreamDialogEntryAction)}.
* *

View file

@ -24,6 +24,8 @@ import android.os.Handler.Callback;
import android.os.IBinder; import android.os.IBinder;
import android.os.Message; import android.os.Message;
import android.os.Parcelable; import android.os.Parcelable;
import androidx.core.content.ContextCompat;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
import android.util.Log; import android.util.Log;
import android.util.SparseArray; import android.util.SparseArray;
@ -157,8 +159,10 @@ public class DownloadManagerService extends Service {
mNotification = builder.build(); mNotification = builder.build();
mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); mNotificationManager = ContextCompat.getSystemService(this,
mConnectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); NotificationManager.class);
mConnectivityManager = ContextCompat.getSystemService(this,
ConnectivityManager.class);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mNetworkStateListenerL = new ConnectivityManager.NetworkCallback() { mNetworkStateListenerL = new ConnectivityManager.NetworkCallback() {

View file

@ -29,6 +29,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.StringRes; import androidx.annotation.StringRes;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import androidx.core.content.FileProvider; import androidx.core.content.FileProvider;
import androidx.core.view.ViewCompat; import androidx.core.view.ViewCompat;
import androidx.recyclerview.widget.DiffUtil; import androidx.recyclerview.widget.DiffUtil;
@ -120,7 +121,7 @@ public class MissionAdapter extends Adapter<ViewHolder> implements Handler.Callb
mContext = context; mContext = context;
mDownloadManager = downloadManager; mDownloadManager = downloadManager;
mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); mInflater = ContextCompat.getSystemService(mContext, LayoutInflater.class);
mLayout = R.layout.mission_item; mLayout = R.layout.mission_item;
mHandler = new Handler(context.getMainLooper()); mHandler = new Handler(context.getMainLooper());

View file

@ -201,7 +201,7 @@ public class Utility {
} }
public static void copyToClipboard(Context context, String str) { public static void copyToClipboard(Context context, String str) {
ClipboardManager cm = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); ClipboardManager cm = ContextCompat.getSystemService(context, ClipboardManager.class);
if (cm == null) { if (cm == null) {
Toast.makeText(context, R.string.permission_denied, Toast.LENGTH_LONG).show(); Toast.makeText(context, R.string.permission_denied, Toast.LENGTH_LONG).show();

View file

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" <set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:interpolator/decelerate_quint"> android:interpolator="@android:interpolator/decelerate_quint">
<alpha <alpha
android:duration="150" android:duration="150"
android:fromAlpha="0.00" android:fromAlpha="0.00"
android:toAlpha="1.0"/> android:toAlpha="1.0" />
</set> </set>

View file

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" <set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:interpolator/accelerate_quint"> android:interpolator="@android:interpolator/accelerate_quint">
<alpha <alpha
android:duration="350" android:duration="350"
android:fromAlpha="1.0" android:fromAlpha="1.0"
android:toAlpha="0.00"/> android:toAlpha="0.00" />
</set> </set>

View file

@ -5,5 +5,5 @@
android:interpolator="@android:interpolator/accelerate_decelerate" android:interpolator="@android:interpolator/accelerate_decelerate"
android:propertyName="alpha" android:propertyName="alpha"
android:valueFrom="0.0f" android:valueFrom="0.0f"
android:valueTo="1.0f"/> android:valueTo="1.0f" />
</set> </set>

View file

@ -5,5 +5,5 @@
android:interpolator="@android:interpolator/accelerate_decelerate" android:interpolator="@android:interpolator/accelerate_decelerate"
android:propertyName="alpha" android:propertyName="alpha"
android:valueFrom="1.0f" android:valueFrom="1.0f"
android:valueTo="0.0f"/> android:valueTo="0.0f" />
</set> </set>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 415 B

After

Width:  |  Height:  |  Size: 358 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 302 B

After

Width:  |  Height:  |  Size: 262 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 570 B

After

Width:  |  Height:  |  Size: 480 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 680 B

After

Width:  |  Height:  |  Size: 581 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 621 B

After

Width:  |  Height:  |  Size: 511 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 285 B

After

Width:  |  Height:  |  Size: 244 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 246 B

After

Width:  |  Height:  |  Size: 223 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 328 B

After

Width:  |  Height:  |  Size: 290 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 401 B

After

Width:  |  Height:  |  Size: 345 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 411 B

After

Width:  |  Height:  |  Size: 345 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 188 B

After

Width:  |  Height:  |  Size: 124 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 602 B

After

Width:  |  Height:  |  Size: 516 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 413 B

After

Width:  |  Height:  |  Size: 362 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 628 B

After

Width:  |  Height:  |  Size: 501 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 734 B

After

Width:  |  Height:  |  Size: 609 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 837 B

After

Width:  |  Height:  |  Size: 693 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1 KiB

After

Width:  |  Height:  |  Size: 873 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 614 B

After

Width:  |  Height:  |  Size: 509 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 912 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 777 B

After

Width:  |  Height:  |  Size: 687 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 901 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -2,4 +2,4 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android" <shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval"> android:shape="oval">
<solid android:color="#64000000" /> <solid android:color="#64000000" />
</shape> </shape>

View file

@ -2,16 +2,16 @@
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/background"> <item android:id="@android:id/background">
<shape> <shape>
<solid android:color="@color/gray"/> <solid android:color="@color/gray" />
</shape> </shape>
</item> </item>
<item android:id="@android:id/progress"> <item android:id="@android:id/progress">
<clip> <clip>
<shape> <shape>
<solid android:color="@color/middle_gray"/> <solid android:color="@color/middle_gray" />
</shape> </shape>
</clip> </clip>
</item> </item>
</layer-list> </layer-list>

View file

@ -3,6 +3,6 @@
<stroke <stroke
android:width="1dp" android:width="1dp"
android:color="@color/black_border_color" android:color="@color/black_border_color"
android:dashGap="4dp" android:dashWidth="4dp"
android:dashWidth="4dp"/> android:dashGap="4dp" />
</shape> </shape>

View file

@ -3,6 +3,6 @@
<stroke <stroke
android:width="1dp" android:width="1dp"
android:color="@color/dark_border_color" android:color="@color/dark_border_color"
android:dashGap="4dp" android:dashWidth="4dp"
android:dashWidth="4dp"/> android:dashGap="4dp" />
</shape> </shape>

View file

@ -3,6 +3,6 @@
<stroke <stroke
android:width="1dp" android:width="1dp"
android:color="@color/light_border_color" android:color="@color/light_border_color"
android:dashGap="4dp" android:dashWidth="4dp"
android:dashWidth="4dp"/> android:dashGap="4dp" />
</shape> </shape>

View file

@ -6,7 +6,7 @@
android:shape="ring" android:shape="ring"
android:thickness="4dp" android:thickness="4dp"
android:useLevel="false"> android:useLevel="false">
<solid android:color="@android:color/darker_gray"/> <solid android:color="@android:color/darker_gray" />
</shape> </shape>
</item> </item>
</layer-list> </layer-list>

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