mirror of
https://github.com/TeamNewPipe/NewPipe
synced 2026-01-14 19:17:55 +00:00
Compare commits
345 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c900ef036c | ||
|
|
d088d432c5 | ||
|
|
c9fafbe198 | ||
|
|
2d909b0514 | ||
|
|
c2a012553d | ||
|
|
085f0266ac | ||
|
|
7ede2daa3c | ||
|
|
713c53d170 | ||
|
|
110b3a6a8f | ||
|
|
e12e6dd7a7 | ||
|
|
e183fc6118 | ||
|
|
dd57e246b8 | ||
|
|
f4a4172369 | ||
|
|
b96d1714b5 | ||
|
|
dbd809b040 | ||
|
|
ff4e6b139d | ||
|
|
af098aaac8 | ||
|
|
5d7e62c736 | ||
|
|
e2ead011f5 | ||
|
|
a067c950e1 | ||
|
|
3369618cfa | ||
|
|
4e9b6520e5 | ||
|
|
a074203fae | ||
|
|
42092e3f28 | ||
|
|
c07b34e298 | ||
|
|
ef5f181328 | ||
|
|
a7ea2fcf92 | ||
|
|
e81730715c | ||
|
|
c09f2ad482 | ||
|
|
84aef8b5b5 | ||
|
|
8fac3e8221 | ||
|
|
5874ed781d | ||
|
|
d8f29bd7a7 | ||
|
|
8120b6aaaa | ||
|
|
13a0d1de70 | ||
|
|
20e828be51 | ||
|
|
ccd82fb8b8 | ||
|
|
0711650ff8 | ||
|
|
4194ac2226 | ||
|
|
248e2d7ee0 | ||
|
|
1daa654051 | ||
|
|
d751434979 | ||
|
|
8cc21920b7 | ||
|
|
248212588d | ||
|
|
13c0fdef08 | ||
|
|
dfc27b2480 | ||
|
|
b2d78d380b | ||
|
|
faa6cb5c7d | ||
|
|
452977abdf | ||
|
|
93570b2f59 | ||
|
|
073f5c2c8c | ||
|
|
1b4313f847 | ||
|
|
07cead7e99 | ||
|
|
f128751aba | ||
|
|
9516d9da17 | ||
|
|
e9aafc2a56 | ||
|
|
a91a6575e0 | ||
|
|
5bd4093dfb | ||
|
|
f9890e2016 | ||
|
|
00529fe134 | ||
|
|
5e9dce7d39 | ||
|
|
734680b9f0 | ||
|
|
d0b5345252 | ||
|
|
c2b4a44a59 | ||
|
|
38c79bbc11 | ||
|
|
0d7028a36c | ||
|
|
b63f687491 | ||
|
|
952b636569 | ||
|
|
e14ba48244 | ||
|
|
720c8c31ec | ||
|
|
4e8407ed8f | ||
|
|
26c5f69161 | ||
|
|
078ae15794 | ||
|
|
16ae90dc9f | ||
|
|
3de5afc68e | ||
|
|
a7e8f5087e | ||
|
|
2e6e75cd4e | ||
|
|
9f3b35634a | ||
|
|
c24dfc63dc | ||
|
|
c029929850 | ||
|
|
d9100913d5 | ||
|
|
e03d970bc2 | ||
|
|
fe4516ea23 | ||
|
|
039b47b872 | ||
|
|
0a57a8a7f3 | ||
|
|
8c823f3a2d | ||
|
|
33f3a4f455 | ||
|
|
2ecf8044d7 | ||
|
|
c9a1eb55b5 | ||
|
|
76bb1fd61e | ||
|
|
1275b26ba0 | ||
|
|
a470a4af9b | ||
|
|
487c9ebbd4 | ||
|
|
746cab92f0 | ||
|
|
33266a96ff | ||
|
|
2816889d8d | ||
|
|
58e177b3e4 | ||
|
|
5cfd8bbb56 | ||
|
|
e6fe6fd645 | ||
|
|
63e167b38e | ||
|
|
abe77c4783 | ||
|
|
72af51fe9d | ||
|
|
36b4134838 | ||
|
|
cf6ee26fdb | ||
|
|
858111e623 | ||
|
|
367e625804 | ||
|
|
ae4d9c7f80 | ||
|
|
6c6ee41346 | ||
|
|
9ef7688f9e | ||
|
|
7d6e226c2b | ||
|
|
17d1346a8a | ||
|
|
59e0c10c42 | ||
|
|
0d29e66092 | ||
|
|
caa000f447 | ||
|
|
267e114354 | ||
|
|
b5375396d2 | ||
|
|
e34f666b70 | ||
|
|
19334b4f96 | ||
|
|
1fa609e539 | ||
|
|
d9ce25a721 | ||
|
|
5b8fc25da6 | ||
|
|
c70968dcf1 | ||
|
|
77147510fb | ||
|
|
7092577482 | ||
|
|
3e70050056 | ||
|
|
1f23c814e5 | ||
|
|
145e0a0b7b | ||
|
|
e68e787e7a | ||
|
|
903308d285 | ||
|
|
a4d8388b2e | ||
|
|
3e83f9f956 | ||
|
|
4063221313 | ||
|
|
c3c8d80919 | ||
|
|
3ae71c73c4 | ||
|
|
b3db8c9549 | ||
|
|
596eb4a0f9 | ||
|
|
6b0381b903 | ||
|
|
049c8f70cd | ||
|
|
353bf69550 | ||
|
|
cdb989ede3 | ||
|
|
74079e4238 | ||
|
|
c89746214c | ||
|
|
1d97db3ef9 | ||
|
|
b2a5ff5f9d | ||
|
|
f47ef2b5ea | ||
|
|
bd7ec3b692 | ||
|
|
52895e7b6b | ||
|
|
a6a82c6477 | ||
|
|
af66ed94b2 | ||
|
|
e43fdf5ef9 | ||
|
|
3984fc075f | ||
|
|
ee2a159374 | ||
|
|
57c9b29ba3 | ||
|
|
927ea72337 | ||
|
|
123bdbd13b | ||
|
|
0baa5a2f04 | ||
|
|
7d98a70028 | ||
|
|
73b72ab01c | ||
|
|
e8cf71f41c | ||
|
|
5f2d2a64d2 | ||
|
|
27bf3901de | ||
|
|
16d4fa03a5 | ||
|
|
bef579ec26 | ||
|
|
0cff10e02e | ||
|
|
4c6f7238dd | ||
|
|
583f1476d6 | ||
|
|
b42bef32fd | ||
|
|
8bb85ccf19 | ||
|
|
3d88c2a5fa | ||
|
|
e350acaf08 | ||
|
|
172f70bef9 | ||
|
|
9d25c0bf8a | ||
|
|
75b377aab3 | ||
|
|
3706f30b44 | ||
|
|
a9697a61ad | ||
|
|
e16a2d7cb6 | ||
|
|
f106e2945b | ||
|
|
1ad7deddb1 | ||
|
|
7b81e98581 | ||
|
|
0cae58ce8e | ||
|
|
7231150115 | ||
|
|
071986a4c9 | ||
|
|
39e7d43f10 | ||
|
|
aa1b17ae66 | ||
|
|
92bae88355 | ||
|
|
067eaf363e | ||
|
|
00efc266d9 | ||
|
|
0b014185e3 | ||
|
|
3eee7378de | ||
|
|
c31428f6bc | ||
|
|
163e561cf9 | ||
|
|
a32ded2829 | ||
|
|
a5b7517fbd | ||
|
|
176d57b35a | ||
|
|
927a1d58e2 | ||
|
|
bbd0df08d3 | ||
|
|
9e57195e14 | ||
|
|
05ab54c30d | ||
|
|
e3e2028153 | ||
|
|
883bcc735d | ||
|
|
158727e2f2 | ||
|
|
899f69d120 | ||
|
|
b575046c05 | ||
|
|
b5c60d2be2 | ||
|
|
631dfee763 | ||
|
|
d7f610113e | ||
|
|
e0e4f6db2b | ||
|
|
c27a26c0aa | ||
|
|
3dcd2468a2 | ||
|
|
ea43b28f74 | ||
|
|
a3e2a085b6 | ||
|
|
635d51b60d | ||
|
|
95eb1c0d95 | ||
|
|
8aca43a7e6 | ||
|
|
f6afe59788 | ||
|
|
8e13161f64 | ||
|
|
97437b8af3 | ||
|
|
9a938093e2 | ||
|
|
3edcc9f9fd | ||
|
|
55db408720 | ||
|
|
caef874814 | ||
|
|
1622639eca | ||
|
|
4df89f4217 | ||
|
|
079b98ed3f | ||
|
|
a0526d2c9c | ||
|
|
169b1cbd32 | ||
|
|
8968081e77 | ||
|
|
93ba7510e1 | ||
|
|
579bb743bb | ||
|
|
9e5e9ea612 | ||
|
|
2241a13cba | ||
|
|
9c1fb0cb92 | ||
|
|
4904514257 | ||
|
|
6d829c26a1 | ||
|
|
c05467fb92 | ||
|
|
d47f7f3348 | ||
|
|
5931a84651 | ||
|
|
5771783d11 | ||
|
|
aae07a60bd | ||
|
|
462d418ee0 | ||
|
|
ce63c2e1db | ||
|
|
52baf8cbe5 | ||
|
|
1779b9ee1a | ||
|
|
eae169236c | ||
|
|
ce0efba0d2 | ||
|
|
87c7ac3970 | ||
|
|
0f8d196a52 | ||
|
|
6dc7dab154 | ||
|
|
dd4cb23005 | ||
|
|
0589017f8c | ||
|
|
55027a9b2b | ||
|
|
0a984ca8c8 | ||
|
|
929d13bfea | ||
|
|
375e18bec8 | ||
|
|
b9de74f183 | ||
|
|
08ca69507f | ||
|
|
65ca982342 | ||
|
|
658281a92c | ||
|
|
a959f61367 | ||
|
|
8e61f744ec | ||
|
|
73d3e52e29 | ||
|
|
49a134845c | ||
|
|
29807f3d39 | ||
|
|
29b79b7725 | ||
|
|
1cdb10a040 | ||
|
|
5f7851df72 | ||
|
|
3d9bc05d7a | ||
|
|
c127428c59 | ||
|
|
7966d8403a | ||
|
|
80cc8a8e02 | ||
|
|
ee4e205fef | ||
|
|
ea443dc80c | ||
|
|
283645513d | ||
|
|
81b99382b8 | ||
|
|
ab74465e6c | ||
|
|
b3eadb557b | ||
|
|
0abd2bcba6 | ||
|
|
9cf76a918e | ||
|
|
ae437b1510 | ||
|
|
1096ec1c09 | ||
|
|
235394d96c | ||
|
|
d25e1d801c | ||
|
|
2dca5ab966 | ||
|
|
89ab57b1c1 | ||
|
|
dc66e6a4bf | ||
|
|
5c2f2fd882 | ||
|
|
5c3ddefbf9 | ||
|
|
a8d3f45ea1 | ||
|
|
cc2c41ddc8 | ||
|
|
b990f30a09 | ||
|
|
5c711322d4 | ||
|
|
b7d4a4f604 | ||
|
|
cc8874b687 | ||
|
|
2d0bc05488 | ||
|
|
1429774487 | ||
|
|
2060312dc1 | ||
|
|
8b6728480f | ||
|
|
cecafdee29 | ||
|
|
05f2af25af | ||
|
|
e3d826f6c4 | ||
|
|
02430bed90 | ||
|
|
3f7005ed9a | ||
|
|
586ee75833 | ||
|
|
1d903f11a8 | ||
|
|
578159b95c | ||
|
|
5c95587284 | ||
|
|
f7e9227ad2 | ||
|
|
c11a4d6867 | ||
|
|
6c2b0448a4 | ||
|
|
bd0eb8cccf | ||
|
|
09bb043952 | ||
|
|
9ca6cfd637 | ||
|
|
3869a66fcc | ||
|
|
d1c94f5120 | ||
|
|
c55e9941ec | ||
|
|
fa9a419d73 | ||
|
|
ab4e0da6b4 | ||
|
|
073572681e | ||
|
|
b630f269c4 | ||
|
|
40b1cd82b1 | ||
|
|
abcbdef63b | ||
|
|
56d53d8805 | ||
|
|
7433fe049c | ||
|
|
bb2be49d3b | ||
|
|
cd1b578e84 | ||
|
|
c3df9b4105 | ||
|
|
b0415a5289 | ||
|
|
ff7344438b | ||
|
|
f5f8e5d279 | ||
|
|
b4ddc8f96c | ||
|
|
8ebb1e29fa | ||
|
|
72c9845174 | ||
|
|
1975973ff2 | ||
|
|
f60cce54ea | ||
|
|
63087a4311 | ||
|
|
5a193d50f6 | ||
|
|
08a6e999b9 | ||
|
|
e33cdca1ef | ||
|
|
9ede7a3c42 | ||
|
|
430d4e1ccd | ||
|
|
de4d6037d3 | ||
|
|
63afacc067 | ||
|
|
3175199787 | ||
|
|
562754c0b9 | ||
|
|
d9c6f7acb6 |
8
.gitignore
vendored
8
.gitignore
vendored
@@ -10,3 +10,11 @@
|
||||
*~
|
||||
.weblate
|
||||
*.class
|
||||
|
||||
# vscode / eclipse files
|
||||
*.classpath
|
||||
*.project
|
||||
*.settings
|
||||
bin/
|
||||
.vscode/
|
||||
*.code-workspace
|
||||
|
||||
@@ -5,13 +5,13 @@ android:
|
||||
components:
|
||||
# The BuildTools version used by NewPipe
|
||||
- tools
|
||||
- build-tools-28.0.3
|
||||
- build-tools-29.0.3
|
||||
|
||||
# The SDK version used to compile NewPipe
|
||||
- android-28
|
||||
- android-29
|
||||
|
||||
before_install:
|
||||
- yes | sdkmanager "platforms;android-28"
|
||||
- yes | sdkmanager "platforms;android-29"
|
||||
script: ./gradlew -Dorg.gradle.jvmargs=-Xmx1536m assembleDebug lintDebug testDebugUnitTest
|
||||
|
||||
licenses:
|
||||
|
||||
161
app/build.gradle
161
app/build.gradle
@@ -5,16 +5,16 @@ apply plugin: 'kotlin-kapt'
|
||||
apply plugin: 'checkstyle'
|
||||
|
||||
android {
|
||||
compileSdkVersion 28
|
||||
buildToolsVersion '28.0.3'
|
||||
compileSdkVersion 29
|
||||
buildToolsVersion '29.0.3'
|
||||
|
||||
defaultConfig {
|
||||
applicationId "org.schabi.newpipe"
|
||||
resValue "string", "app_name", "NewPipe"
|
||||
minSdkVersion 19
|
||||
targetSdkVersion 28
|
||||
versionCode 930
|
||||
versionName "0.19.3"
|
||||
targetSdkVersion 29
|
||||
versionCode 951
|
||||
versionName "0.19.6"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
@@ -66,6 +66,7 @@ android {
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
encoding 'utf-8'
|
||||
}
|
||||
|
||||
// Required and used only by groupie
|
||||
@@ -79,22 +80,27 @@ android {
|
||||
}
|
||||
|
||||
ext {
|
||||
androidxLibVersion = '1.0.0'
|
||||
exoPlayerLibVersion = '2.10.8'
|
||||
roomDbLibVersion = '2.1.0'
|
||||
leakCanaryLibVersion = '1.5.4' //1.6.1
|
||||
okHttpLibVersion = '3.12.6'
|
||||
icepickLibVersion = '3.2.0'
|
||||
stethoLibVersion = '1.5.0'
|
||||
markwonVersion = '4.2.1'
|
||||
checkstyleVersion = '8.31'
|
||||
icepickVersion = '3.2.0'
|
||||
checkstyleVersion = '8.32'
|
||||
stethoVersion = '1.5.1'
|
||||
leakCanaryVersion = '2.2'
|
||||
exoPlayerVersion = '2.11.6'
|
||||
androidxLifecycleVersion = '2.2.0'
|
||||
androidxRoomVersion = '2.2.5'
|
||||
groupieVersion = '2.8.0'
|
||||
markwonVersion = '4.3.1'
|
||||
}
|
||||
|
||||
configurations {
|
||||
checkstyle
|
||||
ktlint
|
||||
}
|
||||
|
||||
checkstyle {
|
||||
configFile rootProject.file('checkstyle.xml')
|
||||
ignoreFailures false
|
||||
showViolations true
|
||||
toolVersion = "${checkstyleVersion}"
|
||||
toolVersion = checkstyleVersion
|
||||
}
|
||||
|
||||
task runCheckstyle(type: Checkstyle) {
|
||||
@@ -105,8 +111,7 @@ task runCheckstyle(type: Checkstyle) {
|
||||
exclude '**/BuildConfig.java'
|
||||
exclude 'main/java/us/shandian/giga/**'
|
||||
|
||||
// empty classpath
|
||||
classpath = files()
|
||||
classpath = configurations.checkstyle
|
||||
|
||||
showViolations true
|
||||
|
||||
@@ -116,88 +121,92 @@ task runCheckstyle(type: Checkstyle) {
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType(Checkstyle).each {
|
||||
checkstyleTask -> checkstyleTask.doLast {
|
||||
reports.all { report ->
|
||||
def outputFile = report.destination
|
||||
if (outputFile.exists() && outputFile.text.contains("severity=\"error\"")) {
|
||||
throw new GradleException("There were checkstyle errors! For more info check $outputFile")
|
||||
}
|
||||
}
|
||||
}
|
||||
task runKtlint(type: JavaExec) {
|
||||
main = "com.pinterest.ktlint.Main"
|
||||
classpath = configurations.ktlint
|
||||
args "src/**/*.kt"
|
||||
}
|
||||
|
||||
task formatKtlint(type: JavaExec) {
|
||||
main = "com.pinterest.ktlint.Main"
|
||||
classpath = configurations.ktlint
|
||||
args "-F", "src/**/*.kt"
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
preDebugBuild.dependsOn runCheckstyle
|
||||
preDebugBuild.dependsOn runCheckstyle, runKtlint
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
|
||||
debugImplementation "com.puppycrawl.tools:checkstyle:${checkstyleVersion}"
|
||||
implementation "frankiesardo:icepick:${icepickVersion}"
|
||||
kapt "frankiesardo:icepick-processor:${icepickVersion}"
|
||||
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
||||
androidTestImplementation "android.arch.persistence.room:testing:1.1.1"
|
||||
androidTestImplementation('androidx.test.espresso:espresso-core:3.1.0', {
|
||||
checkstyle "com.puppycrawl.tools:checkstyle:${checkstyleVersion}"
|
||||
ktlint "com.pinterest:ktlint:0.35.0"
|
||||
|
||||
debugImplementation "com.facebook.stetho:stetho:${stethoVersion}"
|
||||
debugImplementation "com.facebook.stetho:stetho-okhttp3:${stethoVersion}"
|
||||
|
||||
debugImplementation "com.squareup.leakcanary:leakcanary-android:${leakCanaryVersion}"
|
||||
implementation "com.squareup.leakcanary:leakcanary-object-watcher-android:${leakCanaryVersion}"
|
||||
|
||||
debugImplementation "androidx.multidex:multidex:2.0.1"
|
||||
|
||||
testImplementation 'junit:junit:4.13'
|
||||
testImplementation 'org.mockito:mockito-core:3.3.3'
|
||||
|
||||
androidTestImplementation "androidx.test.ext:junit:1.1.1"
|
||||
androidTestImplementation "androidx.room:room-testing:${androidxRoomVersion}"
|
||||
androidTestImplementation "androidx.test.espresso:espresso-core:3.2.0", {
|
||||
exclude module: 'support-annotations'
|
||||
})
|
||||
}
|
||||
|
||||
implementation 'com.github.TeamNewPipe:NewPipeExtractor:fc3a69ed54b393e3e4e3a78ae6e89edc1d47c45a'
|
||||
testImplementation 'junit:junit:4.12'
|
||||
testImplementation 'org.mockito:mockito-core:2.23.0'
|
||||
implementation 'com.github.TeamNewPipe:NewPipeExtractor:a70cb0283ffc3bba2709815673a5a7940aab0a3a'
|
||||
|
||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||
implementation "androidx.legacy:legacy-support-v4:${androidxLibVersion}"
|
||||
implementation "com.google.android.material:material:${androidxLibVersion}"
|
||||
implementation "androidx.recyclerview:recyclerview:${androidxLibVersion}"
|
||||
implementation "androidx.legacy:legacy-preference-v14:${androidxLibVersion}"
|
||||
implementation "androidx.cardview:cardview:${androidxLibVersion}"
|
||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||
implementation "com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751"
|
||||
implementation "org.jsoup:jsoup:1.13.1"
|
||||
|
||||
implementation 'com.xwray:groupie:2.7.0'
|
||||
implementation 'com.xwray:groupie-kotlin-android-extensions:2.7.0'
|
||||
implementation "com.squareup.okhttp3:okhttp:3.12.11"
|
||||
|
||||
implementation 'androidx.lifecycle:lifecycle-livedata:2.0.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-viewmodel:2.0.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
|
||||
implementation "com.google.android.exoplayer:exoplayer:${exoPlayerVersion}"
|
||||
implementation "com.google.android.exoplayer:extension-mediasession:${exoPlayerVersion}"
|
||||
|
||||
// Originally in NewPipeExtractor
|
||||
implementation 'com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751'
|
||||
implementation 'org.jsoup:jsoup:1.9.2'
|
||||
implementation "com.google.android.material:material:1.1.0"
|
||||
|
||||
implementation 'ch.acra:acra:4.9.2' //4.11
|
||||
implementation "androidx.appcompat:appcompat:1.1.0"
|
||||
implementation "androidx.preference:preference:1.1.1"
|
||||
implementation "androidx.recyclerview:recyclerview:1.1.0"
|
||||
implementation "androidx.cardview:cardview:1.0.0"
|
||||
implementation "androidx.constraintlayout:constraintlayout:1.1.3"
|
||||
|
||||
implementation 'com.nostra13.universalimageloader:universal-image-loader:1.9.5'
|
||||
implementation 'de.hdodenhof:circleimageview:2.2.0'
|
||||
implementation 'com.nononsenseapps:filepicker:4.2.1'
|
||||
implementation "androidx.lifecycle:lifecycle-livedata:${androidxLifecycleVersion}"
|
||||
implementation "androidx.lifecycle:lifecycle-viewmodel:${androidxLifecycleVersion}"
|
||||
implementation "androidx.lifecycle:lifecycle-extensions:${androidxLifecycleVersion}"
|
||||
|
||||
implementation "com.google.android.exoplayer:exoplayer:${exoPlayerLibVersion}"
|
||||
implementation "com.google.android.exoplayer:extension-mediasession:${exoPlayerLibVersion}"
|
||||
implementation "androidx.room:room-runtime:${androidxRoomVersion}"
|
||||
implementation "androidx.room:room-rxjava2:${androidxRoomVersion}"
|
||||
kapt "androidx.room:room-compiler:${androidxRoomVersion}"
|
||||
|
||||
debugImplementation "com.facebook.stetho:stetho:${stethoLibVersion}"
|
||||
debugImplementation "com.facebook.stetho:stetho-urlconnection:${stethoLibVersion}"
|
||||
debugImplementation 'androidx.multidex:multidex:2.0.1'
|
||||
implementation "com.xwray:groupie:${groupieVersion}"
|
||||
implementation "com.xwray:groupie-kotlin-android-extensions:${groupieVersion}"
|
||||
|
||||
implementation 'io.reactivex.rxjava2:rxjava:2.2.2'
|
||||
implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
|
||||
implementation 'com.jakewharton.rxbinding2:rxbinding:2.1.1'
|
||||
implementation 'org.ocpsoft.prettytime:prettytime:4.0.3.Final'
|
||||
|
||||
implementation "androidx.room:room-runtime:${roomDbLibVersion}"
|
||||
implementation "androidx.room:room-rxjava2:${roomDbLibVersion}"
|
||||
kapt "androidx.room:room-compiler:${roomDbLibVersion}"
|
||||
|
||||
implementation "frankiesardo:icepick:${icepickLibVersion}"
|
||||
kapt "frankiesardo:icepick-processor:${icepickLibVersion}"
|
||||
|
||||
debugImplementation "com.squareup.leakcanary:leakcanary-android:${leakCanaryLibVersion}"
|
||||
releaseImplementation "com.squareup.leakcanary:leakcanary-android-no-op:${leakCanaryLibVersion}"
|
||||
|
||||
implementation "com.squareup.okhttp3:okhttp:${okHttpLibVersion}"
|
||||
debugImplementation "com.facebook.stetho:stetho-okhttp3:${stethoLibVersion}"
|
||||
implementation "de.hdodenhof:circleimageview:3.1.0"
|
||||
implementation "com.nostra13.universalimageloader:universal-image-loader:1.9.5"
|
||||
|
||||
implementation "io.noties.markwon:core:${markwonVersion}"
|
||||
implementation "io.noties.markwon:linkify:${markwonVersion}"
|
||||
|
||||
implementation "com.nononsenseapps:filepicker:4.2.1"
|
||||
|
||||
implementation "ch.acra:acra-core:5.5.0"
|
||||
|
||||
implementation "io.reactivex.rxjava2:rxjava:2.2.19"
|
||||
implementation "io.reactivex.rxjava2:rxandroid:2.1.1"
|
||||
implementation "com.jakewharton.rxbinding2:rxbinding:2.2.0"
|
||||
|
||||
implementation "org.ocpsoft.prettytime:prettytime:4.0.5.Final"
|
||||
}
|
||||
|
||||
static String getGitWorkingBranch() {
|
||||
|
||||
@@ -116,4 +116,4 @@ class AppDatabaseTest {
|
||||
testHelper.closeWhenFinished(database)
|
||||
return database
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,107 +0,0 @@
|
||||
package org.schabi.newpipe;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.multidex.MultiDex;
|
||||
|
||||
import com.facebook.stetho.Stetho;
|
||||
import com.facebook.stetho.okhttp3.StethoInterceptor;
|
||||
import com.squareup.leakcanary.AndroidHeapDumper;
|
||||
import com.squareup.leakcanary.DefaultLeakDirectoryProvider;
|
||||
import com.squareup.leakcanary.HeapDumper;
|
||||
import com.squareup.leakcanary.LeakCanary;
|
||||
import com.squareup.leakcanary.LeakDirectoryProvider;
|
||||
import com.squareup.leakcanary.RefWatcher;
|
||||
|
||||
import org.schabi.newpipe.extractor.downloader.Downloader;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import okhttp3.OkHttpClient;
|
||||
|
||||
public class DebugApp extends App {
|
||||
private static final String TAG = DebugApp.class.toString();
|
||||
|
||||
@Override
|
||||
protected void attachBaseContext(final Context base) {
|
||||
super.attachBaseContext(base);
|
||||
MultiDex.install(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
initStetho();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Downloader getDownloader() {
|
||||
DownloaderImpl downloader = DownloaderImpl.init(new OkHttpClient.Builder()
|
||||
.addNetworkInterceptor(new StethoInterceptor()));
|
||||
setCookiesToDownloader(downloader);
|
||||
return downloader;
|
||||
}
|
||||
|
||||
private void initStetho() {
|
||||
// Create an InitializerBuilder
|
||||
Stetho.InitializerBuilder initializerBuilder =
|
||||
Stetho.newInitializerBuilder(this);
|
||||
|
||||
// Enable Chrome DevTools
|
||||
initializerBuilder.enableWebKitInspector(
|
||||
Stetho.defaultInspectorModulesProvider(this)
|
||||
);
|
||||
|
||||
// Enable command line interface
|
||||
initializerBuilder.enableDumpapp(
|
||||
Stetho.defaultDumperPluginsProvider(getApplicationContext())
|
||||
);
|
||||
|
||||
// Use the InitializerBuilder to generate an Initializer
|
||||
Stetho.Initializer initializer = initializerBuilder.build();
|
||||
|
||||
// Initialize Stetho with the Initializer
|
||||
Stetho.initialize(initializer);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isDisposedRxExceptionsReported() {
|
||||
return PreferenceManager.getDefaultSharedPreferences(this)
|
||||
.getBoolean(getString(R.string.allow_disposed_exceptions_key), false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RefWatcher installLeakCanary() {
|
||||
return LeakCanary.refWatcher(this)
|
||||
.heapDumper(new ToggleableHeapDumper(this))
|
||||
// give each object 10 seconds to be gc'ed, before leak canary gets nosy on it
|
||||
.watchDelay(10, TimeUnit.SECONDS)
|
||||
.buildAndInstall();
|
||||
}
|
||||
|
||||
public static class ToggleableHeapDumper implements HeapDumper {
|
||||
private final HeapDumper dumper;
|
||||
private final SharedPreferences preferences;
|
||||
private final String dumpingAllowanceKey;
|
||||
|
||||
ToggleableHeapDumper(@NonNull final Context context) {
|
||||
LeakDirectoryProvider leakDirectoryProvider = new DefaultLeakDirectoryProvider(context);
|
||||
this.dumper = new AndroidHeapDumper(context, leakDirectoryProvider);
|
||||
this.preferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
this.dumpingAllowanceKey = context.getString(R.string.allow_heap_dumping_key);
|
||||
}
|
||||
|
||||
private boolean isDumpingAllowed() {
|
||||
return preferences.getBoolean(dumpingAllowanceKey, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public File dumpHeap() {
|
||||
return isDumpingAllowed() ? dumper.dumpHeap() : HeapDumper.RETRY_LATER;
|
||||
}
|
||||
}
|
||||
}
|
||||
59
app/src/debug/java/org/schabi/newpipe/DebugApp.kt
Normal file
59
app/src/debug/java/org/schabi/newpipe/DebugApp.kt
Normal file
@@ -0,0 +1,59 @@
|
||||
package org.schabi.newpipe
|
||||
|
||||
import android.content.Context
|
||||
import androidx.multidex.MultiDex
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.facebook.stetho.Stetho
|
||||
import com.facebook.stetho.okhttp3.StethoInterceptor
|
||||
import leakcanary.AppWatcher
|
||||
import leakcanary.LeakCanary
|
||||
import okhttp3.OkHttpClient
|
||||
import org.schabi.newpipe.extractor.downloader.Downloader
|
||||
|
||||
class DebugApp : App() {
|
||||
override fun attachBaseContext(base: Context) {
|
||||
super.attachBaseContext(base)
|
||||
MultiDex.install(this)
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
initStetho()
|
||||
|
||||
// Give each object 10 seconds to be GC'ed, before LeakCanary gets nosy on it
|
||||
AppWatcher.config = AppWatcher.config.copy(watchDurationMillis = 10000)
|
||||
LeakCanary.config = LeakCanary.config.copy(dumpHeap = PreferenceManager
|
||||
.getDefaultSharedPreferences(this).getBoolean(getString(
|
||||
R.string.allow_heap_dumping_key), false))
|
||||
}
|
||||
|
||||
override fun getDownloader(): Downloader {
|
||||
val downloader = DownloaderImpl.init(OkHttpClient.Builder()
|
||||
.addNetworkInterceptor(StethoInterceptor()))
|
||||
setCookiesToDownloader(downloader)
|
||||
return downloader
|
||||
}
|
||||
|
||||
private fun initStetho() {
|
||||
// Create an InitializerBuilder
|
||||
val initializerBuilder = Stetho.newInitializerBuilder(this)
|
||||
|
||||
// Enable Chrome DevTools
|
||||
initializerBuilder.enableWebKitInspector(Stetho.defaultInspectorModulesProvider(this))
|
||||
|
||||
// Enable command line interface
|
||||
initializerBuilder.enableDumpapp(
|
||||
Stetho.defaultDumperPluginsProvider(applicationContext))
|
||||
|
||||
// Use the InitializerBuilder to generate an Initializer
|
||||
val initializer = initializerBuilder.build()
|
||||
|
||||
// Initialize Stetho with the Initializer
|
||||
Stetho.initialize(initializer)
|
||||
}
|
||||
|
||||
override fun isDisposedRxExceptionsReported(): Boolean {
|
||||
return PreferenceManager.getDefaultSharedPreferences(this)
|
||||
.getBoolean(getString(R.string.allow_disposed_exceptions_key), false)
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="org.schabi.newpipe">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
|
||||
<uses-feature android:name="android.hardware.touchscreen" android:required="false"/>
|
||||
<uses-feature
|
||||
android:name="android.hardware.touchscreen"
|
||||
android:required="false" />
|
||||
|
||||
<application
|
||||
android:banner="@mipmap/newpipe_tv_banner"
|
||||
android:name=".App"
|
||||
android:allowBackup="true"
|
||||
android:banner="@mipmap/newpipe_tv_banner"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:logo="@mipmap/ic_launcher"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:theme="@style/OpeningTheme"
|
||||
tools:ignore="AllowBackup">
|
||||
<activity
|
||||
@@ -27,13 +29,14 @@
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTask">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
<category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<receiver android:name="androidx.media.session.MediaButtonReceiver" >
|
||||
<receiver android:name="androidx.media.session.MediaButtonReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MEDIA_BUTTON" />
|
||||
</intent-filter>
|
||||
@@ -49,36 +52,36 @@
|
||||
|
||||
<activity
|
||||
android:name=".player.BackgroundPlayerActivity"
|
||||
android:launchMode="singleTask"
|
||||
android:label="@string/title_activity_background_player"/>
|
||||
android:label="@string/title_activity_background_player"
|
||||
android:launchMode="singleTask" />
|
||||
|
||||
<activity
|
||||
android:name=".player.PopupVideoPlayerActivity"
|
||||
android:launchMode="singleTask"
|
||||
android:label="@string/title_activity_popup_player"/>
|
||||
android:label="@string/title_activity_popup_player"
|
||||
android:launchMode="singleTask" />
|
||||
|
||||
<service
|
||||
android:name=".player.PopupVideoPlayer"
|
||||
android:exported="false"/>
|
||||
android:exported="false" />
|
||||
|
||||
<activity
|
||||
android:name=".player.MainVideoPlayer"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTask"
|
||||
android:theme="@style/VideoPlayerTheme"/>
|
||||
android:theme="@style/VideoPlayerTheme" />
|
||||
|
||||
<activity
|
||||
android:name=".settings.SettingsActivity"
|
||||
android:label="@string/settings"/>
|
||||
android:label="@string/settings" />
|
||||
|
||||
<activity
|
||||
android:name=".about.AboutActivity"
|
||||
android:label="@string/title_activity_about"/>
|
||||
android:label="@string/title_activity_about" />
|
||||
|
||||
<service android:name=".local.subscription.services.SubscriptionsImportService"/>
|
||||
<service android:name=".local.subscription.services.SubscriptionsExportService"/>
|
||||
<service android:name=".local.feed.service.FeedLoadService"/>
|
||||
<service android:name=".local.subscription.services.SubscriptionsImportService" />
|
||||
<service android:name=".local.subscription.services.SubscriptionsExportService" />
|
||||
<service android:name=".local.feed.service.FeedLoadService" />
|
||||
|
||||
<activity
|
||||
android:name=".PanicResponderActivity"
|
||||
@@ -86,25 +89,25 @@
|
||||
android:noHistory="true"
|
||||
android:theme="@android:style/Theme.NoDisplay">
|
||||
<intent-filter>
|
||||
<action android:name="info.guardianproject.panic.action.TRIGGER"/>
|
||||
<action android:name="info.guardianproject.panic.action.TRIGGER" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".ExitActivity"
|
||||
android:label="@string/general_error"
|
||||
android:theme="@android:style/Theme.NoDisplay"/>
|
||||
<activity android:name=".report.ErrorActivity"/>
|
||||
android:theme="@android:style/Theme.NoDisplay" />
|
||||
<activity android:name=".report.ErrorActivity" />
|
||||
|
||||
<!-- giga get related -->
|
||||
<activity
|
||||
android:name=".download.DownloadActivity"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTask"/>
|
||||
android:launchMode="singleTask" />
|
||||
|
||||
<service android:name="us.shandian.giga.service.DownloadManagerService"/>
|
||||
<service android:name="us.shandian.giga.service.DownloadManagerService" />
|
||||
|
||||
<activity
|
||||
android:name=".util.FilePickerActivityHelper"
|
||||
@@ -118,7 +121,7 @@
|
||||
|
||||
<activity
|
||||
android:name=".ReCaptchaActivity"
|
||||
android:label="@string/recaptcha"/>
|
||||
android:label="@string/recaptcha" />
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
@@ -127,7 +130,7 @@
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/nnf_provider_paths"/>
|
||||
android:resource="@xml/nnf_provider_paths" />
|
||||
</provider>
|
||||
|
||||
<activity
|
||||
@@ -139,165 +142,197 @@
|
||||
|
||||
<!-- Youtube filter -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
|
||||
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
|
||||
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="http"/>
|
||||
<data android:scheme="https"/>
|
||||
<data android:host="youtube.com"/>
|
||||
<data android:host="m.youtube.com"/>
|
||||
<data android:host="www.youtube.com"/>
|
||||
<data android:host="music.youtube.com"/>
|
||||
<data android:scheme="http" />
|
||||
<data android:scheme="https" />
|
||||
<data android:host="youtube.com" />
|
||||
<data android:host="m.youtube.com" />
|
||||
<data android:host="www.youtube.com" />
|
||||
<data android:host="music.youtube.com" />
|
||||
<!-- video prefix -->
|
||||
<data android:pathPrefix="/v/"/>
|
||||
<data android:pathPrefix="/embed/"/>
|
||||
<data android:pathPrefix="/watch"/>
|
||||
<data android:pathPrefix="/attribution_link"/>
|
||||
<data android:pathPrefix="/v/" />
|
||||
<data android:pathPrefix="/embed/" />
|
||||
<data android:pathPrefix="/watch" />
|
||||
<data android:pathPrefix="/attribution_link" />
|
||||
<!-- channel prefix -->
|
||||
<data android:pathPrefix="/channel/"/>
|
||||
<data android:pathPrefix="/user/"/>
|
||||
<data android:pathPrefix="/c/"/>
|
||||
<data android:pathPrefix="/channel/" />
|
||||
<data android:pathPrefix="/user/" />
|
||||
<data android:pathPrefix="/c/" />
|
||||
<!-- playlist prefix -->
|
||||
<data android:pathPrefix="/playlist"/>
|
||||
<data android:pathPrefix="/playlist" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
|
||||
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
|
||||
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="http"/>
|
||||
<data android:scheme="https"/>
|
||||
<data android:host="youtu.be"/>
|
||||
<data android:pathPrefix="/"/>
|
||||
<data android:scheme="http" />
|
||||
<data android:scheme="https" />
|
||||
<data android:host="youtu.be" />
|
||||
<data android:pathPrefix="/" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
|
||||
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
|
||||
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="http"/>
|
||||
<data android:scheme="https"/>
|
||||
<data android:host="www.youtube-nocookie.com"/>
|
||||
<data android:pathPrefix="/embed/"/>
|
||||
<data android:scheme="http" />
|
||||
<data android:scheme="https" />
|
||||
<data android:host="www.youtube-nocookie.com" />
|
||||
<data android:pathPrefix="/embed/" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
|
||||
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
|
||||
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="vnd.youtube"/>
|
||||
<data android:scheme="vnd.youtube.launch"/>
|
||||
<data android:scheme="vnd.youtube" />
|
||||
<data android:scheme="vnd.youtube.launch" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- Hooktube filter -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
|
||||
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
|
||||
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="http"/>
|
||||
<data android:scheme="https"/>
|
||||
<data android:host="hooktube.com"/>
|
||||
<data android:host="*.hooktube.com"/>
|
||||
<data android:scheme="http" />
|
||||
<data android:scheme="https" />
|
||||
<data android:host="hooktube.com" />
|
||||
<data android:host="*.hooktube.com" />
|
||||
<!-- video prefix -->
|
||||
<data android:pathPrefix="/v/"/>
|
||||
<data android:pathPrefix="/embed/"/>
|
||||
<data android:pathPrefix="/watch"/>
|
||||
<data android:pathPrefix="/v/" />
|
||||
<data android:pathPrefix="/embed/" />
|
||||
<data android:pathPrefix="/watch" />
|
||||
<!-- channel prefix -->
|
||||
<data android:pathPrefix="/channel/"/>
|
||||
<data android:pathPrefix="/user/"/>
|
||||
<data android:pathPrefix="/channel/" />
|
||||
<data android:pathPrefix="/user/" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- Invidious filter -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
|
||||
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
|
||||
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="http"/>
|
||||
<data android:scheme="https"/>
|
||||
<data android:host="invidio.us"/>
|
||||
<data android:host="dev.invidio.us"/>
|
||||
<data android:host="www.invidio.us"/>
|
||||
<data android:host="invidious.snopyta.org"/>
|
||||
<data android:host="de.invidious.snopyta.org"/>
|
||||
<data android:host="fi.invidious.snopyta.org"/>
|
||||
<data android:host="vid.wxzm.sx"/>
|
||||
<data android:host="invidious.kabi.tk"/>
|
||||
<data android:host="invidiou.sh"/>
|
||||
<data android:host="www.invidiou.sh"/>
|
||||
<data android:host="no.invidiou.sh"/>
|
||||
<data android:host="invidious.enkirton.net"/>
|
||||
<data android:host="tube.poal.co"/>
|
||||
<data android:host="invidious.13ad.de"/>
|
||||
<data android:host="yt.elukerio.org"/>
|
||||
<data android:pathPrefix="/"/>
|
||||
<data android:scheme="http" />
|
||||
<data android:scheme="https" />
|
||||
<data android:host="invidio.us" />
|
||||
<data android:host="dev.invidio.us" />
|
||||
<data android:host="www.invidio.us" />
|
||||
<data android:host="invidious.snopyta.org" />
|
||||
<data android:host="fi.invidious.snopyta.org" />
|
||||
<data android:host="yewtu.be" />
|
||||
<data android:host="invidious.ggc-project.de" />
|
||||
<data android:host="yt.maisputain.ovh" />
|
||||
<data android:host="invidious.13ad.de" />
|
||||
<data android:host="invidious.toot.koeln" />
|
||||
<data android:host="invidious.fdn.fr" />
|
||||
<data android:host="watch.nettohikari.com" />
|
||||
<data android:host="invidious.snwmds.net" />
|
||||
<data android:host="invidious.snwmds.org" />
|
||||
<data android:host="invidious.snwmds.com" />
|
||||
<data android:host="invidious.sunsetravens.com" />
|
||||
<data android:host="invidious.gachirangers.com" />
|
||||
<data android:pathPrefix="/" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- Soundcloud filter -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
|
||||
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
|
||||
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="http"/>
|
||||
<data android:scheme="https"/>
|
||||
<data android:host="soundcloud.com"/>
|
||||
<data android:host="m.soundcloud.com"/>
|
||||
<data android:host="www.soundcloud.com"/>
|
||||
<data android:pathPrefix="/"/>
|
||||
<data android:scheme="http" />
|
||||
<data android:scheme="https" />
|
||||
<data android:host="soundcloud.com" />
|
||||
<data android:host="m.soundcloud.com" />
|
||||
<data android:host="www.soundcloud.com" />
|
||||
<data android:pathPrefix="/" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- Share filter -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<data android:mimeType="text/plain"/>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="text/plain" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- MediaCCC filter -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
|
||||
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
|
||||
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="http"/>
|
||||
<data android:scheme="https"/>
|
||||
<data android:host="media.ccc.de"/>
|
||||
<data android:scheme="http" />
|
||||
<data android:scheme="https" />
|
||||
<data android:host="media.ccc.de" />
|
||||
<!-- video prefix -->
|
||||
<data android:pathPrefix="/v/"/>
|
||||
<data android:pathPrefix="/v/" />
|
||||
<!-- channel prefix-->
|
||||
<data android:pathPrefix="/c/"/>
|
||||
<data android:pathPrefix="/b/"/>
|
||||
<data android:pathPrefix="/c/" />
|
||||
<data android:pathPrefix="/b/" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- PeerTube filter -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
|
||||
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="http" />
|
||||
<data android:scheme="https" />
|
||||
|
||||
<data android:host="framatube.org" />
|
||||
<data android:host="media.assassinate-you.net" />
|
||||
<data android:host="peertube.co.uk" />
|
||||
<data android:host="peertube.cpy.re" />
|
||||
<data android:host="peertube.mastodon.host" />
|
||||
<data android:host="peertube.fr" />
|
||||
<data android:host="peertube.live" />
|
||||
<data android:host="peertube.video" />
|
||||
<data android:host="tube.privacytools.io" />
|
||||
<data android:host="video.ploud.fr" />
|
||||
<data android:host="video.lqdn.fr" />
|
||||
<data android:host="skeptikon.fr" />
|
||||
|
||||
<data android:pathPrefix="/videos/" /> <!-- it contains playlists -->
|
||||
<data android:pathPrefix="/accounts/" />
|
||||
<data android:pathPrefix="/video-channels/" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<service
|
||||
android:name=".RouterActivity$FetcherService"
|
||||
android:exported="false"/>
|
||||
android:exported="false" />
|
||||
</application>
|
||||
</manifest>
|
||||
|
||||
@@ -9,19 +9,16 @@ import android.content.SharedPreferences;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import com.nostra13.universalimageloader.cache.memory.impl.LRULimitedMemoryCache;
|
||||
import com.nostra13.universalimageloader.core.ImageLoader;
|
||||
import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
|
||||
import com.squareup.leakcanary.LeakCanary;
|
||||
import com.squareup.leakcanary.RefWatcher;
|
||||
|
||||
import org.acra.ACRA;
|
||||
import org.acra.config.ACRAConfiguration;
|
||||
import org.acra.config.ACRAConfigurationException;
|
||||
import org.acra.config.ConfigurationBuilder;
|
||||
import org.acra.config.CoreConfiguration;
|
||||
import org.acra.config.CoreConfigurationBuilder;
|
||||
import org.acra.sender.ReportSenderFactory;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.downloader.Downloader;
|
||||
@@ -72,13 +69,6 @@ public class App extends Application {
|
||||
private static final Class<? extends ReportSenderFactory>[]
|
||||
REPORT_SENDER_FACTORY_CLASSES = new Class[]{AcraReportSenderFactory.class};
|
||||
private static App app;
|
||||
private RefWatcher refWatcher;
|
||||
|
||||
@Nullable
|
||||
public static RefWatcher getRefWatcher(final Context context) {
|
||||
final App application = (App) context.getApplicationContext();
|
||||
return application.refWatcher;
|
||||
}
|
||||
|
||||
public static App getApp() {
|
||||
return app;
|
||||
@@ -95,13 +85,6 @@ public class App extends Application {
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
if (LeakCanary.isInAnalyzerProcess(this)) {
|
||||
// This process is dedicated to LeakCanary for heap analysis.
|
||||
// You should not init your app in this process.
|
||||
return;
|
||||
}
|
||||
refWatcher = installLeakCanary();
|
||||
|
||||
app = this;
|
||||
|
||||
// Initialize settings first because others inits can use its values
|
||||
@@ -136,7 +119,8 @@ public class App extends Application {
|
||||
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(
|
||||
getApplicationContext());
|
||||
final String key = getApplicationContext().getString(R.string.recaptcha_cookies_key);
|
||||
downloader.setCookies(prefs.getString(key, ""));
|
||||
downloader.setCookie(ReCaptchaActivity.RECAPTCHA_COOKIES_KEY, prefs.getString(key, ""));
|
||||
downloader.updateYoutubeRestrictedModeCookies(getApplicationContext());
|
||||
}
|
||||
|
||||
private void configureRxJavaErrorHandler() {
|
||||
@@ -218,7 +202,7 @@ public class App extends Application {
|
||||
|
||||
private void initACRA() {
|
||||
try {
|
||||
final ACRAConfiguration acraConfig = new ConfigurationBuilder(this)
|
||||
final CoreConfiguration acraConfig = new CoreConfigurationBuilder(this)
|
||||
.setReportSenderFactoryClasses(REPORT_SENDER_FACTORY_CLASSES)
|
||||
.setBuildConfigClass(BuildConfig.class)
|
||||
.build();
|
||||
@@ -279,10 +263,6 @@ public class App extends Application {
|
||||
appUpdateNotificationManager.createNotificationChannel(appUpdateChannel);
|
||||
}
|
||||
|
||||
protected RefWatcher installLeakCanary() {
|
||||
return RefWatcher.DISABLED;
|
||||
}
|
||||
|
||||
protected boolean isDisposedRxExceptionsReported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -11,10 +11,10 @@ import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
|
||||
import com.nostra13.universalimageloader.core.ImageLoader;
|
||||
import com.squareup.leakcanary.RefWatcher;
|
||||
|
||||
import icepick.Icepick;
|
||||
import icepick.State;
|
||||
import leakcanary.AppWatcher;
|
||||
|
||||
public abstract class BaseFragment extends Fragment {
|
||||
public static final ImageLoader IMAGE_LOADER = ImageLoader.getInstance();
|
||||
@@ -78,16 +78,14 @@ public abstract class BaseFragment extends Fragment {
|
||||
Icepick.saveInstanceState(this, outState);
|
||||
}
|
||||
|
||||
protected void onRestoreInstanceState(@NonNull final Bundle savedInstanceState) { }
|
||||
protected void onRestoreInstanceState(@NonNull final Bundle savedInstanceState) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
RefWatcher refWatcher = App.getRefWatcher(getActivity());
|
||||
if (refWatcher != null) {
|
||||
refWatcher.watch(this);
|
||||
}
|
||||
AppWatcher.INSTANCE.getObjectWatcher().watch(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -100,9 +98,11 @@ public abstract class BaseFragment extends Fragment {
|
||||
// Init
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
protected void initViews(final View rootView, final Bundle savedInstanceState) { }
|
||||
protected void initViews(final View rootView, final Bundle savedInstanceState) {
|
||||
}
|
||||
|
||||
protected void initListeners() { }
|
||||
protected void initListeners() {
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Utils
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
package org.schabi.newpipe;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -10,6 +11,8 @@ import org.schabi.newpipe.extractor.downloader.Downloader;
|
||||
import org.schabi.newpipe.extractor.downloader.Request;
|
||||
import org.schabi.newpipe.extractor.downloader.Response;
|
||||
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
|
||||
import org.schabi.newpipe.util.CookieUtils;
|
||||
import org.schabi.newpipe.util.InfoCache;
|
||||
import org.schabi.newpipe.util.TLSSocketFactoryCompat;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -20,6 +23,7 @@ import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -40,9 +44,13 @@ import static org.schabi.newpipe.MainActivity.DEBUG;
|
||||
public final class DownloaderImpl extends Downloader {
|
||||
public static final String USER_AGENT
|
||||
= "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:68.0) Gecko/20100101 Firefox/68.0";
|
||||
public static final String YOUTUBE_RESTRICTED_MODE_COOKIE_KEY
|
||||
= "youtube_restricted_mode_key";
|
||||
public static final String YOUTUBE_RESTRICTED_MODE_COOKIE = "PREF=f2=8000000";
|
||||
public static final String YOUTUBE_DOMAIN = "youtube.com";
|
||||
|
||||
private static DownloaderImpl instance;
|
||||
private String mCookies;
|
||||
private Map<String, String> mCookies;
|
||||
private OkHttpClient client;
|
||||
|
||||
private DownloaderImpl(final OkHttpClient.Builder builder) {
|
||||
@@ -54,6 +62,7 @@ public final class DownloaderImpl extends Downloader {
|
||||
// .cache(new Cache(new File(context.getExternalCacheDir(), "okhttp"),
|
||||
// 16 * 1024 * 1024))
|
||||
.build();
|
||||
this.mCookies = new HashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -121,12 +130,50 @@ public final class DownloaderImpl extends Downloader {
|
||||
}
|
||||
}
|
||||
|
||||
public String getCookies() {
|
||||
return mCookies;
|
||||
public String getCookies(final String url) {
|
||||
List<String> resultCookies = new ArrayList<>();
|
||||
if (url.contains(YOUTUBE_DOMAIN)) {
|
||||
String youtubeCookie = getCookie(YOUTUBE_RESTRICTED_MODE_COOKIE_KEY);
|
||||
if (youtubeCookie != null) {
|
||||
resultCookies.add(youtubeCookie);
|
||||
}
|
||||
}
|
||||
// Recaptcha cookie is always added TODO: not sure if this is necessary
|
||||
String recaptchaCookie = getCookie(ReCaptchaActivity.RECAPTCHA_COOKIES_KEY);
|
||||
if (recaptchaCookie != null) {
|
||||
resultCookies.add(recaptchaCookie);
|
||||
}
|
||||
return CookieUtils.concatCookies(resultCookies);
|
||||
}
|
||||
|
||||
public void setCookies(final String cookies) {
|
||||
mCookies = cookies;
|
||||
public String getCookie(final String key) {
|
||||
return mCookies.get(key);
|
||||
}
|
||||
|
||||
public void setCookie(final String key, final String cookie) {
|
||||
mCookies.put(key, cookie);
|
||||
}
|
||||
|
||||
public void removeCookie(final String key) {
|
||||
mCookies.remove(key);
|
||||
}
|
||||
|
||||
public void updateYoutubeRestrictedModeCookies(final Context context) {
|
||||
String restrictedModeEnabledKey =
|
||||
context.getString(R.string.youtube_restricted_mode_enabled);
|
||||
boolean restrictedModeEnabled = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
.getBoolean(restrictedModeEnabledKey, false);
|
||||
updateYoutubeRestrictedModeCookies(restrictedModeEnabled);
|
||||
}
|
||||
|
||||
public void updateYoutubeRestrictedModeCookies(final boolean youtubeRestrictedModeEnabled) {
|
||||
if (youtubeRestrictedModeEnabled) {
|
||||
setCookie(YOUTUBE_RESTRICTED_MODE_COOKIE_KEY,
|
||||
YOUTUBE_RESTRICTED_MODE_COOKIE);
|
||||
} else {
|
||||
removeCookie(YOUTUBE_RESTRICTED_MODE_COOKIE_KEY);
|
||||
}
|
||||
InfoCache.getInstance().clearCache();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -152,8 +199,9 @@ public final class DownloaderImpl extends Downloader {
|
||||
.method("GET", null).url(siteUrl)
|
||||
.addHeader("User-Agent", USER_AGENT);
|
||||
|
||||
if (!TextUtils.isEmpty(mCookies)) {
|
||||
requestBuilder.addHeader("Cookie", mCookies);
|
||||
String cookies = getCookies(siteUrl);
|
||||
if (!cookies.isEmpty()) {
|
||||
requestBuilder.addHeader("Cookie", cookies);
|
||||
}
|
||||
|
||||
final okhttp3.Request request = requestBuilder.build();
|
||||
@@ -192,8 +240,9 @@ public final class DownloaderImpl extends Downloader {
|
||||
.method(httpMethod, requestBody).url(url)
|
||||
.addHeader("User-Agent", USER_AGENT);
|
||||
|
||||
if (!TextUtils.isEmpty(mCookies)) {
|
||||
requestBuilder.addHeader("Cookie", mCookies);
|
||||
String cookies = getCookies(url);
|
||||
if (!cookies.isEmpty()) {
|
||||
requestBuilder.addHeader("Cookie", cookies);
|
||||
}
|
||||
|
||||
for (Map.Entry<String, List<String>> pair : headers.entrySet()) {
|
||||
|
||||
@@ -34,8 +34,6 @@ import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
@@ -127,12 +125,6 @@ public class MainActivity extends AppCompatActivity {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
Window w = getWindow();
|
||||
w.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS,
|
||||
WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
|
||||
}
|
||||
|
||||
if (getSupportFragmentManager() != null
|
||||
&& getSupportFragmentManager().getBackStackEntryCount() == 0) {
|
||||
initFragments();
|
||||
@@ -165,7 +157,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
drawerItems.getMenu()
|
||||
.add(R.id.menu_tabs_group, kioskId, 0, KioskTranslator
|
||||
.getTranslatedKioskName(ks, this))
|
||||
.setIcon(KioskTranslator.getKioskIcons(ks, this));
|
||||
.setIcon(KioskTranslator.getKioskIcon(ks, this));
|
||||
kioskId++;
|
||||
}
|
||||
|
||||
@@ -175,24 +167,24 @@ public class MainActivity extends AppCompatActivity {
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_channel));
|
||||
drawerItems.getMenu()
|
||||
.add(R.id.menu_tabs_group, ITEM_ID_FEED, ORDER, R.string.fragment_feed_title)
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.rss));
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_rss));
|
||||
drawerItems.getMenu()
|
||||
.add(R.id.menu_tabs_group, ITEM_ID_BOOKMARKS, ORDER, R.string.tab_bookmarks)
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_bookmark));
|
||||
drawerItems.getMenu()
|
||||
.add(R.id.menu_tabs_group, ITEM_ID_DOWNLOADS, ORDER, R.string.downloads)
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.download));
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_file_download));
|
||||
drawerItems.getMenu()
|
||||
.add(R.id.menu_tabs_group, ITEM_ID_HISTORY, ORDER, R.string.action_history)
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.history));
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_history));
|
||||
|
||||
//Settings and About
|
||||
drawerItems.getMenu()
|
||||
.add(R.id.menu_options_about_group, ITEM_ID_SETTINGS, ORDER, R.string.settings)
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.settings));
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_settings));
|
||||
drawerItems.getMenu()
|
||||
.add(R.id.menu_options_about_group, ITEM_ID_ABOUT, ORDER, R.string.tab_about)
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.info));
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_info_outline));
|
||||
|
||||
toggle = new ActionBarDrawerToggle(this, drawer, toolbar, R.string.drawer_open,
|
||||
R.string.drawer_close);
|
||||
@@ -420,7 +412,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
drawerItems.getMenu()
|
||||
.add(R.id.menu_tabs_group, kioskId, ORDER,
|
||||
KioskTranslator.getTranslatedKioskName(ks, this))
|
||||
.setIcon(KioskTranslator.getKioskIcons(ks, this));
|
||||
.setIcon(KioskTranslator.getKioskIcon(ks, this));
|
||||
kioskId++;
|
||||
}
|
||||
|
||||
@@ -429,24 +421,24 @@ public class MainActivity extends AppCompatActivity {
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_channel));
|
||||
drawerItems.getMenu()
|
||||
.add(R.id.menu_tabs_group, ITEM_ID_FEED, ORDER, R.string.fragment_feed_title)
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.rss));
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_rss));
|
||||
drawerItems.getMenu()
|
||||
.add(R.id.menu_tabs_group, ITEM_ID_BOOKMARKS, ORDER, R.string.tab_bookmarks)
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_bookmark));
|
||||
drawerItems.getMenu()
|
||||
.add(R.id.menu_tabs_group, ITEM_ID_DOWNLOADS, ORDER, R.string.downloads)
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.download));
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_file_download));
|
||||
drawerItems.getMenu()
|
||||
.add(R.id.menu_tabs_group, ITEM_ID_HISTORY, ORDER, R.string.action_history)
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.history));
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_history));
|
||||
|
||||
//Settings and About
|
||||
drawerItems.getMenu()
|
||||
.add(R.id.menu_options_about_group, ITEM_ID_SETTINGS, ORDER, R.string.settings)
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.settings));
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_settings));
|
||||
drawerItems.getMenu()
|
||||
.add(R.id.menu_options_about_group, ITEM_ID_ABOUT, ORDER, R.string.tab_about)
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.info));
|
||||
.setIcon(ThemeHelper.resolveResourceIdFromAttr(this, R.attr.ic_info_outline));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -51,6 +51,7 @@ public class ReCaptchaActivity extends AppCompatActivity {
|
||||
public static final String RECAPTCHA_URL_EXTRA = "recaptcha_url_extra";
|
||||
public static final String TAG = ReCaptchaActivity.class.toString();
|
||||
public static final String YT_URL = "https://www.youtube.com";
|
||||
public static final String RECAPTCHA_COOKIES_KEY = "recaptcha_cookies";
|
||||
|
||||
private WebView webView;
|
||||
private String foundCookies = "";
|
||||
@@ -168,7 +169,7 @@ public class ReCaptchaActivity extends AppCompatActivity {
|
||||
prefs.edit().putString(key, foundCookies).apply();
|
||||
|
||||
// give cookies to Downloader class
|
||||
DownloaderImpl.getInstance().setCookies(foundCookies);
|
||||
DownloaderImpl.getInstance().setCookie(RECAPTCHA_COOKIES_KEY, foundCookies);
|
||||
setResult(RESULT_OK);
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.content.res.AppCompatResources;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
|
||||
@@ -43,15 +44,15 @@ import org.schabi.newpipe.player.playqueue.PlayQueue;
|
||||
import org.schabi.newpipe.player.playqueue.PlaylistPlayQueue;
|
||||
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
import org.schabi.newpipe.util.AndroidTvUtils;
|
||||
import org.schabi.newpipe.util.Constants;
|
||||
import org.schabi.newpipe.util.ExtractorHelper;
|
||||
import org.schabi.newpipe.util.AndroidTvUtils;
|
||||
import org.schabi.newpipe.util.ListHelper;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.PermissionHelper;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
import org.schabi.newpipe.views.FocusOverlayView;
|
||||
import org.schabi.newpipe.util.urlfinder.UrlFinder;
|
||||
import org.schabi.newpipe.views.FocusOverlayView;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
@@ -313,7 +314,9 @@ public class RouterActivity extends AppCompatActivity {
|
||||
final RadioButton radioButton
|
||||
= (RadioButton) inflater.inflate(R.layout.list_radio_icon_item, null);
|
||||
radioButton.setText(item.description);
|
||||
radioButton.setCompoundDrawablesWithIntrinsicBounds(item.icon, 0, 0, 0);
|
||||
radioButton.setCompoundDrawablesWithIntrinsicBounds(
|
||||
AppCompatResources.getDrawable(getApplicationContext(), item.icon),
|
||||
null, null, null);
|
||||
radioButton.setChecked(false);
|
||||
radioButton.setId(id++);
|
||||
radioButton.setLayoutParams(new RadioGroup.LayoutParams(
|
||||
@@ -366,26 +369,26 @@ public class RouterActivity extends AppCompatActivity {
|
||||
|
||||
returnList.add(new AdapterChoiceItem(getString(R.string.show_info_key),
|
||||
getString(R.string.show_info),
|
||||
resolveResourceIdFromAttr(context, R.attr.info)));
|
||||
resolveResourceIdFromAttr(context, R.attr.ic_info_outline)));
|
||||
|
||||
if (capabilities.contains(VIDEO) && !(isExtVideoEnabled && linkType != LinkType.STREAM)) {
|
||||
returnList.add(new AdapterChoiceItem(getString(R.string.video_player_key),
|
||||
getString(R.string.video_player),
|
||||
resolveResourceIdFromAttr(context, R.attr.play)));
|
||||
resolveResourceIdFromAttr(context, R.attr.ic_play_arrow)));
|
||||
returnList.add(new AdapterChoiceItem(getString(R.string.popup_player_key),
|
||||
getString(R.string.popup_player),
|
||||
resolveResourceIdFromAttr(context, R.attr.popup)));
|
||||
resolveResourceIdFromAttr(context, R.attr.ic_popup)));
|
||||
}
|
||||
|
||||
if (capabilities.contains(AUDIO) && !(isExtAudioEnabled && linkType != LinkType.STREAM)) {
|
||||
returnList.add(new AdapterChoiceItem(getString(R.string.background_player_key),
|
||||
getString(R.string.background_player),
|
||||
resolveResourceIdFromAttr(context, R.attr.audio)));
|
||||
resolveResourceIdFromAttr(context, R.attr.ic_headset)));
|
||||
}
|
||||
|
||||
returnList.add(new AdapterChoiceItem(getString(R.string.download_key),
|
||||
getString(R.string.download),
|
||||
resolveResourceIdFromAttr(context, R.attr.download)));
|
||||
resolveResourceIdFromAttr(context, R.attr.ic_file_download)));
|
||||
|
||||
return returnList;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package org.schabi.newpipe.about;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
@@ -26,6 +24,7 @@ import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
|
||||
import static org.schabi.newpipe.util.ShareUtils.openUrlInBrowser;
|
||||
|
||||
public class AboutActivity extends AppCompatActivity {
|
||||
/**
|
||||
@@ -143,28 +142,23 @@ public class AboutActivity extends AppCompatActivity {
|
||||
|
||||
View githubLink = rootView.findViewById(R.id.github_link);
|
||||
githubLink.setOnClickListener(nv ->
|
||||
openWebsite(context.getString(R.string.github_url), context));
|
||||
openUrlInBrowser(context, context.getString(R.string.github_url)));
|
||||
|
||||
View donationLink = rootView.findViewById(R.id.donation_link);
|
||||
donationLink.setOnClickListener(v ->
|
||||
openWebsite(context.getString(R.string.donation_url), context));
|
||||
openUrlInBrowser(context, context.getString(R.string.donation_url)));
|
||||
|
||||
View websiteLink = rootView.findViewById(R.id.website_link);
|
||||
websiteLink.setOnClickListener(nv ->
|
||||
openWebsite(context.getString(R.string.website_url), context));
|
||||
openUrlInBrowser(context, context.getString(R.string.website_url)));
|
||||
|
||||
View privacyPolicyLink = rootView.findViewById(R.id.privacy_policy_link);
|
||||
privacyPolicyLink.setOnClickListener(v ->
|
||||
openWebsite(context.getString(R.string.privacy_policy_url), context));
|
||||
openUrlInBrowser(context, context.getString(R.string.privacy_policy_url)));
|
||||
|
||||
return rootView;
|
||||
}
|
||||
|
||||
private void openWebsite(final String url, final Context context) {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
|
||||
context.startActivity(intent);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,8 +2,6 @@ package org.schabi.newpipe.about;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -17,9 +15,9 @@ import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.util.ShareUtils;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
|
||||
/**
|
||||
* Fragment containing the software licenses.
|
||||
@@ -27,7 +25,7 @@ import java.util.Comparator;
|
||||
public class LicenseFragment extends Fragment {
|
||||
private static final String ARG_COMPONENTS = "components";
|
||||
private SoftwareComponent[] softwareComponents;
|
||||
private SoftwareComponent mComponentForContextMenu;
|
||||
private SoftwareComponent componentForContextMenu;
|
||||
|
||||
public static LicenseFragment newInstance(final SoftwareComponent[] softwareComponents) {
|
||||
if (softwareComponents == null) {
|
||||
@@ -46,7 +44,7 @@ public class LicenseFragment extends Fragment {
|
||||
* @param context the context to use
|
||||
* @param license the license to show
|
||||
*/
|
||||
public static void showLicense(final Context context, final License license) {
|
||||
private static void showLicense(final Context context, final License license) {
|
||||
new LicenseFragmentHelper((Activity) context).execute(license);
|
||||
}
|
||||
|
||||
@@ -57,45 +55,34 @@ public class LicenseFragment extends Fragment {
|
||||
.getParcelableArray(ARG_COMPONENTS);
|
||||
|
||||
// Sort components by name
|
||||
Arrays.sort(softwareComponents, new Comparator<SoftwareComponent>() {
|
||||
@Override
|
||||
public int compare(final SoftwareComponent o1, final SoftwareComponent o2) {
|
||||
return o1.getName().compareTo(o2.getName());
|
||||
}
|
||||
});
|
||||
Arrays.sort(softwareComponents, (o1, o2) -> o1.getName().compareTo(o2.getName()));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(final LayoutInflater inflater, @Nullable final ViewGroup container,
|
||||
@Nullable final Bundle savedInstanceState) {
|
||||
View rootView = inflater.inflate(R.layout.fragment_licenses, container, false);
|
||||
ViewGroup softwareComponentsView = rootView.findViewById(R.id.software_components);
|
||||
final View rootView = inflater.inflate(R.layout.fragment_licenses, container, false);
|
||||
final ViewGroup softwareComponentsView = rootView.findViewById(R.id.software_components);
|
||||
|
||||
View licenseLink = rootView.findViewById(R.id.app_read_license);
|
||||
licenseLink.setOnClickListener(new OnReadFullLicenseClickListener());
|
||||
final View licenseLink = rootView.findViewById(R.id.app_read_license);
|
||||
licenseLink.setOnClickListener(v ->
|
||||
showLicense(getActivity(), StandardLicenses.GPL3));
|
||||
|
||||
for (final SoftwareComponent component : softwareComponents) {
|
||||
View componentView = inflater
|
||||
final View componentView = inflater
|
||||
.inflate(R.layout.item_software_component, container, false);
|
||||
TextView softwareName = componentView.findViewById(R.id.name);
|
||||
TextView copyright = componentView.findViewById(R.id.copyright);
|
||||
final TextView softwareName = componentView.findViewById(R.id.name);
|
||||
final TextView copyright = componentView.findViewById(R.id.copyright);
|
||||
softwareName.setText(component.getName());
|
||||
copyright.setText(getContext().getString(R.string.copyright,
|
||||
copyright.setText(getString(R.string.copyright,
|
||||
component.getYears(),
|
||||
component.getCopyrightOwner(),
|
||||
component.getLicense().getAbbreviation()));
|
||||
|
||||
componentView.setTag(component);
|
||||
componentView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(final View v) {
|
||||
Context context = v.getContext();
|
||||
if (context != null) {
|
||||
showLicense(context, component.getLicense());
|
||||
}
|
||||
}
|
||||
});
|
||||
componentView.setOnClickListener(v ->
|
||||
showLicense(getActivity(), component.getLicense()));
|
||||
softwareComponentsView.addView(componentView);
|
||||
registerForContextMenu(componentView);
|
||||
}
|
||||
@@ -105,40 +92,28 @@ public class LicenseFragment extends Fragment {
|
||||
@Override
|
||||
public void onCreateContextMenu(final ContextMenu menu, final View v,
|
||||
final ContextMenu.ContextMenuInfo menuInfo) {
|
||||
MenuInflater inflater = getActivity().getMenuInflater();
|
||||
SoftwareComponent component = (SoftwareComponent) v.getTag();
|
||||
final MenuInflater inflater = getActivity().getMenuInflater();
|
||||
final SoftwareComponent component = (SoftwareComponent) v.getTag();
|
||||
menu.setHeaderTitle(component.getName());
|
||||
inflater.inflate(R.menu.software_component, menu);
|
||||
super.onCreateContextMenu(menu, v, menuInfo);
|
||||
mComponentForContextMenu = (SoftwareComponent) v.getTag();
|
||||
componentForContextMenu = (SoftwareComponent) v.getTag();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onContextItemSelected(final MenuItem item) {
|
||||
// item.getMenuInfo() is null so we use the tag of the view
|
||||
final SoftwareComponent component = mComponentForContextMenu;
|
||||
final SoftwareComponent component = componentForContextMenu;
|
||||
if (component == null) {
|
||||
return false;
|
||||
}
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_website:
|
||||
openWebsite(component.getLink());
|
||||
ShareUtils.openUrlInBrowser(getActivity(), component.getLink());
|
||||
return true;
|
||||
case R.id.action_show_license:
|
||||
showLicense(getContext(), component.getLicense());
|
||||
showLicense(getActivity(), component.getLicense());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void openWebsite(final String componentLink) {
|
||||
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(componentLink));
|
||||
startActivity(browserIntent);
|
||||
}
|
||||
|
||||
private static class OnReadFullLicenseClickListener implements View.OnClickListener {
|
||||
@Override
|
||||
public void onClick(final View v) {
|
||||
LicenseFragment.showLicense(v.getContext(), StandardLicenses.GPL3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,10 @@ package org.schabi.newpipe.about;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.os.AsyncTask;
|
||||
import android.util.Base64;
|
||||
import android.webkit.WebView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
@@ -12,6 +14,7 @@ import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
@@ -26,28 +29,18 @@ public class LicenseFragmentHelper extends AsyncTask<Object, Void, Integer> {
|
||||
weakReference = new WeakReference<>(activity);
|
||||
}
|
||||
|
||||
private static String getFinishString(final Activity activity) {
|
||||
return activity.getApplicationContext().getResources().getString(R.string.finish);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param context the context to use
|
||||
* @param license the license
|
||||
* @return String which contains a HTML formatted license page
|
||||
* styled according to the context's theme
|
||||
*/
|
||||
public static String getFormattedLicense(final Context context, final License license) {
|
||||
if (context == null) {
|
||||
throw new NullPointerException("context is null");
|
||||
}
|
||||
if (license == null) {
|
||||
throw new NullPointerException("license is null");
|
||||
}
|
||||
|
||||
StringBuilder licenseContent = new StringBuilder();
|
||||
String webViewData;
|
||||
private static String getFormattedLicense(@NonNull final Context context,
|
||||
@NonNull final License license) {
|
||||
final StringBuilder licenseContent = new StringBuilder();
|
||||
final String webViewData;
|
||||
try {
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(
|
||||
final BufferedReader in = new BufferedReader(new InputStreamReader(
|
||||
context.getAssets().open(license.getFilename()), StandardCharsets.UTF_8));
|
||||
String str;
|
||||
while ((str = in.readLine()) != null) {
|
||||
@@ -56,13 +49,11 @@ public class LicenseFragmentHelper extends AsyncTask<Object, Void, Integer> {
|
||||
in.close();
|
||||
|
||||
// split the HTML file and insert the stylesheet into the HEAD of the file
|
||||
String[] insert = licenseContent.toString().split("</head>");
|
||||
webViewData = insert[0] + "<style type=\"text/css\">"
|
||||
+ getLicenseStylesheet(context) + "</style></head>"
|
||||
+ insert[1];
|
||||
} catch (Exception e) {
|
||||
throw new NullPointerException("could not get license file:"
|
||||
+ getLicenseStylesheet(context));
|
||||
webViewData = licenseContent.toString().replace("</head>",
|
||||
"<style>" + getLicenseStylesheet(context) + "</style></head>");
|
||||
} catch (IOException e) {
|
||||
throw new IllegalArgumentException(
|
||||
"Could not get license file: " + license.getFilename(), e);
|
||||
}
|
||||
return webViewData;
|
||||
}
|
||||
@@ -71,21 +62,19 @@ public class LicenseFragmentHelper extends AsyncTask<Object, Void, Integer> {
|
||||
* @param context
|
||||
* @return String which is a CSS stylesheet according to the context's theme
|
||||
*/
|
||||
public static String getLicenseStylesheet(final Context context) {
|
||||
boolean isLightTheme = ThemeHelper.isLightThemeSelected(context);
|
||||
return "body{padding:12px 15px;margin:0;background:#"
|
||||
+ getHexRGBColor(context, isLightTheme
|
||||
private static String getLicenseStylesheet(final Context context) {
|
||||
final boolean isLightTheme = ThemeHelper.isLightThemeSelected(context);
|
||||
return "body{padding:12px 15px;margin:0;"
|
||||
+ "background:#" + getHexRGBColor(context, isLightTheme
|
||||
? R.color.light_license_background_color
|
||||
: R.color.dark_license_background_color)
|
||||
+ ";color:#"
|
||||
+ getHexRGBColor(context, isLightTheme
|
||||
: R.color.dark_license_background_color) + ";"
|
||||
+ "color:#" + getHexRGBColor(context, isLightTheme
|
||||
? R.color.light_license_text_color
|
||||
: R.color.dark_license_text_color) + ";}"
|
||||
+ "a[href]{color:#"
|
||||
+ getHexRGBColor(context, isLightTheme
|
||||
: R.color.dark_license_text_color) + "}"
|
||||
+ "a[href]{color:#" + getHexRGBColor(context, isLightTheme
|
||||
? R.color.light_youtube_primary_color
|
||||
: R.color.dark_youtube_primary_color) + ";}"
|
||||
+ "pre{white-space: pre-wrap;}";
|
||||
: R.color.dark_youtube_primary_color) + "}"
|
||||
+ "pre{white-space:pre-wrap}";
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -95,13 +84,13 @@ public class LicenseFragmentHelper extends AsyncTask<Object, Void, Integer> {
|
||||
* @param color the color number from R.color
|
||||
* @return a six characters long String with hexadecimal RGB values
|
||||
*/
|
||||
public static String getHexRGBColor(final Context context, final int color) {
|
||||
private static String getHexRGBColor(final Context context, final int color) {
|
||||
return context.getResources().getString(color).substring(3);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Activity getActivity() {
|
||||
Activity activity = weakReference.get();
|
||||
final Activity activity = weakReference.get();
|
||||
|
||||
if (activity != null && activity.isFinishing()) {
|
||||
return null;
|
||||
@@ -118,22 +107,22 @@ public class LicenseFragmentHelper extends AsyncTask<Object, Void, Integer> {
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(final Integer result) {
|
||||
Activity activity = getActivity();
|
||||
final Activity activity = getActivity();
|
||||
if (activity == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String webViewData = getFormattedLicense(activity, license);
|
||||
AlertDialog.Builder alert = new AlertDialog.Builder(activity);
|
||||
final String webViewData = Base64.encodeToString(getFormattedLicense(activity, license)
|
||||
.getBytes(StandardCharsets.UTF_8), Base64.NO_PADDING);
|
||||
final WebView webView = new WebView(activity);
|
||||
webView.loadData(webViewData, "text/html; charset=UTF-8", "base64");
|
||||
|
||||
final AlertDialog.Builder alert = new AlertDialog.Builder(activity);
|
||||
alert.setTitle(license.getName());
|
||||
|
||||
WebView wv = new WebView(activity);
|
||||
wv.loadData(webViewData, "text/html; charset=UTF-8", null);
|
||||
|
||||
alert.setView(wv);
|
||||
assureCorrectAppLanguage(activity.getApplicationContext());
|
||||
alert.setNegativeButton(getFinishString(activity), (dialog, which) -> dialog.dismiss());
|
||||
alert.setView(webView);
|
||||
assureCorrectAppLanguage(activity);
|
||||
alert.setNegativeButton(activity.getString(R.string.finish),
|
||||
(dialog, which) -> dialog.dismiss());
|
||||
alert.show();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
package org.schabi.newpipe.database.feed.dao
|
||||
|
||||
import androidx.room.*
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import androidx.room.Transaction
|
||||
import androidx.room.Update
|
||||
import io.reactivex.Flowable
|
||||
import java.util.Date
|
||||
import org.schabi.newpipe.database.feed.model.FeedEntity
|
||||
import org.schabi.newpipe.database.feed.model.FeedLastUpdatedEntity
|
||||
import org.schabi.newpipe.database.stream.model.StreamEntity
|
||||
import org.schabi.newpipe.database.subscription.SubscriptionEntity
|
||||
import java.util.*
|
||||
|
||||
@Dao
|
||||
abstract class FeedDAO {
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
package org.schabi.newpipe.database.feed.dao
|
||||
|
||||
import androidx.room.*
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import androidx.room.Transaction
|
||||
import androidx.room.Update
|
||||
import io.reactivex.Flowable
|
||||
import io.reactivex.Maybe
|
||||
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
|
||||
|
||||
@@ -27,11 +27,11 @@ import org.schabi.newpipe.database.subscription.SubscriptionEntity
|
||||
]
|
||||
)
|
||||
data class FeedEntity(
|
||||
@ColumnInfo(name = STREAM_ID)
|
||||
var streamId: Long,
|
||||
@ColumnInfo(name = STREAM_ID)
|
||||
var streamId: Long,
|
||||
|
||||
@ColumnInfo(name = SUBSCRIPTION_ID)
|
||||
var subscriptionId: Long
|
||||
@ColumnInfo(name = SUBSCRIPTION_ID)
|
||||
var subscriptionId: Long
|
||||
) {
|
||||
|
||||
companion object {
|
||||
@@ -40,4 +40,4 @@ data class FeedEntity(
|
||||
const val STREAM_ID = "stream_id"
|
||||
const val SUBSCRIPTION_ID = "subscription_id"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,18 +13,18 @@ import org.schabi.newpipe.local.subscription.FeedGroupIcon
|
||||
indices = [Index(SORT_ORDER)]
|
||||
)
|
||||
data class FeedGroupEntity(
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
@ColumnInfo(name = ID)
|
||||
val uid: Long,
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
@ColumnInfo(name = ID)
|
||||
val uid: Long,
|
||||
|
||||
@ColumnInfo(name = NAME)
|
||||
var name: String,
|
||||
@ColumnInfo(name = NAME)
|
||||
var name: String,
|
||||
|
||||
@ColumnInfo(name = ICON)
|
||||
var icon: FeedGroupIcon,
|
||||
@ColumnInfo(name = ICON)
|
||||
var icon: FeedGroupIcon,
|
||||
|
||||
@ColumnInfo(name = SORT_ORDER)
|
||||
var sortOrder: Long = -1
|
||||
@ColumnInfo(name = SORT_ORDER)
|
||||
var sortOrder: Long = -1
|
||||
) {
|
||||
companion object {
|
||||
const val FEED_GROUP_TABLE = "feed_group"
|
||||
@@ -36,4 +36,4 @@ data class FeedGroupEntity(
|
||||
|
||||
const val GROUP_ALL_ID = -1L
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,11 +29,11 @@ import org.schabi.newpipe.database.subscription.SubscriptionEntity
|
||||
]
|
||||
)
|
||||
data class FeedGroupSubscriptionEntity(
|
||||
@ColumnInfo(name = GROUP_ID)
|
||||
var feedGroupId: Long,
|
||||
@ColumnInfo(name = GROUP_ID)
|
||||
var feedGroupId: Long,
|
||||
|
||||
@ColumnInfo(name = SUBSCRIPTION_ID)
|
||||
var subscriptionId: Long
|
||||
@ColumnInfo(name = SUBSCRIPTION_ID)
|
||||
var subscriptionId: Long
|
||||
) {
|
||||
|
||||
companion object {
|
||||
@@ -42,4 +42,4 @@ data class FeedGroupSubscriptionEntity(
|
||||
const val GROUP_ID = "group_id"
|
||||
const val SUBSCRIPTION_ID = "subscription_id"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,10 @@ import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.ForeignKey
|
||||
import androidx.room.PrimaryKey
|
||||
import java.util.Date
|
||||
import org.schabi.newpipe.database.feed.model.FeedLastUpdatedEntity.Companion.FEED_LAST_UPDATED_TABLE
|
||||
import org.schabi.newpipe.database.feed.model.FeedLastUpdatedEntity.Companion.SUBSCRIPTION_ID
|
||||
import org.schabi.newpipe.database.subscription.SubscriptionEntity
|
||||
import java.util.*
|
||||
|
||||
@Entity(
|
||||
tableName = FEED_LAST_UPDATED_TABLE,
|
||||
@@ -20,12 +20,12 @@ import java.util.*
|
||||
]
|
||||
)
|
||||
data class FeedLastUpdatedEntity(
|
||||
@PrimaryKey
|
||||
@ColumnInfo(name = SUBSCRIPTION_ID)
|
||||
var subscriptionId: Long,
|
||||
@PrimaryKey
|
||||
@ColumnInfo(name = SUBSCRIPTION_ID)
|
||||
var subscriptionId: Long,
|
||||
|
||||
@ColumnInfo(name = LAST_UPDATED)
|
||||
var lastUpdated: Date? = null
|
||||
@ColumnInfo(name = LAST_UPDATED)
|
||||
var lastUpdated: Date? = null
|
||||
) {
|
||||
|
||||
companion object {
|
||||
@@ -34,4 +34,4 @@ data class FeedLastUpdatedEntity(
|
||||
const val SUBSCRIPTION_ID = "subscription_id"
|
||||
const val LAST_UPDATED = "last_updated"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,21 +2,21 @@ package org.schabi.newpipe.database.history.model
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Embedded
|
||||
import java.util.Date
|
||||
import org.schabi.newpipe.database.stream.model.StreamEntity
|
||||
import java.util.*
|
||||
|
||||
data class StreamHistoryEntry(
|
||||
@Embedded
|
||||
val streamEntity: StreamEntity,
|
||||
@Embedded
|
||||
val streamEntity: StreamEntity,
|
||||
|
||||
@ColumnInfo(name = StreamHistoryEntity.JOIN_STREAM_ID)
|
||||
val streamId: Long,
|
||||
@ColumnInfo(name = StreamHistoryEntity.JOIN_STREAM_ID)
|
||||
val streamId: Long,
|
||||
|
||||
@ColumnInfo(name = StreamHistoryEntity.STREAM_ACCESS_DATE)
|
||||
val accessDate: Date,
|
||||
@ColumnInfo(name = StreamHistoryEntity.STREAM_ACCESS_DATE)
|
||||
val accessDate: Date,
|
||||
|
||||
@ColumnInfo(name = StreamHistoryEntity.STREAM_REPEAT_COUNT)
|
||||
val repeatCount: Long
|
||||
@ColumnInfo(name = StreamHistoryEntity.STREAM_REPEAT_COUNT)
|
||||
val repeatCount: Long
|
||||
) {
|
||||
|
||||
fun toStreamHistoryEntity(): StreamHistoryEntity {
|
||||
|
||||
@@ -1,7 +1,33 @@
|
||||
package org.schabi.newpipe.database.playlist;
|
||||
|
||||
import org.schabi.newpipe.database.LocalItem;
|
||||
import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public interface PlaylistLocalItem extends LocalItem {
|
||||
String getOrderingName();
|
||||
|
||||
static List<PlaylistLocalItem> merge(
|
||||
final List<PlaylistMetadataEntry> localPlaylists,
|
||||
final List<PlaylistRemoteEntity> remotePlaylists) {
|
||||
final List<PlaylistLocalItem> items = new ArrayList<>(
|
||||
localPlaylists.size() + remotePlaylists.size());
|
||||
items.addAll(localPlaylists);
|
||||
items.addAll(remotePlaylists);
|
||||
|
||||
Collections.sort(items, (left, right) -> {
|
||||
final String on1 = left.getOrderingName();
|
||||
final String on2 = right.getOrderingName();
|
||||
if (on1 == null) {
|
||||
return on2 == null ? 0 : 1;
|
||||
} else {
|
||||
return on2 == null ? -1 : on1.compareToIgnoreCase(on2);
|
||||
}
|
||||
});
|
||||
|
||||
return items;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,14 +8,14 @@ import org.schabi.newpipe.database.stream.model.StreamEntity
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem
|
||||
|
||||
class PlaylistStreamEntry(
|
||||
@Embedded
|
||||
val streamEntity: StreamEntity,
|
||||
@Embedded
|
||||
val streamEntity: StreamEntity,
|
||||
|
||||
@ColumnInfo(name = PlaylistStreamEntity.JOIN_STREAM_ID)
|
||||
val streamId: Long,
|
||||
@ColumnInfo(name = PlaylistStreamEntity.JOIN_STREAM_ID)
|
||||
val streamId: Long,
|
||||
|
||||
@ColumnInfo(name = PlaylistStreamEntity.JOIN_INDEX)
|
||||
val joinIndex: Int
|
||||
@ColumnInfo(name = PlaylistStreamEntity.JOIN_INDEX)
|
||||
val joinIndex: Int
|
||||
) : LocalItem {
|
||||
|
||||
@Throws(IllegalArgumentException::class)
|
||||
|
||||
@@ -2,24 +2,24 @@ package org.schabi.newpipe.database.stream
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Embedded
|
||||
import java.util.Date
|
||||
import org.schabi.newpipe.database.LocalItem
|
||||
import org.schabi.newpipe.database.history.model.StreamHistoryEntity
|
||||
import org.schabi.newpipe.database.stream.model.StreamEntity
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem
|
||||
import java.util.*
|
||||
|
||||
class StreamStatisticsEntry(
|
||||
@Embedded
|
||||
val streamEntity: StreamEntity,
|
||||
@Embedded
|
||||
val streamEntity: StreamEntity,
|
||||
|
||||
@ColumnInfo(name = StreamHistoryEntity.JOIN_STREAM_ID)
|
||||
val streamId: Long,
|
||||
@ColumnInfo(name = StreamHistoryEntity.JOIN_STREAM_ID)
|
||||
val streamId: Long,
|
||||
|
||||
@ColumnInfo(name = STREAM_LATEST_DATE)
|
||||
val latestAccessDate: Date,
|
||||
@ColumnInfo(name = STREAM_LATEST_DATE)
|
||||
val latestAccessDate: Date,
|
||||
|
||||
@ColumnInfo(name = STREAM_WATCH_COUNT)
|
||||
val watchCount: Long
|
||||
@ColumnInfo(name = STREAM_WATCH_COUNT)
|
||||
val watchCount: Long
|
||||
) : LocalItem {
|
||||
|
||||
fun toStreamInfoItem(): StreamInfoItem {
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
package org.schabi.newpipe.database.stream.dao
|
||||
|
||||
import androidx.room.*
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import androidx.room.Transaction
|
||||
import io.reactivex.Flowable
|
||||
import java.util.Date
|
||||
import org.schabi.newpipe.database.BasicDAO
|
||||
import org.schabi.newpipe.database.stream.model.StreamEntity
|
||||
import org.schabi.newpipe.database.stream.model.StreamEntity.Companion.STREAM_ID
|
||||
import org.schabi.newpipe.extractor.stream.StreamType
|
||||
import org.schabi.newpipe.extractor.stream.StreamType.AUDIO_LIVE_STREAM
|
||||
import org.schabi.newpipe.extractor.stream.StreamType.LIVE_STREAM
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
@Dao
|
||||
abstract class StreamDAO : BasicDAO<StreamEntity> {
|
||||
@@ -94,7 +98,6 @@ abstract class StreamDAO : BasicDAO<StreamEntity> {
|
||||
if (existentMinimalStream.duration > 0 && newerStream.duration < 0) {
|
||||
newerStream.duration = existentMinimalStream.duration
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,21 +119,22 @@ abstract class StreamDAO : BasicDAO<StreamEntity> {
|
||||
* Minimal entry class used when comparing/updating an existent stream.
|
||||
*/
|
||||
internal data class StreamCompareFeed(
|
||||
@ColumnInfo(name = STREAM_ID)
|
||||
var uid: Long = 0,
|
||||
@ColumnInfo(name = STREAM_ID)
|
||||
var uid: Long = 0,
|
||||
|
||||
@ColumnInfo(name = StreamEntity.STREAM_TYPE)
|
||||
var streamType: StreamType,
|
||||
@ColumnInfo(name = StreamEntity.STREAM_TYPE)
|
||||
var streamType: StreamType,
|
||||
|
||||
@ColumnInfo(name = StreamEntity.STREAM_TEXTUAL_UPLOAD_DATE)
|
||||
var textualUploadDate: String? = null,
|
||||
@ColumnInfo(name = StreamEntity.STREAM_TEXTUAL_UPLOAD_DATE)
|
||||
var textualUploadDate: String? = null,
|
||||
|
||||
@ColumnInfo(name = StreamEntity.STREAM_UPLOAD_DATE)
|
||||
var uploadDate: Date? = null,
|
||||
@ColumnInfo(name = StreamEntity.STREAM_UPLOAD_DATE)
|
||||
var uploadDate: Date? = null,
|
||||
|
||||
@ColumnInfo(name = StreamEntity.STREAM_IS_UPLOAD_DATE_APPROXIMATION)
|
||||
var isUploadDateApproximation: Boolean? = null,
|
||||
@ColumnInfo(name = StreamEntity.STREAM_IS_UPLOAD_DATE_APPROXIMATION)
|
||||
var isUploadDateApproximation: Boolean? = null,
|
||||
|
||||
@ColumnInfo(name = StreamEntity.STREAM_DURATION)
|
||||
var duration: Long)
|
||||
@ColumnInfo(name = StreamEntity.STREAM_DURATION)
|
||||
var duration: Long
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
package org.schabi.newpipe.database.stream.model
|
||||
|
||||
import androidx.room.*
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.Ignore
|
||||
import androidx.room.Index
|
||||
import androidx.room.PrimaryKey
|
||||
import java.io.Serializable
|
||||
import java.util.Calendar
|
||||
import java.util.Date
|
||||
import org.schabi.newpipe.database.stream.model.StreamEntity.Companion.STREAM_SERVICE_ID
|
||||
import org.schabi.newpipe.database.stream.model.StreamEntity.Companion.STREAM_TABLE
|
||||
import org.schabi.newpipe.database.stream.model.StreamEntity.Companion.STREAM_URL
|
||||
@@ -9,8 +16,6 @@ import org.schabi.newpipe.extractor.stream.StreamInfo
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem
|
||||
import org.schabi.newpipe.extractor.stream.StreamType
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueueItem
|
||||
import java.io.Serializable
|
||||
import java.util.*
|
||||
|
||||
@Entity(tableName = STREAM_TABLE,
|
||||
indices = [
|
||||
@@ -18,42 +23,42 @@ import java.util.*
|
||||
]
|
||||
)
|
||||
data class StreamEntity(
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
@ColumnInfo(name = STREAM_ID)
|
||||
var uid: Long = 0,
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
@ColumnInfo(name = STREAM_ID)
|
||||
var uid: Long = 0,
|
||||
|
||||
@ColumnInfo(name = STREAM_SERVICE_ID)
|
||||
var serviceId: Int,
|
||||
@ColumnInfo(name = STREAM_SERVICE_ID)
|
||||
var serviceId: Int,
|
||||
|
||||
@ColumnInfo(name = STREAM_URL)
|
||||
var url: String,
|
||||
@ColumnInfo(name = STREAM_URL)
|
||||
var url: String,
|
||||
|
||||
@ColumnInfo(name = STREAM_TITLE)
|
||||
var title: String,
|
||||
@ColumnInfo(name = STREAM_TITLE)
|
||||
var title: String,
|
||||
|
||||
@ColumnInfo(name = STREAM_TYPE)
|
||||
var streamType: StreamType,
|
||||
@ColumnInfo(name = STREAM_TYPE)
|
||||
var streamType: StreamType,
|
||||
|
||||
@ColumnInfo(name = STREAM_DURATION)
|
||||
var duration: Long,
|
||||
@ColumnInfo(name = STREAM_DURATION)
|
||||
var duration: Long,
|
||||
|
||||
@ColumnInfo(name = STREAM_UPLOADER)
|
||||
var uploader: String,
|
||||
@ColumnInfo(name = STREAM_UPLOADER)
|
||||
var uploader: String,
|
||||
|
||||
@ColumnInfo(name = STREAM_THUMBNAIL_URL)
|
||||
var thumbnailUrl: String? = null,
|
||||
@ColumnInfo(name = STREAM_THUMBNAIL_URL)
|
||||
var thumbnailUrl: String? = null,
|
||||
|
||||
@ColumnInfo(name = STREAM_VIEWS)
|
||||
var viewCount: Long? = null,
|
||||
@ColumnInfo(name = STREAM_VIEWS)
|
||||
var viewCount: Long? = null,
|
||||
|
||||
@ColumnInfo(name = STREAM_TEXTUAL_UPLOAD_DATE)
|
||||
var textualUploadDate: String? = null,
|
||||
@ColumnInfo(name = STREAM_TEXTUAL_UPLOAD_DATE)
|
||||
var textualUploadDate: String? = null,
|
||||
|
||||
@ColumnInfo(name = STREAM_UPLOAD_DATE)
|
||||
var uploadDate: Date? = null,
|
||||
@ColumnInfo(name = STREAM_UPLOAD_DATE)
|
||||
var uploadDate: Date? = null,
|
||||
|
||||
@ColumnInfo(name = STREAM_IS_UPLOAD_DATE_APPROXIMATION)
|
||||
var isUploadDateApproximation: Boolean? = null
|
||||
@ColumnInfo(name = STREAM_IS_UPLOAD_DATE_APPROXIMATION)
|
||||
var isUploadDateApproximation: Boolean? = null
|
||||
) : Serializable {
|
||||
|
||||
@Ignore
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package org.schabi.newpipe.database.subscription
|
||||
|
||||
import androidx.room.*
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import androidx.room.Transaction
|
||||
import io.reactivex.Flowable
|
||||
import io.reactivex.Maybe
|
||||
import org.schabi.newpipe.database.BasicDAO
|
||||
@@ -16,6 +20,45 @@ abstract class SubscriptionDAO : BasicDAO<SubscriptionEntity> {
|
||||
@Query("SELECT * FROM subscriptions ORDER BY name COLLATE NOCASE ASC")
|
||||
abstract override fun getAll(): Flowable<List<SubscriptionEntity>>
|
||||
|
||||
@Query("""
|
||||
SELECT * FROM subscriptions
|
||||
|
||||
WHERE name LIKE '%' || :filter || '%'
|
||||
|
||||
ORDER BY name COLLATE NOCASE ASC
|
||||
""")
|
||||
abstract fun getSubscriptionsFiltered(filter: String): Flowable<List<SubscriptionEntity>>
|
||||
|
||||
@Query("""
|
||||
SELECT * FROM subscriptions s
|
||||
|
||||
LEFT JOIN feed_group_subscription_join fgs
|
||||
ON s.uid = fgs.subscription_id
|
||||
|
||||
WHERE (fgs.subscription_id IS NULL OR fgs.group_id = :currentGroupId)
|
||||
|
||||
ORDER BY name COLLATE NOCASE ASC
|
||||
""")
|
||||
abstract fun getSubscriptionsOnlyUngrouped(
|
||||
currentGroupId: Long
|
||||
): Flowable<List<SubscriptionEntity>>
|
||||
|
||||
@Query("""
|
||||
SELECT * FROM subscriptions s
|
||||
|
||||
LEFT JOIN feed_group_subscription_join fgs
|
||||
ON s.uid = fgs.subscription_id
|
||||
|
||||
WHERE (fgs.subscription_id IS NULL OR fgs.group_id = :currentGroupId)
|
||||
AND s.name LIKE '%' || :filter || '%'
|
||||
|
||||
ORDER BY name COLLATE NOCASE ASC
|
||||
""")
|
||||
abstract fun getSubscriptionsOnlyUngroupedFiltered(
|
||||
currentGroupId: Long,
|
||||
filter: String
|
||||
): Flowable<List<SubscriptionEntity>>
|
||||
|
||||
@Query("SELECT * FROM subscriptions WHERE url LIKE :url AND service_id = :serviceId")
|
||||
abstract fun getSubscriptionFlowable(serviceId: Int, url: String): Flowable<List<SubscriptionEntity>>
|
||||
|
||||
@@ -48,7 +91,7 @@ abstract class SubscriptionDAO : BasicDAO<SubscriptionEntity> {
|
||||
entity.uid = uidFromInsert
|
||||
} else {
|
||||
val subscriptionIdFromDb = getSubscriptionIdInternal(entity.serviceId, entity.url)
|
||||
?: throw IllegalStateException("Subscription cannot be null just after insertion.")
|
||||
?: throw IllegalStateException("Subscription cannot be null just after insertion.")
|
||||
entity.uid = subscriptionIdFromDb
|
||||
|
||||
update(entity)
|
||||
|
||||
@@ -130,4 +130,55 @@ public class SubscriptionEntity {
|
||||
item.setDescription(getDescription());
|
||||
return item;
|
||||
}
|
||||
|
||||
|
||||
// TODO: Remove these generated methods by migrating this class to a data class from Kotlin.
|
||||
@Override
|
||||
@SuppressWarnings("EqualsReplaceableByObjectsCall")
|
||||
public boolean equals(final Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final SubscriptionEntity that = (SubscriptionEntity) o;
|
||||
|
||||
if (uid != that.uid) {
|
||||
return false;
|
||||
}
|
||||
if (serviceId != that.serviceId) {
|
||||
return false;
|
||||
}
|
||||
if (!url.equals(that.url)) {
|
||||
return false;
|
||||
}
|
||||
if (name != null ? !name.equals(that.name) : that.name != null) {
|
||||
return false;
|
||||
}
|
||||
if (avatarUrl != null ? !avatarUrl.equals(that.avatarUrl) : that.avatarUrl != null) {
|
||||
return false;
|
||||
}
|
||||
if (subscriberCount != null
|
||||
? !subscriberCount.equals(that.subscriberCount)
|
||||
: that.subscriberCount != null) {
|
||||
return false;
|
||||
}
|
||||
return description != null
|
||||
? description.equals(that.description)
|
||||
: that.description == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = (int) (uid ^ (uid >>> 32));
|
||||
result = 31 * result + serviceId;
|
||||
result = 31 * result + url.hashCode();
|
||||
result = 31 * result + (name != null ? name.hashCode() : 0);
|
||||
result = 31 * result + (avatarUrl != null ? avatarUrl.hashCode() : 0);
|
||||
result = 31 * result + (subscriberCount != null ? subscriberCount.hashCode() : 0);
|
||||
result = 31 * result + (description != null ? description.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -396,13 +396,11 @@ public class DownloadDialog extends DialogFragment
|
||||
Log.d(TAG, "initToolbar() called with: toolbar = [" + toolbar + "]");
|
||||
}
|
||||
|
||||
boolean isLight = ThemeHelper.isLightThemeSelected(getActivity());
|
||||
|
||||
toolbar.setTitle(R.string.download_dialog_title);
|
||||
toolbar.setNavigationIcon(isLight ? R.drawable.ic_arrow_back_black_24dp
|
||||
: R.drawable.ic_arrow_back_white_24dp);
|
||||
toolbar.setNavigationIcon(
|
||||
ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_arrow_back));
|
||||
toolbar.inflateMenu(R.menu.dialog_url);
|
||||
toolbar.setNavigationOnClickListener(v -> getDialog().dismiss());
|
||||
toolbar.setNavigationOnClickListener(v -> requireDialog().dismiss());
|
||||
toolbar.setNavigationContentDescription(R.string.cancel);
|
||||
|
||||
okButton = toolbar.findViewById(R.id.okay);
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package org.schabi.newpipe.fragments;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
@@ -30,6 +32,7 @@ import org.schabi.newpipe.settings.tabs.Tab;
|
||||
import org.schabi.newpipe.settings.tabs.TabsManager;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.ServiceHelper;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
import org.schabi.newpipe.views.ScrollableTabLayout;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -45,6 +48,9 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
||||
|
||||
private boolean hasTabsChanged = false;
|
||||
|
||||
private boolean previousYoutubeRestrictedModeEnabled;
|
||||
private String youtubeRestrictedModeEnabledKey;
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Fragment's LifeCycle
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
@@ -53,7 +59,6 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
||||
public void onCreate(final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setHasOptionsMenu(true);
|
||||
|
||||
tabsManager = TabsManager.getManager(activity);
|
||||
tabsManager.setSavedTabsListener(() -> {
|
||||
if (DEBUG) {
|
||||
@@ -66,6 +71,11 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
||||
hasTabsChanged = true;
|
||||
}
|
||||
});
|
||||
|
||||
youtubeRestrictedModeEnabledKey = getString(R.string.youtube_restricted_mode_enabled);
|
||||
previousYoutubeRestrictedModeEnabled =
|
||||
PreferenceManager.getDefaultSharedPreferences(getContext())
|
||||
.getBoolean(youtubeRestrictedModeEnabledKey, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -82,6 +92,8 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
||||
tabLayout = rootView.findViewById(R.id.main_tab_layout);
|
||||
viewPager = rootView.findViewById(R.id.pager);
|
||||
|
||||
tabLayout.setTabIconTint(ColorStateList.valueOf(
|
||||
ThemeHelper.resolveColorFromAttr(requireContext(), R.attr.colorAccent)));
|
||||
tabLayout.setupWithViewPager(viewPager);
|
||||
tabLayout.addOnTabSelectedListener(this);
|
||||
|
||||
@@ -92,7 +104,13 @@ public class MainFragment extends BaseFragment implements TabLayout.OnTabSelecte
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
if (hasTabsChanged) {
|
||||
boolean youtubeRestrictedModeEnabled =
|
||||
PreferenceManager.getDefaultSharedPreferences(getContext())
|
||||
.getBoolean(youtubeRestrictedModeEnabledKey, false);
|
||||
if (previousYoutubeRestrictedModeEnabled != youtubeRestrictedModeEnabled) {
|
||||
previousYoutubeRestrictedModeEnabled = youtubeRestrictedModeEnabled;
|
||||
setupTabs();
|
||||
} else if (hasTabsChanged) {
|
||||
setupTabs();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
@@ -35,6 +36,7 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.content.res.AppCompatResources;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.viewpager.widget.ViewPager;
|
||||
@@ -85,6 +87,7 @@ import org.schabi.newpipe.util.PermissionHelper;
|
||||
import org.schabi.newpipe.util.ShareUtils;
|
||||
import org.schabi.newpipe.util.StreamItemAdapter;
|
||||
import org.schabi.newpipe.util.StreamItemAdapter.StreamSizeWrapper;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
import org.schabi.newpipe.views.AnimatedProgressBar;
|
||||
import org.schabi.newpipe.views.LargeTextMovementMethod;
|
||||
|
||||
@@ -175,6 +178,8 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
|
||||
private View uploaderRootLayout;
|
||||
private TextView uploaderTextView;
|
||||
private ImageView uploaderThumb;
|
||||
private TextView subChannelTextView;
|
||||
private ImageView subChannelThumb;
|
||||
|
||||
private TextView thumbsUpTextView;
|
||||
private ImageView thumbsUpImageView;
|
||||
@@ -419,18 +424,17 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
|
||||
}
|
||||
break;
|
||||
case R.id.detail_uploader_root_layout:
|
||||
if (TextUtils.isEmpty(currentInfo.getUploaderUrl())) {
|
||||
Log.w(TAG, "Can't open channel because we got no channel URL");
|
||||
} else {
|
||||
try {
|
||||
NavigationHelper.openChannelFragment(
|
||||
getFragmentManager(),
|
||||
currentInfo.getServiceId(),
|
||||
currentInfo.getUploaderUrl(),
|
||||
currentInfo.getUploaderName());
|
||||
} catch (Exception e) {
|
||||
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
|
||||
if (TextUtils.isEmpty(currentInfo.getSubChannelUrl())) {
|
||||
if (!TextUtils.isEmpty(currentInfo.getUploaderUrl())) {
|
||||
openChannel(currentInfo.getUploaderUrl(), currentInfo.getUploaderName());
|
||||
}
|
||||
|
||||
if (DEBUG) {
|
||||
Log.i(TAG, "Can't open sub-channel because we got no channel URL");
|
||||
}
|
||||
} else {
|
||||
openChannel(currentInfo.getSubChannelUrl(),
|
||||
currentInfo.getSubChannelName());
|
||||
}
|
||||
break;
|
||||
case R.id.detail_thumbnail_root_layout:
|
||||
@@ -447,6 +451,18 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
|
||||
}
|
||||
}
|
||||
|
||||
private void openChannel(final String subChannelUrl, final String subChannelName) {
|
||||
try {
|
||||
NavigationHelper.openChannelFragment(
|
||||
getFragmentManager(),
|
||||
currentInfo.getServiceId(),
|
||||
subChannelUrl,
|
||||
subChannelName);
|
||||
} catch (Exception e) {
|
||||
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onLongClick(final View v) {
|
||||
if (isLoading.get() || currentInfo == null) {
|
||||
@@ -463,6 +479,17 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
|
||||
case R.id.detail_controls_download:
|
||||
NavigationHelper.openDownloads(getActivity());
|
||||
break;
|
||||
case R.id.detail_uploader_root_layout:
|
||||
if (TextUtils.isEmpty(currentInfo.getSubChannelUrl())) {
|
||||
Log.w(TAG,
|
||||
"Can't open parent channel because we got no parent channel URL");
|
||||
} else {
|
||||
openChannel(currentInfo.getUploaderUrl(), currentInfo.getUploaderName());
|
||||
}
|
||||
break;
|
||||
case R.id.detail_title_root_layout:
|
||||
ShareUtils.copyToClipboard(getContext(), videoTitleTextView.getText().toString());
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -473,13 +500,15 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
|
||||
videoTitleTextView.setMaxLines(1);
|
||||
videoDescriptionRootLayout.setVisibility(View.GONE);
|
||||
videoDescriptionView.setFocusable(false);
|
||||
videoTitleToggleArrow.setImageResource(R.drawable.arrow_down);
|
||||
videoTitleToggleArrow.setImageResource(
|
||||
ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_expand_more));
|
||||
} else {
|
||||
videoTitleTextView.setMaxLines(10);
|
||||
videoDescriptionRootLayout.setVisibility(View.VISIBLE);
|
||||
videoDescriptionView.setFocusable(true);
|
||||
videoDescriptionView.setMovementMethod(new LargeTextMovementMethod());
|
||||
videoTitleToggleArrow.setImageResource(R.drawable.arrow_up);
|
||||
videoTitleToggleArrow.setImageResource(
|
||||
ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_expand_less));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -525,6 +554,8 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
|
||||
uploaderRootLayout = rootView.findViewById(R.id.detail_uploader_root_layout);
|
||||
uploaderTextView = rootView.findViewById(R.id.detail_uploader_text_view);
|
||||
uploaderThumb = rootView.findViewById(R.id.detail_uploader_thumbnail_view);
|
||||
subChannelTextView = rootView.findViewById(R.id.detail_sub_channel_text_view);
|
||||
subChannelThumb = rootView.findViewById(R.id.detail_sub_channel_thumbnail_view);
|
||||
|
||||
appBarLayout = rootView.findViewById(R.id.appbarlayout);
|
||||
viewPager = rootView.findViewById(R.id.viewpager);
|
||||
@@ -554,8 +585,10 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
|
||||
protected void initListeners() {
|
||||
super.initListeners();
|
||||
|
||||
videoTitleRoot.setOnClickListener(this);
|
||||
videoTitleRoot.setOnLongClickListener(this);
|
||||
uploaderRootLayout.setOnClickListener(this);
|
||||
uploaderRootLayout.setOnLongClickListener(this);
|
||||
videoTitleRoot.setOnClickListener(this);
|
||||
thumbnailBackgroundButton.setOnClickListener(this);
|
||||
detailControlsBackground.setOnClickListener(this);
|
||||
detailControlsPopup.setOnClickListener(this);
|
||||
@@ -603,6 +636,11 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
|
||||
ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS, onFailListener);
|
||||
}
|
||||
|
||||
if (!TextUtils.isEmpty(info.getSubChannelAvatarUrl())) {
|
||||
IMAGE_LOADER.displayImage(info.getSubChannelAvatarUrl(), subChannelThumb,
|
||||
ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS);
|
||||
}
|
||||
|
||||
if (!TextUtils.isEmpty(info.getUploaderAvatarUrl())) {
|
||||
IMAGE_LOADER.displayImage(info.getUploaderAvatarUrl(), uploaderThumb,
|
||||
ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS);
|
||||
@@ -632,9 +670,10 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
|
||||
}
|
||||
|
||||
private void updateMenuItemVisibility() {
|
||||
// show kodi if set in settings
|
||||
// show kodi button if it supports the current service and it is enabled in settings
|
||||
menu.findItem(R.id.action_play_with_kodi).setVisible(
|
||||
PreferenceManager.getDefaultSharedPreferences(activity).getBoolean(
|
||||
KoreUtil.isServiceSupportedByKore(serviceId)
|
||||
&& PreferenceManager.getDefaultSharedPreferences(activity).getBoolean(
|
||||
activity.getString(R.string.show_play_with_kodi_key), false));
|
||||
}
|
||||
|
||||
@@ -665,8 +704,7 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
|
||||
return true;
|
||||
case R.id.action_play_with_kodi:
|
||||
try {
|
||||
NavigationHelper.playWithKore(activity, Uri.parse(
|
||||
url.replace("https", "http")));
|
||||
NavigationHelper.playWithKore(activity, Uri.parse(currentInfo.getUrl()));
|
||||
} catch (Exception e) {
|
||||
if (DEBUG) {
|
||||
Log.i(TAG, "Failed to start kore", e);
|
||||
@@ -964,7 +1002,7 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
|
||||
@NonNull final StreamInfo info,
|
||||
@NonNull final Stream selectedStream) {
|
||||
NavigationHelper.playOnExternalPlayer(context, currentInfo.getName(),
|
||||
currentInfo.getUploaderName(), selectedStream);
|
||||
currentInfo.getSubChannelName(), selectedStream);
|
||||
|
||||
final HistoryRecordManager recordManager = new HistoryRecordManager(requireContext());
|
||||
disposables.add(recordManager.onViewed(info).onErrorComplete()
|
||||
@@ -1043,7 +1081,8 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
|
||||
return;
|
||||
}
|
||||
|
||||
thumbnailImageView.setImageDrawable(ContextCompat.getDrawable(activity, imageResource));
|
||||
thumbnailImageView.setImageDrawable(
|
||||
AppCompatResources.getDrawable(requireContext(), imageResource));
|
||||
animateView(thumbnailImageView, false, 0, 0,
|
||||
() -> animateView(thumbnailImageView, true, 500));
|
||||
}
|
||||
@@ -1084,7 +1123,6 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
|
||||
animateView(videoTitleTextView, true, 0);
|
||||
|
||||
videoDescriptionRootLayout.setVisibility(View.GONE);
|
||||
videoTitleToggleArrow.setImageResource(R.drawable.arrow_down);
|
||||
videoTitleToggleArrow.setVisibility(View.GONE);
|
||||
videoTitleRoot.setClickable(false);
|
||||
|
||||
@@ -1097,9 +1135,9 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
|
||||
}
|
||||
|
||||
IMAGE_LOADER.cancelDisplayTask(thumbnailImageView);
|
||||
IMAGE_LOADER.cancelDisplayTask(uploaderThumb);
|
||||
IMAGE_LOADER.cancelDisplayTask(subChannelThumb);
|
||||
thumbnailImageView.setImageBitmap(null);
|
||||
uploaderThumb.setImageBitmap(null);
|
||||
subChannelThumb.setImageBitmap(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1127,14 +1165,18 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
|
||||
animateView(thumbnailPlayButton, true, 200);
|
||||
videoTitleTextView.setText(name);
|
||||
|
||||
if (!TextUtils.isEmpty(info.getUploaderName())) {
|
||||
uploaderTextView.setText(info.getUploaderName());
|
||||
uploaderTextView.setVisibility(View.VISIBLE);
|
||||
uploaderTextView.setSelected(true);
|
||||
if (!TextUtils.isEmpty(info.getSubChannelName())) {
|
||||
displayBothUploaderAndSubChannel(info);
|
||||
} else if (!TextUtils.isEmpty(info.getUploaderName())) {
|
||||
displayUploaderAsSubChannel(info);
|
||||
} else {
|
||||
uploaderTextView.setVisibility(View.GONE);
|
||||
uploaderThumb.setVisibility(View.GONE);
|
||||
}
|
||||
uploaderThumb.setImageDrawable(ContextCompat.getDrawable(activity, R.drawable.buddy));
|
||||
|
||||
Drawable buddyDrawable = AppCompatResources.getDrawable(activity, R.drawable.buddy);
|
||||
subChannelThumb.setImageDrawable(buddyDrawable);
|
||||
uploaderThumb.setImageDrawable(buddyDrawable);
|
||||
|
||||
if (info.getViewCount() >= 0) {
|
||||
if (info.getStreamType().equals(StreamType.AUDIO_LIVE_STREAM)) {
|
||||
@@ -1196,8 +1238,9 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
|
||||
|
||||
videoDescriptionView.setVisibility(View.GONE);
|
||||
videoTitleRoot.setClickable(true);
|
||||
videoTitleToggleArrow.setImageResource(
|
||||
ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_expand_more));
|
||||
videoTitleToggleArrow.setVisibility(View.VISIBLE);
|
||||
videoTitleToggleArrow.setImageResource(R.drawable.arrow_down);
|
||||
videoDescriptionRootLayout.setVisibility(View.GONE);
|
||||
|
||||
if (info.getUploadDate() != null) {
|
||||
@@ -1243,7 +1286,7 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
|
||||
|
||||
detailControlsPopup.setVisibility(View.GONE);
|
||||
spinnerToolbar.setVisibility(View.GONE);
|
||||
thumbnailPlayButton.setImageResource(R.drawable.ic_headset_white_24dp);
|
||||
thumbnailPlayButton.setImageResource(R.drawable.ic_headset_shadow);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1265,6 +1308,31 @@ public class VideoDetailFragment extends BaseStateFragment<StreamInfo>
|
||||
tabLayout.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
private void displayUploaderAsSubChannel(final StreamInfo info) {
|
||||
subChannelTextView.setText(info.getUploaderName());
|
||||
subChannelTextView.setVisibility(View.VISIBLE);
|
||||
subChannelTextView.setSelected(true);
|
||||
uploaderTextView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
private void displayBothUploaderAndSubChannel(final StreamInfo info) {
|
||||
subChannelTextView.setText(info.getSubChannelName());
|
||||
subChannelTextView.setVisibility(View.VISIBLE);
|
||||
subChannelTextView.setSelected(true);
|
||||
|
||||
subChannelThumb.setVisibility(View.VISIBLE);
|
||||
|
||||
if (!TextUtils.isEmpty(info.getUploaderName())) {
|
||||
uploaderTextView.setText(
|
||||
String.format(getString(R.string.video_detail_by), info.getUploaderName()));
|
||||
uploaderTextView.setVisibility(View.VISIBLE);
|
||||
uploaderTextView.setSelected(true);
|
||||
} else {
|
||||
uploaderTextView.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void openDownloadDialog() {
|
||||
try {
|
||||
DownloadDialog downloadDialog = DownloadDialog.newInstance(currentInfo);
|
||||
|
||||
@@ -9,6 +9,7 @@ import androidx.annotation.NonNull;
|
||||
|
||||
import org.schabi.newpipe.extractor.ListExtractor;
|
||||
import org.schabi.newpipe.extractor.ListInfo;
|
||||
import org.schabi.newpipe.extractor.Page;
|
||||
import org.schabi.newpipe.util.Constants;
|
||||
import org.schabi.newpipe.views.NewPipeRecyclerView;
|
||||
|
||||
@@ -30,7 +31,7 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
|
||||
protected String url;
|
||||
|
||||
protected I currentInfo;
|
||||
protected String currentNextPageUrl;
|
||||
protected Page currentNextPage;
|
||||
protected Disposable currentWorker;
|
||||
|
||||
@Override
|
||||
@@ -78,7 +79,7 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
|
||||
public void writeTo(final Queue<Object> objectsToSave) {
|
||||
super.writeTo(objectsToSave);
|
||||
objectsToSave.add(currentInfo);
|
||||
objectsToSave.add(currentNextPageUrl);
|
||||
objectsToSave.add(currentNextPage);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -86,7 +87,7 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
|
||||
public void readFrom(@NonNull final Queue<Object> savedObjects) throws Exception {
|
||||
super.readFrom(savedObjects);
|
||||
currentInfo = (I) savedObjects.poll();
|
||||
currentNextPageUrl = (String) savedObjects.poll();
|
||||
currentNextPage = (Page) savedObjects.poll();
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
@@ -130,7 +131,7 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
|
||||
.subscribe((@NonNull I result) -> {
|
||||
isLoading.set(false);
|
||||
currentInfo = result;
|
||||
currentNextPageUrl = result.getNextPageUrl();
|
||||
currentNextPage = result.getNextPage();
|
||||
handleResult(result);
|
||||
}, (@NonNull Throwable throwable) -> onError(throwable));
|
||||
}
|
||||
@@ -182,7 +183,7 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
|
||||
@Override
|
||||
public void handleNextItems(final ListExtractor.InfoItemsPage result) {
|
||||
super.handleNextItems(result);
|
||||
currentNextPageUrl = result.getNextPageUrl();
|
||||
currentNextPage = result.getNextPage();
|
||||
infoListAdapter.addInfoItemList(result.getItems());
|
||||
|
||||
showListFooter(hasMoreItems());
|
||||
@@ -190,7 +191,7 @@ public abstract class BaseListInfoFragment<I extends ListInfo>
|
||||
|
||||
@Override
|
||||
protected boolean hasMoreItems() {
|
||||
return !TextUtils.isEmpty(currentNextPageUrl);
|
||||
return Page.isValid(currentNextPage);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -21,6 +21,7 @@ import android.widget.TextView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.jakewharton.rxbinding2.view.RxView;
|
||||
@@ -38,6 +39,7 @@ import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
|
||||
import org.schabi.newpipe.local.subscription.SubscriptionManager;
|
||||
import org.schabi.newpipe.player.playqueue.ChannelPlayQueue;
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
||||
import org.schabi.newpipe.report.ErrorActivity;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
import org.schabi.newpipe.util.AnimationUtils;
|
||||
import org.schabi.newpipe.util.ExtractorHelper;
|
||||
@@ -45,6 +47,7 @@ import org.schabi.newpipe.util.ImageDisplayConstants;
|
||||
import org.schabi.newpipe.util.Localization;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.ShareUtils;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
@@ -65,7 +68,8 @@ import static org.schabi.newpipe.util.AnimationUtils.animateBackgroundColor;
|
||||
import static org.schabi.newpipe.util.AnimationUtils.animateTextColor;
|
||||
import static org.schabi.newpipe.util.AnimationUtils.animateView;
|
||||
|
||||
public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||
public class ChannelFragment extends BaseListInfoFragment<ChannelInfo>
|
||||
implements View.OnClickListener {
|
||||
private static final int BUTTON_DEBOUNCE_INTERVAL = 100;
|
||||
private final CompositeDisposable disposables = new CompositeDisposable();
|
||||
private Disposable subscribeButtonMonitor;
|
||||
@@ -79,6 +83,8 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||
private ImageView headerChannelBanner;
|
||||
private ImageView headerAvatarView;
|
||||
private TextView headerTitleView;
|
||||
private ImageView headerSubChannelAvatarView;
|
||||
private TextView headerSubChannelTitleView;
|
||||
private TextView headerSubscribersTextView;
|
||||
private Button headerSubscribeButton;
|
||||
private View playlistCtrl;
|
||||
@@ -156,7 +162,10 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||
headerSubscribersTextView = headerRootLayout.findViewById(R.id.channel_subscriber_view);
|
||||
headerSubscribeButton = headerRootLayout.findViewById(R.id.channel_subscribe_button);
|
||||
playlistCtrl = headerRootLayout.findViewById(R.id.playlist_control);
|
||||
|
||||
headerSubChannelAvatarView =
|
||||
headerRootLayout.findViewById(R.id.sub_channel_avatar_view);
|
||||
headerSubChannelTitleView =
|
||||
headerRootLayout.findViewById(R.id.sub_channel_title_view);
|
||||
|
||||
headerPlayAllButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_all_button);
|
||||
headerPopupButton = headerRootLayout.findViewById(R.id.playlist_ctrl_play_popup_button);
|
||||
@@ -165,6 +174,14 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||
return headerRootLayout;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initListeners() {
|
||||
super.initListeners();
|
||||
|
||||
headerSubChannelTitleView.setOnClickListener(this);
|
||||
headerSubChannelAvatarView.setOnClickListener(this);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Menu
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
@@ -358,8 +375,8 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||
int backgroundDuration = isButtonVisible ? 300 : 0;
|
||||
int textDuration = isButtonVisible ? 200 : 0;
|
||||
|
||||
int subscribeBackground = ContextCompat
|
||||
.getColor(activity, R.color.subscribe_background_color);
|
||||
int subscribeBackground = ThemeHelper
|
||||
.resolveColorFromAttr(activity, R.attr.colorPrimary);
|
||||
int subscribeText = ContextCompat.getColor(activity, R.color.subscribe_text_color);
|
||||
int subscribedBackground = ContextCompat
|
||||
.getColor(activity, R.color.subscribed_background_color);
|
||||
@@ -386,7 +403,7 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||
|
||||
@Override
|
||||
protected Single<ListExtractor.InfoItemsPage> loadMoreItemsLogic() {
|
||||
return ExtractorHelper.getMoreChannelItems(serviceId, url, currentNextPageUrl);
|
||||
return ExtractorHelper.getMoreChannelItems(serviceId, url, currentNextPage);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -394,6 +411,34 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||
return ExtractorHelper.getChannelInfo(serviceId, url, forceLoad);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// OnClick
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void onClick(final View v) {
|
||||
if (isLoading.get() || currentInfo == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (v.getId()) {
|
||||
case R.id.sub_channel_avatar_view:
|
||||
case R.id.sub_channel_title_view:
|
||||
if (!TextUtils.isEmpty(currentInfo.getParentChannelUrl())) {
|
||||
try {
|
||||
NavigationHelper.openChannelFragment(getFragmentManager(),
|
||||
currentInfo.getServiceId(), currentInfo.getParentChannelUrl(),
|
||||
currentInfo.getParentChannelName());
|
||||
} catch (Exception e) {
|
||||
ErrorActivity.reportUiError((AppCompatActivity) getActivity(), e);
|
||||
}
|
||||
} else if (DEBUG) {
|
||||
Log.i(TAG, "Can't open parent channel because we got no channel URL");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Contract
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
@@ -404,6 +449,7 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||
|
||||
IMAGE_LOADER.cancelDisplayTask(headerChannelBanner);
|
||||
IMAGE_LOADER.cancelDisplayTask(headerAvatarView);
|
||||
IMAGE_LOADER.cancelDisplayTask(headerSubChannelAvatarView);
|
||||
animateView(headerSubscribeButton, false, 100);
|
||||
}
|
||||
|
||||
@@ -416,6 +462,8 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||
ImageDisplayConstants.DISPLAY_BANNER_OPTIONS);
|
||||
IMAGE_LOADER.displayImage(result.getAvatarUrl(), headerAvatarView,
|
||||
ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS);
|
||||
IMAGE_LOADER.displayImage(result.getParentChannelAvatarUrl(), headerSubChannelAvatarView,
|
||||
ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS);
|
||||
|
||||
headerSubscribersTextView.setVisibility(View.VISIBLE);
|
||||
if (result.getSubscriberCount() >= 0) {
|
||||
@@ -425,6 +473,17 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||
headerSubscribersTextView.setText(R.string.subscribers_count_not_available);
|
||||
}
|
||||
|
||||
if (!TextUtils.isEmpty(currentInfo.getParentChannelName())) {
|
||||
headerSubChannelTitleView.setText(String.format(
|
||||
getString(R.string.channel_created_by),
|
||||
currentInfo.getParentChannelName())
|
||||
);
|
||||
headerSubChannelTitleView.setVisibility(View.VISIBLE);
|
||||
headerSubChannelAvatarView.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
headerSubChannelTitleView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (menuRssButton != null) {
|
||||
menuRssButton.setVisible(!TextUtils.isEmpty(result.getFeedUrl()));
|
||||
}
|
||||
@@ -496,7 +555,7 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||
}
|
||||
}
|
||||
return new ChannelPlayQueue(currentInfo.getServiceId(), currentInfo.getUrl(),
|
||||
currentInfo.getNextPageUrl(), streamItems, index);
|
||||
currentInfo.getNextPage(), streamItems, index);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -71,7 +71,7 @@ public class CommentsFragment extends BaseListInfoFragment<CommentsInfo> {
|
||||
|
||||
@Override
|
||||
protected Single<ListExtractor.InfoItemsPage> loadMoreItemsLogic() {
|
||||
return ExtractorHelper.getMoreCommentItems(serviceId, currentInfo, currentNextPageUrl);
|
||||
return ExtractorHelper.getMoreCommentItems(serviceId, currentInfo, currentNextPage);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -44,7 +44,7 @@ public class DefaultKioskFragment extends KioskFragment {
|
||||
name = kioskTranslatedName;
|
||||
|
||||
currentInfo = null;
|
||||
currentNextPageUrl = null;
|
||||
currentNextPage = null;
|
||||
} catch (ExtractionException e) {
|
||||
onUnrecoverableError(e, UserAction.REQUESTED_KIOSK, "none",
|
||||
"Loading default kiosk from selected service", 0);
|
||||
|
||||
@@ -150,7 +150,7 @@ public class KioskFragment extends BaseListInfoFragment<KioskInfo> {
|
||||
|
||||
@Override
|
||||
public Single<ListExtractor.InfoItemsPage> loadMoreItemsLogic() {
|
||||
return ExtractorHelper.getMoreKioskItems(serviceId, url, currentNextPageUrl);
|
||||
return ExtractorHelper.getMoreKioskItems(serviceId, url, currentNextPage);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -229,7 +229,7 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
||||
|
||||
@Override
|
||||
protected Single<ListExtractor.InfoItemsPage> loadMoreItemsLogic() {
|
||||
return ExtractorHelper.getMorePlaylistItems(serviceId, url, currentNextPageUrl);
|
||||
return ExtractorHelper.getMorePlaylistItems(serviceId, url, currentNextPage);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -349,7 +349,7 @@ public class PlaylistFragment extends BaseListInfoFragment<PlaylistInfo> {
|
||||
return new PlaylistPlayQueue(
|
||||
currentInfo.getServiceId(),
|
||||
currentInfo.getUrl(),
|
||||
currentInfo.getNextPageUrl(),
|
||||
currentInfo.getNextPage(),
|
||||
infoItems,
|
||||
index
|
||||
);
|
||||
|
||||
@@ -7,6 +7,7 @@ import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.text.Editable;
|
||||
import android.text.Html;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.Log;
|
||||
@@ -37,16 +38,17 @@ import org.schabi.newpipe.database.history.model.SearchHistoryEntry;
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.ListExtractor;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.Page;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.search.SearchExtractor;
|
||||
import org.schabi.newpipe.extractor.search.SearchInfo;
|
||||
import org.schabi.newpipe.util.AndroidTvUtils;
|
||||
import org.schabi.newpipe.fragments.BackPressable;
|
||||
import org.schabi.newpipe.fragments.list.BaseListFragment;
|
||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||
import org.schabi.newpipe.report.ErrorActivity;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
import org.schabi.newpipe.util.AndroidTvUtils;
|
||||
import org.schabi.newpipe.util.AnimationUtils;
|
||||
import org.schabi.newpipe.util.Constants;
|
||||
import org.schabi.newpipe.util.ExtractorHelper;
|
||||
@@ -71,6 +73,7 @@ import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import io.reactivex.subjects.PublishSubject;
|
||||
|
||||
import static android.text.Html.escapeHtml;
|
||||
import static androidx.recyclerview.widget.ItemTouchHelper.Callback.makeMovementFlags;
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.schabi.newpipe.util.AnimationUtils.animateView;
|
||||
@@ -118,13 +121,18 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
@State
|
||||
String lastSearchedString;
|
||||
|
||||
@State
|
||||
String searchSuggestion;
|
||||
|
||||
@State
|
||||
boolean isCorrectedSearch;
|
||||
|
||||
@State
|
||||
boolean wasSearchFocused = false;
|
||||
|
||||
private Map<Integer, String> menuItemToFilterName;
|
||||
private StreamingService service;
|
||||
private String currentPageUrl;
|
||||
private String nextPageUrl;
|
||||
private Page nextPage;
|
||||
private String contentCountry;
|
||||
private boolean isSuggestionsEnabled = true;
|
||||
|
||||
@@ -143,6 +151,8 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
private EditText searchEditText;
|
||||
private View searchClear;
|
||||
|
||||
private TextView correctSuggestion;
|
||||
|
||||
private View suggestionsPanel;
|
||||
private RecyclerView suggestionsRecyclerView;
|
||||
|
||||
@@ -257,6 +267,8 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
}
|
||||
}
|
||||
|
||||
handleSearchSuggestion();
|
||||
|
||||
if (suggestionDisposable == null || suggestionDisposable.isDisposed()) {
|
||||
initSuggestionObserver();
|
||||
}
|
||||
@@ -345,6 +357,8 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
searchToolbarContainer = activity.findViewById(R.id.toolbar_search_container);
|
||||
searchEditText = searchToolbarContainer.findViewById(R.id.toolbar_search_edit_text);
|
||||
searchClear = searchToolbarContainer.findViewById(R.id.toolbar_search_clear);
|
||||
|
||||
correctSuggestion = rootView.findViewById(R.id.correct_suggestion);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
@@ -354,15 +368,13 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
@Override
|
||||
public void writeTo(final Queue<Object> objectsToSave) {
|
||||
super.writeTo(objectsToSave);
|
||||
objectsToSave.add(currentPageUrl);
|
||||
objectsToSave.add(nextPageUrl);
|
||||
objectsToSave.add(nextPage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readFrom(@NonNull final Queue<Object> savedObjects) throws Exception {
|
||||
super.readFrom(savedObjects);
|
||||
currentPageUrl = (String) savedObjects.poll();
|
||||
nextPageUrl = (String) savedObjects.poll();
|
||||
nextPage = (Page) savedObjects.poll();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -497,6 +509,8 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
return;
|
||||
}
|
||||
|
||||
correctSuggestion.setVisibility(View.GONE);
|
||||
|
||||
searchEditText.setText("");
|
||||
suggestionListAdapter.setItems(new ArrayList<>());
|
||||
showKeyboardSearch();
|
||||
@@ -554,11 +568,13 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
textWatcher = new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(final CharSequence s, final int start,
|
||||
final int count, final int after) { }
|
||||
final int count, final int after) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(final CharSequence s, final int start,
|
||||
final int before, final int count) { }
|
||||
final int before, final int count) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(final Editable s) {
|
||||
@@ -688,10 +704,6 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
return false;
|
||||
}
|
||||
|
||||
public void giveSearchEditTextFocus() {
|
||||
showKeyboardSearch();
|
||||
}
|
||||
|
||||
private void initSuggestionObserver() {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "initSuggestionObserver() called");
|
||||
@@ -845,7 +857,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
|
||||
@Override
|
||||
protected void loadMoreItems() {
|
||||
if (nextPageUrl == null || nextPageUrl.isEmpty()) {
|
||||
if (!Page.isValid(nextPage)) {
|
||||
return;
|
||||
}
|
||||
isLoading.set(true);
|
||||
@@ -858,7 +870,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
searchString,
|
||||
asList(contentFilter),
|
||||
sortFilter,
|
||||
nextPageUrl)
|
||||
nextPage)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.doOnEvent((nextItemsResult, throwable) -> isLoading.set(false))
|
||||
@@ -961,9 +973,13 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
NewPipe.getNameOfService(serviceId), searchString, 0);
|
||||
}
|
||||
|
||||
searchSuggestion = result.getSearchSuggestion();
|
||||
isCorrectedSearch = result.isCorrectedSearch();
|
||||
|
||||
handleSearchSuggestion();
|
||||
|
||||
lastSearchedString = searchString;
|
||||
nextPageUrl = result.getNextPageUrl();
|
||||
currentPageUrl = result.getUrl();
|
||||
nextPage = result.getNextPage();
|
||||
|
||||
if (infoListAdapter.getItemsList().size() == 0) {
|
||||
if (!result.getRelatedItems().isEmpty()) {
|
||||
@@ -978,17 +994,49 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
super.handleResult(result);
|
||||
}
|
||||
|
||||
private void handleSearchSuggestion() {
|
||||
if (TextUtils.isEmpty(searchSuggestion)) {
|
||||
correctSuggestion.setVisibility(View.GONE);
|
||||
} else {
|
||||
final String helperText = getString(isCorrectedSearch
|
||||
? R.string.search_showing_result_for
|
||||
: R.string.did_you_mean);
|
||||
|
||||
final String highlightedSearchSuggestion =
|
||||
"<b><i>" + escapeHtml(searchSuggestion) + "</i></b>";
|
||||
correctSuggestion.setText(
|
||||
Html.fromHtml(String.format(helperText, highlightedSearchSuggestion)));
|
||||
|
||||
|
||||
correctSuggestion.setOnClickListener(v -> {
|
||||
correctSuggestion.setVisibility(View.GONE);
|
||||
search(searchSuggestion, contentFilter, sortFilter);
|
||||
searchEditText.setText(searchSuggestion);
|
||||
});
|
||||
|
||||
correctSuggestion.setOnLongClickListener(v -> {
|
||||
searchEditText.setText(searchSuggestion);
|
||||
searchEditText.setSelection(searchSuggestion.length());
|
||||
showKeyboardSearch();
|
||||
return true;
|
||||
});
|
||||
|
||||
correctSuggestion.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleNextItems(final ListExtractor.InfoItemsPage result) {
|
||||
showListFooter(false);
|
||||
currentPageUrl = result.getNextPageUrl();
|
||||
infoListAdapter.addInfoItemList(result.getItems());
|
||||
nextPageUrl = result.getNextPageUrl();
|
||||
nextPage = result.getNextPage();
|
||||
|
||||
if (!result.getErrors().isEmpty()) {
|
||||
showSnackBarError(result.getErrors(), UserAction.SEARCHED,
|
||||
NewPipe.getNameOfService(serviceId),
|
||||
"\"" + searchString + "\" → page: " + nextPageUrl, 0);
|
||||
"\"" + searchString + "\" → pageUrl: " + nextPage.getUrl() + ", "
|
||||
+ "pageIds: " + nextPage.getIds() + ", "
|
||||
+ "pageCookies: " + nextPage.getCookies(), 0);
|
||||
}
|
||||
super.handleNextItems(result);
|
||||
}
|
||||
@@ -1020,6 +1068,10 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
|
||||
public int getSuggestionMovementFlags(@NonNull final RecyclerView recyclerView,
|
||||
@NonNull final RecyclerView.ViewHolder viewHolder) {
|
||||
final int position = viewHolder.getAdapterPosition();
|
||||
if (position == RecyclerView.NO_POSITION) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
final SuggestionItem item = suggestionListAdapter.getItem(position);
|
||||
return item.fromHistory ? makeMovementFlags(0,
|
||||
ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) : 0;
|
||||
|
||||
@@ -117,8 +117,8 @@ public class SuggestionListAdapter
|
||||
queryView = rootView.findViewById(R.id.suggestion_search);
|
||||
insertView = rootView.findViewById(R.id.suggestion_insert);
|
||||
|
||||
historyResId = resolveResourceIdFromAttr(rootView.getContext(), R.attr.history);
|
||||
searchResId = resolveResourceIdFromAttr(rootView.getContext(), R.attr.search);
|
||||
historyResId = resolveResourceIdFromAttr(rootView.getContext(), R.attr.ic_history);
|
||||
searchResId = resolveResourceIdFromAttr(rootView.getContext(), R.attr.ic_search);
|
||||
}
|
||||
|
||||
private static int resolveResourceIdFromAttr(final Context context,
|
||||
|
||||
@@ -48,6 +48,6 @@ public class CommentsInfoItemHolder extends CommentsMiniInfoItemHolder {
|
||||
}
|
||||
final CommentsInfoItem item = (CommentsInfoItem) infoItem;
|
||||
|
||||
itemTitleView.setText(item.getAuthorName());
|
||||
itemTitleView.setText(item.getUploaderName());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
package org.schabi.newpipe.info_list.holder;
|
||||
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.text.style.URLSpan;
|
||||
import android.text.util.Linkify;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import org.jsoup.helper.StringUtil;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
|
||||
@@ -24,6 +20,7 @@ import org.schabi.newpipe.util.CommentTextOnTouchListener;
|
||||
import org.schabi.newpipe.util.ImageDisplayConstants;
|
||||
import org.schabi.newpipe.util.Localization;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.ShareUtils;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -89,7 +86,7 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
|
||||
final CommentsInfoItem item = (CommentsInfoItem) infoItem;
|
||||
|
||||
itemBuilder.getImageLoader()
|
||||
.displayImage(item.getAuthorThumbnail(),
|
||||
.displayImage(item.getUploaderAvatarUrl(),
|
||||
itemThumbnailView,
|
||||
ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS);
|
||||
|
||||
@@ -114,10 +111,10 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
|
||||
itemLikesCountView.setText("-");
|
||||
}
|
||||
|
||||
if (item.getPublishedTime() != null) {
|
||||
itemPublishedTime.setText(Localization.relativeTime(item.getPublishedTime().date()));
|
||||
if (item.getUploadDate() != null) {
|
||||
itemPublishedTime.setText(Localization.relativeTime(item.getUploadDate().date()));
|
||||
} else {
|
||||
itemPublishedTime.setText(item.getTextualPublishedTime());
|
||||
itemPublishedTime.setText(item.getTextualUploadDate());
|
||||
}
|
||||
|
||||
itemView.setOnClickListener(view -> {
|
||||
@@ -129,21 +126,17 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
|
||||
|
||||
|
||||
itemView.setOnLongClickListener(view -> {
|
||||
if (!AndroidTvUtils.isTv(itemBuilder.getContext())) {
|
||||
ClipboardManager clipboardManager = (ClipboardManager) itemBuilder.getContext()
|
||||
.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
clipboardManager.setPrimaryClip(ClipData.newPlainText(null, commentText));
|
||||
Toast.makeText(itemBuilder.getContext(), R.string.msg_copied, Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
} else {
|
||||
if (AndroidTvUtils.isTv(itemBuilder.getContext())) {
|
||||
openCommentAuthor(item);
|
||||
} else {
|
||||
ShareUtils.copyToClipboard(itemBuilder.getContext(), commentText);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void openCommentAuthor(final CommentsInfoItem item) {
|
||||
if (StringUtil.isBlank(item.getAuthorEndpoint())) {
|
||||
if (TextUtils.isEmpty(item.getUploaderUrl())) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
@@ -151,8 +144,8 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
|
||||
NavigationHelper.openChannelFragment(
|
||||
activity.getSupportFragmentManager(),
|
||||
item.getServiceId(),
|
||||
item.getAuthorEndpoint(),
|
||||
item.getAuthorName());
|
||||
item.getUploaderUrl(),
|
||||
item.getUploaderName());
|
||||
} catch (Exception e) {
|
||||
ErrorActivity.reportUiError((AppCompatActivity) itemBuilder.getContext(), e);
|
||||
}
|
||||
|
||||
@@ -30,8 +30,6 @@ import org.schabi.newpipe.report.UserAction;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.OnClickGesture;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import icepick.State;
|
||||
@@ -54,31 +52,6 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
||||
// Fragment LifeCycle - Creation
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private static List<PlaylistLocalItem> merge(
|
||||
final List<PlaylistMetadataEntry> localPlaylists,
|
||||
final List<PlaylistRemoteEntity> remotePlaylists) {
|
||||
List<PlaylistLocalItem> items = new ArrayList<>(
|
||||
localPlaylists.size() + remotePlaylists.size());
|
||||
items.addAll(localPlaylists);
|
||||
items.addAll(remotePlaylists);
|
||||
|
||||
Collections.sort(items, (left, right) -> {
|
||||
String on1 = left.getOrderingName();
|
||||
String on2 = right.getOrderingName();
|
||||
if (on1 == null && on2 == null) {
|
||||
return 0;
|
||||
} else if (on1 != null && on2 == null) {
|
||||
return -1;
|
||||
} else if (on1 == null && on2 != null) {
|
||||
return 1;
|
||||
} else {
|
||||
return on1.compareToIgnoreCase(on2);
|
||||
}
|
||||
});
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
@@ -164,7 +137,7 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
||||
super.startLoading(forceLoad);
|
||||
|
||||
Flowable.combineLatest(localPlaylistManager.getPlaylists(),
|
||||
remotePlaylistManager.getPlaylists(), BookmarkFragment::merge)
|
||||
remotePlaylistManager.getPlaylists(), PlaylistLocalItem::merge)
|
||||
.onBackpressureLatest()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(getPlaylistsSubscriber());
|
||||
|
||||
@@ -7,6 +7,8 @@ import io.reactivex.Flowable
|
||||
import io.reactivex.Maybe
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import java.util.Calendar
|
||||
import java.util.Date
|
||||
import org.schabi.newpipe.MainActivity.DEBUG
|
||||
import org.schabi.newpipe.NewPipeDatabase
|
||||
import org.schabi.newpipe.database.feed.model.FeedEntity
|
||||
@@ -16,8 +18,6 @@ import org.schabi.newpipe.database.stream.model.StreamEntity
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem
|
||||
import org.schabi.newpipe.extractor.stream.StreamType
|
||||
import org.schabi.newpipe.local.subscription.FeedGroupIcon
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
class FeedDatabaseManager(context: Context) {
|
||||
private val database = NewPipeDatabase.getInstance(context)
|
||||
@@ -70,8 +70,11 @@ class FeedDatabaseManager(context: Context) {
|
||||
fun markAsOutdated(subscriptionId: Long) = feedTable
|
||||
.setLastUpdatedForSubscription(FeedLastUpdatedEntity(subscriptionId, null))
|
||||
|
||||
fun upsertAll(subscriptionId: Long, items: List<StreamInfoItem>,
|
||||
oldestAllowedDate: Date = FEED_OLDEST_ALLOWED_DATE.time) {
|
||||
fun upsertAll(
|
||||
subscriptionId: Long,
|
||||
items: List<StreamInfoItem>,
|
||||
oldestAllowedDate: Date = FEED_OLDEST_ALLOWED_DATE.time
|
||||
) {
|
||||
val itemsToInsert = ArrayList<StreamInfoItem>()
|
||||
loop@ for (streamItem in items) {
|
||||
val uploadDate = streamItem.uploadDate
|
||||
@@ -107,9 +110,9 @@ class FeedDatabaseManager(context: Context) {
|
||||
if (DEBUG) Log.d(this::class.java.simpleName, "clear() → streamTable.deleteOrphans() → $deletedOrphans")
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
// Feed Groups
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
|
||||
fun subscriptionIdsForGroup(groupId: Long): Flowable<List<Long>> {
|
||||
return feedGroupTable.getSubscriptionIdsFor(groupId)
|
||||
@@ -161,6 +164,5 @@ class FeedDatabaseManager(context: Context) {
|
||||
FeedGroupEntity.GROUP_ALL_ID -> feedTable.oldestSubscriptionUpdateFromAll()
|
||||
else -> feedTable.oldestSubscriptionUpdate(groupId)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,14 +22,28 @@ package org.schabi.newpipe.local.feed
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import android.view.*
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import androidx.preference.PreferenceManager
|
||||
import icepick.State
|
||||
import kotlinx.android.synthetic.main.error_retry.*
|
||||
import kotlinx.android.synthetic.main.fragment_feed.*
|
||||
import java.util.Calendar
|
||||
import kotlinx.android.synthetic.main.error_retry.error_button_retry
|
||||
import kotlinx.android.synthetic.main.error_retry.error_message_view
|
||||
import kotlinx.android.synthetic.main.fragment_feed.empty_state_view
|
||||
import kotlinx.android.synthetic.main.fragment_feed.error_panel
|
||||
import kotlinx.android.synthetic.main.fragment_feed.items_list
|
||||
import kotlinx.android.synthetic.main.fragment_feed.loading_progress_bar
|
||||
import kotlinx.android.synthetic.main.fragment_feed.loading_progress_text
|
||||
import kotlinx.android.synthetic.main.fragment_feed.refresh_root_view
|
||||
import kotlinx.android.synthetic.main.fragment_feed.refresh_subtitle_text
|
||||
import kotlinx.android.synthetic.main.fragment_feed.refresh_text
|
||||
import org.schabi.newpipe.R
|
||||
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
|
||||
import org.schabi.newpipe.fragments.list.BaseListFragment
|
||||
@@ -37,7 +51,6 @@ import org.schabi.newpipe.local.feed.service.FeedLoadService
|
||||
import org.schabi.newpipe.report.UserAction
|
||||
import org.schabi.newpipe.util.AnimationUtils.animateView
|
||||
import org.schabi.newpipe.util.Localization
|
||||
import java.util.*
|
||||
|
||||
class FeedFragment : BaseListFragment<FeedState, Unit>() {
|
||||
private lateinit var viewModel: FeedViewModel
|
||||
@@ -98,9 +111,9 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
// Menu
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
super.onCreateOptionsMenu(menu, inflater)
|
||||
@@ -150,9 +163,9 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
|
||||
activity?.supportActionBar?.subtitle = null
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
// Handling
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
|
||||
override fun showLoading() {
|
||||
animateView(refresh_root_view, false, 0)
|
||||
@@ -259,7 +272,6 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun handleErrorState(errorState: FeedState.ErrorState): Boolean {
|
||||
hideLoading()
|
||||
errorState.error?.let {
|
||||
@@ -283,9 +295,9 @@ class FeedFragment : BaseListFragment<FeedState, Unit>() {
|
||||
refresh_text?.text = getString(R.string.feed_oldest_subscription_update, oldestSubscriptionUpdateText)
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
// Load Service Handling
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
|
||||
override fun doInitialLoadLogic() {}
|
||||
override fun reloadContent() = triggerUpdate()
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
package org.schabi.newpipe.local.feed
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import java.util.Calendar
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem
|
||||
import java.util.*
|
||||
|
||||
sealed class FeedState {
|
||||
data class ProgressState(
|
||||
val currentProgress: Int = -1,
|
||||
val maxProgress: Int = -1,
|
||||
@StringRes val progressMessage: Int = 0
|
||||
val currentProgress: Int = -1,
|
||||
val maxProgress: Int = -1,
|
||||
@StringRes val progressMessage: Int = 0
|
||||
) : FeedState()
|
||||
|
||||
data class LoadedState(
|
||||
val items: List<StreamInfoItem>,
|
||||
val oldestUpdate: Calendar? = null,
|
||||
val notLoadedCount: Long,
|
||||
val itemsErrors: List<Throwable> = emptyList()
|
||||
val items: List<StreamInfoItem>,
|
||||
val oldestUpdate: Calendar? = null,
|
||||
val notLoadedCount: Long,
|
||||
val itemsErrors: List<Throwable> = emptyList()
|
||||
) : FeedState()
|
||||
|
||||
data class ErrorState(
|
||||
val error: Throwable? = null
|
||||
val error: Throwable? = null
|
||||
) : FeedState()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,13 +9,17 @@ import io.reactivex.Flowable
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.functions.Function4
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import java.util.Calendar
|
||||
import java.util.Date
|
||||
import java.util.concurrent.TimeUnit
|
||||
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem
|
||||
import org.schabi.newpipe.local.feed.service.FeedEventManager
|
||||
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.*
|
||||
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.ErrorResultEvent
|
||||
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.IdleEvent
|
||||
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.ProgressEvent
|
||||
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.SuccessResultEvent
|
||||
import org.schabi.newpipe.util.DEFAULT_THROTTLE_TIMEOUT
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class FeedViewModel(applicationContext: Context, val groupId: Long = FeedGroupEntity.GROUP_ALL_ID) : ViewModel() {
|
||||
class Factory(val context: Context, val groupId: Long = FeedGroupEntity.GROUP_ALL_ID) : ViewModelProvider.Factory {
|
||||
@@ -68,4 +72,4 @@ class FeedViewModel(applicationContext: Context, val groupId: Long = FeedGroupEn
|
||||
}
|
||||
|
||||
private data class CombineResultHolder(val t1: FeedEventManager.Event, val t2: List<StreamInfoItem>, val t3: Long, val t4: Date?)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ package org.schabi.newpipe.local.feed.service
|
||||
import androidx.annotation.StringRes
|
||||
import io.reactivex.Flowable
|
||||
import io.reactivex.processors.BehaviorProcessor
|
||||
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.IdleEvent
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.IdleEvent
|
||||
|
||||
object FeedEventManager {
|
||||
private var processor: BehaviorProcessor<Event> = BehaviorProcessor.create()
|
||||
@@ -34,5 +34,4 @@ object FeedEventManager {
|
||||
data class SuccessResultEvent(val itemsErrors: List<Throwable> = emptyList()) : Event()
|
||||
data class ErrorResultEvent(val error: Throwable) : Event()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -40,6 +40,11 @@ import io.reactivex.functions.Consumer
|
||||
import io.reactivex.functions.Function
|
||||
import io.reactivex.processors.PublishProcessor
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import java.io.IOException
|
||||
import java.util.Calendar
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import org.reactivestreams.Subscriber
|
||||
import org.reactivestreams.Subscription
|
||||
import org.schabi.newpipe.MainActivity.DEBUG
|
||||
@@ -49,17 +54,14 @@ import org.schabi.newpipe.extractor.ListInfo
|
||||
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem
|
||||
import org.schabi.newpipe.local.feed.FeedDatabaseManager
|
||||
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.*
|
||||
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.ErrorResultEvent
|
||||
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.IdleEvent
|
||||
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.ProgressEvent
|
||||
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.SuccessResultEvent
|
||||
import org.schabi.newpipe.local.feed.service.FeedEventManager.postEvent
|
||||
import org.schabi.newpipe.local.subscription.SubscriptionManager
|
||||
import org.schabi.newpipe.util.ExceptionUtils
|
||||
import org.schabi.newpipe.util.ExtractorHelper
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
class FeedLoadService : Service() {
|
||||
companion object {
|
||||
@@ -94,9 +96,9 @@ class FeedLoadService : Service() {
|
||||
private var disposables = CompositeDisposable()
|
||||
private var notificationUpdater = PublishProcessor.create<String>()
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
// Lifecycle
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
@@ -151,9 +153,9 @@ class FeedLoadService : Service() {
|
||||
return null
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
// Loading & Handling
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private class RequestException(val subscriptionId: Long, message: String, cause: Throwable) : Exception(message, cause) {
|
||||
companion object {
|
||||
@@ -312,7 +314,6 @@ class FeedLoadService : Service() {
|
||||
feedResultsHolder.addErrors(RequestException.wrapList(subscriptionId, info))
|
||||
feedDatabaseManager.markAsOutdated(subscriptionId)
|
||||
}
|
||||
|
||||
} else if (notification.isOnError) {
|
||||
val error = notification.error!!
|
||||
feedResultsHolder.addError(error)
|
||||
@@ -325,7 +326,6 @@ class FeedLoadService : Service() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private val errorHandlingConsumer: Consumer<Notification<Pair<Long, ListInfo<StreamInfoItem>>>>
|
||||
get() = Consumer {
|
||||
if (it.isOnError) {
|
||||
@@ -354,9 +354,9 @@ class FeedLoadService : Service() {
|
||||
broadcastProgress()
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
// Notification
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private lateinit var notificationManager: NotificationManagerCompat
|
||||
private lateinit var notificationBuilder: NotificationCompat.Builder
|
||||
@@ -412,9 +412,9 @@ class FeedLoadService : Service() {
|
||||
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build())
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
// Notification Actions
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private lateinit var broadcastReceiver: BroadcastReceiver
|
||||
private val cancelSignal = AtomicBoolean()
|
||||
@@ -430,18 +430,18 @@ class FeedLoadService : Service() {
|
||||
registerReceiver(broadcastReceiver, IntentFilter(ACTION_CANCEL))
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
// Error handling
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private fun handleError(error: Throwable) {
|
||||
postEvent(ErrorResultEvent(error))
|
||||
stopService()
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
// Results Holder
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class ResultsHolder {
|
||||
/**
|
||||
@@ -463,4 +463,4 @@ class FeedLoadService : Service() {
|
||||
itemsErrors = itemsErrorsHolder.toList()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -362,14 +362,14 @@ public class StatisticsPlaylistFragment
|
||||
if (sortMode == StatisticSortMode.LAST_PLAYED) {
|
||||
sortMode = StatisticSortMode.MOST_PLAYED;
|
||||
setTitle(getString(R.string.title_most_played));
|
||||
sortButtonIcon
|
||||
.setImageResource(ThemeHelper.getIconByAttr(R.attr.history, getContext()));
|
||||
sortButtonIcon.setImageResource(
|
||||
ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_history));
|
||||
sortButtonText.setText(R.string.title_last_played);
|
||||
} else {
|
||||
sortMode = StatisticSortMode.LAST_PLAYED;
|
||||
setTitle(getString(R.string.title_last_played));
|
||||
sortButtonIcon
|
||||
.setImageResource(ThemeHelper.getIconByAttr(R.attr.filter, getContext()));
|
||||
sortButtonIcon.setImageResource(
|
||||
ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_filter_list));
|
||||
sortButtonText.setText(R.string.title_most_played);
|
||||
}
|
||||
startLoading(true);
|
||||
|
||||
@@ -7,23 +7,23 @@ import org.schabi.newpipe.R
|
||||
import org.schabi.newpipe.util.ThemeHelper
|
||||
|
||||
enum class FeedGroupIcon(
|
||||
/**
|
||||
* The id that will be used to store and retrieve icons from some persistent storage (e.g. DB).
|
||||
*/
|
||||
val id: Int,
|
||||
/**
|
||||
* The id that will be used to store and retrieve icons from some persistent storage (e.g. DB).
|
||||
*/
|
||||
val id: Int,
|
||||
|
||||
/**
|
||||
* The attribute that points to a drawable resource. "R.attr" is used here to support multiple themes.
|
||||
*/
|
||||
@AttrRes val drawableResourceAttr: Int
|
||||
/**
|
||||
* The attribute that points to a drawable resource. "R.attr" is used here to support multiple themes.
|
||||
*/
|
||||
@AttrRes val drawableResourceAttr: Int
|
||||
) {
|
||||
ALL(0, R.attr.ic_asterisk),
|
||||
MUSIC(1, R.attr.ic_music_note),
|
||||
EDUCATION(2, R.attr.ic_school),
|
||||
FITNESS(3, R.attr.ic_fitness),
|
||||
FITNESS(3, R.attr.ic_fitness_center),
|
||||
SPACE(4, R.attr.ic_telescope),
|
||||
COMPUTER(5, R.attr.ic_computer),
|
||||
GAMING(6, R.attr.ic_videogame),
|
||||
GAMING(6, R.attr.ic_videogame_asset),
|
||||
SPORTS(7, R.attr.ic_sports),
|
||||
NEWS(8, R.attr.ic_megaphone),
|
||||
FAVORITES(9, R.attr.ic_heart),
|
||||
@@ -32,32 +32,32 @@ enum class FeedGroupIcon(
|
||||
TREND(12, R.attr.ic_trending_up),
|
||||
MOVIE(13, R.attr.ic_movie),
|
||||
BACKUP(14, R.attr.ic_backup),
|
||||
ART(15, R.attr.palette),
|
||||
ART(15, R.attr.ic_palette),
|
||||
PERSON(16, R.attr.ic_person),
|
||||
PEOPLE(17, R.attr.ic_people),
|
||||
MONEY(18, R.attr.ic_money),
|
||||
KIDS(19, R.attr.ic_kids),
|
||||
KIDS(19, R.attr.ic_child_care),
|
||||
FOOD(20, R.attr.ic_fastfood),
|
||||
SMILE(21, R.attr.ic_smile),
|
||||
EXPLORE(22, R.attr.ic_explore),
|
||||
RESTAURANT(23, R.attr.ic_restaurant),
|
||||
MIC(24, R.attr.ic_mic),
|
||||
HEADSET(25, R.attr.audio),
|
||||
HEADSET(25, R.attr.ic_headset),
|
||||
RADIO(26, R.attr.ic_radio),
|
||||
SHOPPING_CART(27, R.attr.ic_shopping_cart),
|
||||
WATCH_LATER(28, R.attr.ic_watch_later),
|
||||
WORK(29, R.attr.ic_work),
|
||||
HOT(30, R.attr.ic_hot),
|
||||
HOT(30, R.attr.ic_kiosk_hot),
|
||||
CHANNEL(31, R.attr.ic_channel),
|
||||
BOOKMARK(32, R.attr.ic_bookmark),
|
||||
PETS(33, R.attr.ic_pets),
|
||||
WORLD(34, R.attr.ic_world),
|
||||
STAR(35, R.attr.ic_stars),
|
||||
SUN(36, R.attr.ic_sunny),
|
||||
RSS(37, R.attr.rss);
|
||||
RSS(37, R.attr.ic_rss);
|
||||
|
||||
@DrawableRes
|
||||
fun getDrawableRes(context: Context): Int {
|
||||
return ThemeHelper.resolveResourceIdFromAttr(context, drawableResourceAttr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,13 +2,21 @@ package org.schabi.newpipe.local.subscription
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.AlertDialog
|
||||
import android.content.*
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.res.Configuration
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.os.Parcelable
|
||||
import android.preference.PreferenceManager
|
||||
import android.view.*
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||
@@ -21,8 +29,15 @@ import com.xwray.groupie.Section
|
||||
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
|
||||
import icepick.State
|
||||
import io.reactivex.disposables.CompositeDisposable
|
||||
import kotlinx.android.synthetic.main.dialog_title.view.*
|
||||
import kotlinx.android.synthetic.main.fragment_subscription.*
|
||||
import java.io.File
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
import kotlin.math.floor
|
||||
import kotlin.math.max
|
||||
import kotlinx.android.synthetic.main.dialog_title.view.itemAdditionalDetails
|
||||
import kotlinx.android.synthetic.main.dialog_title.view.itemTitleView
|
||||
import kotlinx.android.synthetic.main.fragment_subscription.items_list
|
||||
import org.schabi.newpipe.R
|
||||
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
|
||||
import org.schabi.newpipe.extractor.channel.ChannelInfoItem
|
||||
@@ -30,21 +45,29 @@ import org.schabi.newpipe.fragments.BaseStateFragment
|
||||
import org.schabi.newpipe.local.subscription.SubscriptionViewModel.SubscriptionState
|
||||
import org.schabi.newpipe.local.subscription.dialog.FeedGroupDialog
|
||||
import org.schabi.newpipe.local.subscription.dialog.FeedGroupReorderDialog
|
||||
import org.schabi.newpipe.local.subscription.item.*
|
||||
import org.schabi.newpipe.local.subscription.item.ChannelItem
|
||||
import org.schabi.newpipe.local.subscription.item.EmptyPlaceholderItem
|
||||
import org.schabi.newpipe.local.subscription.item.FeedGroupAddItem
|
||||
import org.schabi.newpipe.local.subscription.item.FeedGroupCardItem
|
||||
import org.schabi.newpipe.local.subscription.item.FeedGroupCarouselItem
|
||||
import org.schabi.newpipe.local.subscription.item.FeedImportExportItem
|
||||
import org.schabi.newpipe.local.subscription.item.HeaderWithMenuItem
|
||||
import org.schabi.newpipe.local.subscription.item.HeaderWithMenuItem.Companion.PAYLOAD_UPDATE_VISIBILITY_MENU_ITEM
|
||||
import org.schabi.newpipe.local.subscription.services.SubscriptionsExportService
|
||||
import org.schabi.newpipe.local.subscription.services.SubscriptionsExportService.EXPORT_COMPLETE_ACTION
|
||||
import org.schabi.newpipe.local.subscription.services.SubscriptionsExportService.KEY_FILE_PATH
|
||||
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService
|
||||
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.*
|
||||
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.IMPORT_COMPLETE_ACTION
|
||||
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.KEY_MODE
|
||||
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.KEY_VALUE
|
||||
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.PREVIOUS_EXPORT_MODE
|
||||
import org.schabi.newpipe.report.UserAction
|
||||
import org.schabi.newpipe.util.*
|
||||
import org.schabi.newpipe.util.AnimationUtils.animateView
|
||||
import java.io.File
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import kotlin.math.floor
|
||||
import kotlin.math.max
|
||||
import org.schabi.newpipe.util.FilePickerActivityHelper
|
||||
import org.schabi.newpipe.util.NavigationHelper
|
||||
import org.schabi.newpipe.util.OnClickGesture
|
||||
import org.schabi.newpipe.util.ShareUtils
|
||||
import org.schabi.newpipe.util.ThemeHelper
|
||||
|
||||
class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
|
||||
private lateinit var viewModel: SubscriptionViewModel
|
||||
@@ -74,9 +97,9 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
|
||||
setHasOptionsMenu(true)
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
// Fragment LifeCycle
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@@ -120,9 +143,9 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
|
||||
disposables.dispose()
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// ////////////////////////////////////////////////////////////////////////
|
||||
// Menu
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// ////////////////////////////////////////////////////////////////////////
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
super.onCreateOptionsMenu(menu, inflater)
|
||||
@@ -150,7 +173,6 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
|
||||
importExportItem.isExpanded = false
|
||||
importExportItem.notifyChanged(FeedImportExportItem.REFRESH_EXPANDED_STATUS)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,9 +220,9 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// ////////////////////////////////////////////////////////////////////////
|
||||
// Fragment Views
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// ////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private fun setupInitialLayout() {
|
||||
Section().apply {
|
||||
@@ -243,7 +265,6 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
|
||||
{ onExportSelected() },
|
||||
importExportItemExpandedState ?: false)
|
||||
groupAdapter.add(Section(importExportItem, listOf(subscriptionsSection)))
|
||||
|
||||
}
|
||||
|
||||
override fun initViews(rootView: View, savedInstanceState: Bundle?) {
|
||||
@@ -366,9 +387,9 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
|
||||
items_list.post { feedGroupsSortMenuItem.notifyChanged(PAYLOAD_UPDATE_VISIBILITY_MENU_ITEM) }
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
// Contract
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
|
||||
override fun showLoading() {
|
||||
super.showLoading()
|
||||
@@ -380,9 +401,9 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
|
||||
animateView(items_list, true, 200)
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
// Fragment Error Handling
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
|
||||
override fun onError(exception: Throwable): Boolean {
|
||||
if (super.onError(exception)) return true
|
||||
@@ -391,9 +412,9 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
|
||||
return true
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
// Grid Mode
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// TODO: Move these out of this class, as it can be reused
|
||||
|
||||
@@ -405,8 +426,8 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
|
||||
getString(R.string.list_view_mode_auto_key) -> {
|
||||
val configuration = resources.configuration
|
||||
|
||||
(configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
|
||||
&& configuration.isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_LARGE))
|
||||
(configuration.orientation == Configuration.ORIENTATION_LANDSCAPE &&
|
||||
configuration.isLayoutSizeAtLeast(Configuration.SCREENLAYOUT_SIZE_LARGE))
|
||||
}
|
||||
getString(R.string.list_view_mode_grid_key) -> true
|
||||
else -> false
|
||||
|
||||
@@ -2,9 +2,11 @@ package org.schabi.newpipe.local.subscription
|
||||
|
||||
import android.content.Context
|
||||
import io.reactivex.Completable
|
||||
import io.reactivex.Flowable
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import org.schabi.newpipe.NewPipeDatabase
|
||||
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
|
||||
import org.schabi.newpipe.database.subscription.SubscriptionDAO
|
||||
import org.schabi.newpipe.database.subscription.SubscriptionEntity
|
||||
import org.schabi.newpipe.extractor.ListInfo
|
||||
@@ -21,9 +23,28 @@ class SubscriptionManager(context: Context) {
|
||||
fun subscriptionTable(): SubscriptionDAO = subscriptionTable
|
||||
fun subscriptions() = subscriptionTable.all
|
||||
|
||||
fun getSubscriptions(
|
||||
currentGroupId: Long = FeedGroupEntity.GROUP_ALL_ID,
|
||||
filterQuery: String = "",
|
||||
showOnlyUngrouped: Boolean = false
|
||||
): Flowable<List<SubscriptionEntity>> {
|
||||
return when {
|
||||
filterQuery.isNotEmpty() -> {
|
||||
return if (showOnlyUngrouped) {
|
||||
subscriptionTable.getSubscriptionsOnlyUngroupedFiltered(
|
||||
currentGroupId, filterQuery)
|
||||
} else {
|
||||
subscriptionTable.getSubscriptionsFiltered(filterQuery)
|
||||
}
|
||||
}
|
||||
showOnlyUngrouped -> subscriptionTable.getSubscriptionsOnlyUngrouped(currentGroupId)
|
||||
else -> subscriptionTable.all
|
||||
}
|
||||
}
|
||||
|
||||
fun upsertAll(infoList: List<ChannelInfo>): List<SubscriptionEntity> {
|
||||
val listEntities = subscriptionTable.upsertAll(
|
||||
infoList.map { SubscriptionEntity.from(it) })
|
||||
infoList.map { SubscriptionEntity.from(it) })
|
||||
|
||||
database.runInTransaction {
|
||||
infoList.forEachIndexed { index, info ->
|
||||
@@ -35,13 +56,13 @@ class SubscriptionManager(context: Context) {
|
||||
}
|
||||
|
||||
fun updateChannelInfo(info: ChannelInfo): Completable = subscriptionTable.getSubscription(info.serviceId, info.url)
|
||||
.flatMapCompletable {
|
||||
Completable.fromRunnable {
|
||||
it.setData(info.name, info.avatarUrl, info.description, info.subscriberCount)
|
||||
subscriptionTable.update(it)
|
||||
feedDatabaseManager.upsertAll(it.uid, info.relatedItems)
|
||||
}
|
||||
.flatMapCompletable {
|
||||
Completable.fromRunnable {
|
||||
it.setData(info.name, info.avatarUrl, info.description, info.subscriberCount)
|
||||
subscriptionTable.update(it)
|
||||
feedDatabaseManager.upsertAll(it.uid, info.relatedItems)
|
||||
}
|
||||
}
|
||||
|
||||
fun updateFromInfo(subscriptionId: Long, info: ListInfo<StreamInfoItem>) {
|
||||
val subscriptionEntity = subscriptionTable.getSubscription(subscriptionId)
|
||||
@@ -57,8 +78,8 @@ class SubscriptionManager(context: Context) {
|
||||
|
||||
fun deleteSubscription(serviceId: Int, url: String): Completable {
|
||||
return Completable.fromCallable { subscriptionTable.deleteSubscription(serviceId, url) }
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
}
|
||||
|
||||
fun insertSubscription(subscriptionEntity: SubscriptionEntity, info: ChannelInfo) {
|
||||
|
||||
@@ -6,11 +6,11 @@ import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.xwray.groupie.Group
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import java.util.concurrent.TimeUnit
|
||||
import org.schabi.newpipe.local.feed.FeedDatabaseManager
|
||||
import org.schabi.newpipe.local.subscription.item.ChannelItem
|
||||
import org.schabi.newpipe.local.subscription.item.FeedGroupCardItem
|
||||
import org.schabi.newpipe.util.DEFAULT_THROTTLE_TIMEOUT
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class SubscriptionViewModel(application: Application) : AndroidViewModel(application) {
|
||||
private var feedDatabaseManager: FeedDatabaseManager = FeedDatabaseManager(application)
|
||||
|
||||
@@ -32,4 +32,4 @@ class FeedGroupCarouselDecoration(context: Context) : RecyclerView.ItemDecoratio
|
||||
outRect.right = marginStartEnd
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import android.text.Editable
|
||||
import android.text.TextUtils
|
||||
import android.text.TextWatcher
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
@@ -13,30 +14,36 @@ import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.Toast
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.xwray.groupie.GroupAdapter
|
||||
import com.xwray.groupie.OnItemClickListener
|
||||
import com.xwray.groupie.Section
|
||||
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
|
||||
import icepick.Icepick
|
||||
import icepick.State
|
||||
import java.io.Serializable
|
||||
import kotlin.collections.contains
|
||||
import kotlinx.android.synthetic.main.dialog_feed_group_create.*
|
||||
import kotlinx.android.synthetic.main.toolbar_search_layout.*
|
||||
import org.schabi.newpipe.R
|
||||
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
|
||||
import org.schabi.newpipe.database.subscription.SubscriptionEntity
|
||||
import org.schabi.newpipe.fragments.BackPressable
|
||||
import org.schabi.newpipe.local.subscription.FeedGroupIcon
|
||||
import org.schabi.newpipe.local.subscription.dialog.FeedGroupDialog.ScreenState.*
|
||||
import org.schabi.newpipe.local.subscription.dialog.FeedGroupDialog.ScreenState.DeleteScreen
|
||||
import org.schabi.newpipe.local.subscription.dialog.FeedGroupDialog.ScreenState.IconPickerScreen
|
||||
import org.schabi.newpipe.local.subscription.dialog.FeedGroupDialog.ScreenState.InitialScreen
|
||||
import org.schabi.newpipe.local.subscription.dialog.FeedGroupDialog.ScreenState.SubscriptionsPickerScreen
|
||||
import org.schabi.newpipe.local.subscription.dialog.FeedGroupDialogViewModel.DialogEvent.ProcessingEvent
|
||||
import org.schabi.newpipe.local.subscription.dialog.FeedGroupDialogViewModel.DialogEvent.SuccessEvent
|
||||
import org.schabi.newpipe.local.subscription.item.EmptyPlaceholderItem
|
||||
import org.schabi.newpipe.local.subscription.item.PickerIconItem
|
||||
import org.schabi.newpipe.local.subscription.item.PickerSubscriptionItem
|
||||
import org.schabi.newpipe.util.AndroidTvUtils
|
||||
import org.schabi.newpipe.util.ThemeHelper
|
||||
import java.io.Serializable
|
||||
|
||||
class FeedGroupDialog : DialogFragment() {
|
||||
class FeedGroupDialog : DialogFragment(), BackPressable {
|
||||
private lateinit var viewModel: FeedGroupDialogViewModel
|
||||
private var groupId: Long = NO_GROUP_SELECTED
|
||||
private var groupIcon: FeedGroupIcon? = null
|
||||
@@ -49,22 +56,20 @@ class FeedGroupDialog : DialogFragment() {
|
||||
object DeleteScreen : ScreenState()
|
||||
}
|
||||
|
||||
@State
|
||||
@JvmField
|
||||
var selectedIcon: FeedGroupIcon? = null
|
||||
@State
|
||||
@JvmField
|
||||
var selectedSubscriptions: HashSet<Long> = HashSet()
|
||||
@State
|
||||
@JvmField
|
||||
var currentScreen: ScreenState = InitialScreen
|
||||
@State @JvmField var selectedIcon: FeedGroupIcon? = null
|
||||
@State @JvmField var selectedSubscriptions: HashSet<Long> = HashSet()
|
||||
@State @JvmField var wasSubscriptionSelectionChanged: Boolean = false
|
||||
@State @JvmField var currentScreen: ScreenState = InitialScreen
|
||||
|
||||
@State
|
||||
@JvmField
|
||||
var subscriptionsListState: Parcelable? = null
|
||||
@State
|
||||
@JvmField
|
||||
var iconsListState: Parcelable? = null
|
||||
@State @JvmField var subscriptionsListState: Parcelable? = null
|
||||
@State @JvmField var iconsListState: Parcelable? = null
|
||||
@State @JvmField var wasSearchSubscriptionsVisible = false
|
||||
@State @JvmField var subscriptionsCurrentSearchQuery = ""
|
||||
@State @JvmField var subscriptionsShowOnlyUngrouped = false
|
||||
|
||||
private val subscriptionMainSection = Section()
|
||||
private val subscriptionEmptyFooter = Section()
|
||||
private lateinit var subscriptionGroupAdapter: GroupAdapter<GroupieViewHolder>
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@@ -74,22 +79,30 @@ class FeedGroupDialog : DialogFragment() {
|
||||
groupId = arguments?.getLong(KEY_GROUP_ID, NO_GROUP_SELECTED) ?: NO_GROUP_SELECTED
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
return inflater.inflate(R.layout.dialog_feed_group_create, container)
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
return object : Dialog(requireActivity(), theme) {
|
||||
override fun onBackPressed() {
|
||||
if (currentScreen !is InitialScreen) {
|
||||
showScreen(InitialScreen)
|
||||
} else {
|
||||
if (!this@FeedGroupDialog.onBackPressed()) {
|
||||
super.onBackPressed()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
|
||||
wasSearchSubscriptionsVisible = isSearchVisible()
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
|
||||
@@ -102,11 +115,15 @@ class FeedGroupDialog : DialogFragment() {
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
viewModel = ViewModelProviders.of(this, FeedGroupDialogViewModel.Factory(requireContext(), groupId))
|
||||
.get(FeedGroupDialogViewModel::class.java)
|
||||
viewModel = ViewModelProvider(this,
|
||||
FeedGroupDialogViewModel.Factory(requireContext(),
|
||||
groupId, subscriptionsCurrentSearchQuery, subscriptionsShowOnlyUngrouped)
|
||||
).get(FeedGroupDialogViewModel::class.java)
|
||||
|
||||
viewModel.groupLiveData.observe(viewLifecycleOwner, Observer(::handleGroup))
|
||||
viewModel.subscriptionsLiveData.observe(viewLifecycleOwner, Observer { setupSubscriptionPicker(it.first, it.second) })
|
||||
viewModel.subscriptionsLiveData.observe(viewLifecycleOwner, Observer {
|
||||
setupSubscriptionPicker(it.first, it.second)
|
||||
})
|
||||
viewModel.dialogEventLiveData.observe(viewLifecycleOwner, Observer {
|
||||
when (it) {
|
||||
ProcessingEvent -> disableInput()
|
||||
@@ -114,15 +131,54 @@ class FeedGroupDialog : DialogFragment() {
|
||||
}
|
||||
})
|
||||
|
||||
subscriptionGroupAdapter = GroupAdapter<GroupieViewHolder>().apply {
|
||||
add(subscriptionMainSection)
|
||||
add(subscriptionEmptyFooter)
|
||||
spanCount = 4
|
||||
}
|
||||
subscriptions_selector_list.apply {
|
||||
// Disable animations, too distracting.
|
||||
itemAnimator = null
|
||||
adapter = subscriptionGroupAdapter
|
||||
layoutManager = GridLayoutManager(requireContext(), subscriptionGroupAdapter.spanCount,
|
||||
RecyclerView.VERTICAL, false).apply {
|
||||
spanSizeLookup = subscriptionGroupAdapter.spanSizeLookup
|
||||
}
|
||||
}
|
||||
|
||||
setupIconPicker()
|
||||
setupListeners()
|
||||
|
||||
showScreen(currentScreen)
|
||||
|
||||
if (currentScreen == SubscriptionsPickerScreen && wasSearchSubscriptionsVisible) {
|
||||
showSearch()
|
||||
} else if (currentScreen == InitialScreen && groupId == NO_GROUP_SELECTED) {
|
||||
showKeyboard()
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
subscriptions_selector_list?.adapter = null
|
||||
icon_selector?.adapter = null
|
||||
}
|
||||
|
||||
/*///////////////////////////////////////////////////////////////////////////
|
||||
// Setup
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////// */
|
||||
|
||||
override fun onBackPressed(): Boolean {
|
||||
if (currentScreen is SubscriptionsPickerScreen && isSearchVisible()) {
|
||||
hideSearch()
|
||||
return true
|
||||
} else if (currentScreen !is InitialScreen) {
|
||||
showScreen(InitialScreen)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private fun setupListeners() {
|
||||
delete_button.setOnClickListener { showScreen(DeleteScreen) }
|
||||
@@ -146,13 +202,64 @@ class FeedGroupDialog : DialogFragment() {
|
||||
}
|
||||
})
|
||||
|
||||
confirm_button.setOnClickListener {
|
||||
when (currentScreen) {
|
||||
InitialScreen -> handlePositiveButtonInitialScreen()
|
||||
DeleteScreen -> viewModel.deleteGroup()
|
||||
else -> showScreen(InitialScreen)
|
||||
confirm_button.setOnClickListener { handlePositiveButton() }
|
||||
|
||||
select_channel_button.setOnClickListener {
|
||||
subscriptions_selector_list.scrollToPosition(0)
|
||||
showScreen(SubscriptionsPickerScreen)
|
||||
}
|
||||
|
||||
val headerMenu = subscriptions_header_toolbar.menu
|
||||
requireActivity().menuInflater.inflate(R.menu.menu_feed_group_dialog, headerMenu)
|
||||
|
||||
headerMenu.findItem(R.id.action_search).setOnMenuItemClickListener {
|
||||
showSearch()
|
||||
true
|
||||
}
|
||||
|
||||
headerMenu.findItem(R.id.feed_group_toggle_show_only_ungrouped_subscriptions).apply {
|
||||
isChecked = subscriptionsShowOnlyUngrouped
|
||||
setOnMenuItemClickListener {
|
||||
subscriptionsShowOnlyUngrouped = !subscriptionsShowOnlyUngrouped
|
||||
it.isChecked = subscriptionsShowOnlyUngrouped
|
||||
viewModel.toggleShowOnlyUngrouped(subscriptionsShowOnlyUngrouped)
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
toolbar_search_clear.setOnClickListener {
|
||||
if (TextUtils.isEmpty(toolbar_search_edit_text.text)) {
|
||||
hideSearch()
|
||||
return@setOnClickListener
|
||||
}
|
||||
resetSearch()
|
||||
showKeyboardSearch()
|
||||
}
|
||||
|
||||
toolbar_search_edit_text.setOnClickListener {
|
||||
if (AndroidTvUtils.isTv(context)) {
|
||||
showKeyboardSearch()
|
||||
}
|
||||
}
|
||||
|
||||
toolbar_search_edit_text.addTextChangedListener(object : TextWatcher {
|
||||
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) = Unit
|
||||
override fun afterTextChanged(s: Editable) = Unit
|
||||
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
||||
val newQuery: String = toolbar_search_edit_text.text.toString()
|
||||
subscriptionsCurrentSearchQuery = newQuery
|
||||
viewModel.filterSubscriptionsBy(newQuery)
|
||||
}
|
||||
})
|
||||
|
||||
subscriptionGroupAdapter?.setOnItemClickListener(subscriptionPickerItemListener)
|
||||
}
|
||||
|
||||
private fun handlePositiveButton() = when {
|
||||
currentScreen is InitialScreen -> handlePositiveButtonInitialScreen()
|
||||
currentScreen is DeleteScreen -> viewModel.deleteGroup()
|
||||
currentScreen is SubscriptionsPickerScreen && isSearchVisible() -> hideSearch()
|
||||
else -> showScreen(InitialScreen)
|
||||
}
|
||||
|
||||
private fun handlePositiveButtonInitialScreen() {
|
||||
@@ -185,80 +292,73 @@ class FeedGroupDialog : DialogFragment() {
|
||||
groupIcon = feedGroupEntity?.icon
|
||||
groupSortOrder = feedGroupEntity?.sortOrder ?: -1
|
||||
|
||||
icon_preview.setImageResource((if (selectedIcon == null) icon else selectedIcon!!).getDrawableRes(requireContext()))
|
||||
val feedGroupIcon = if (selectedIcon == null) icon else selectedIcon!!
|
||||
icon_preview.setImageResource(feedGroupIcon.getDrawableRes(requireContext()))
|
||||
|
||||
if (group_name_input.text.isNullOrBlank()) {
|
||||
group_name_input.setText(name)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupSubscriptionPicker(subscriptions: List<SubscriptionEntity>, selectedSubscriptions: Set<Long>) {
|
||||
this.selectedSubscriptions.addAll(selectedSubscriptions)
|
||||
val useGridLayout = subscriptions.isNotEmpty()
|
||||
private val subscriptionPickerItemListener = OnItemClickListener { item, view ->
|
||||
if (item is PickerSubscriptionItem) {
|
||||
val subscriptionId = item.subscriptionEntity.uid
|
||||
wasSubscriptionSelectionChanged = true
|
||||
|
||||
val groupAdapter = GroupAdapter<GroupieViewHolder>()
|
||||
groupAdapter.spanCount = if (useGridLayout) 4 else 1
|
||||
|
||||
val subscriptionsCount = this.selectedSubscriptions.size
|
||||
val selectedCountText = resources.getQuantityString(R.plurals.feed_group_dialog_selection_count, subscriptionsCount, subscriptionsCount)
|
||||
selected_subscription_count_view.text = selectedCountText
|
||||
subscriptions_selector_header_info.text = selectedCountText
|
||||
|
||||
Section().apply {
|
||||
addAll(subscriptions.map {
|
||||
val isSelected = this@FeedGroupDialog.selectedSubscriptions.contains(it.uid)
|
||||
PickerSubscriptionItem(it, isSelected)
|
||||
})
|
||||
setPlaceholder(EmptyPlaceholderItem())
|
||||
|
||||
groupAdapter.add(this)
|
||||
}
|
||||
|
||||
subscriptions_selector_list.apply {
|
||||
layoutManager = if (useGridLayout) {
|
||||
GridLayoutManager(requireContext(), groupAdapter.spanCount, RecyclerView.VERTICAL, false)
|
||||
val isSelected = if (this.selectedSubscriptions.contains(subscriptionId)) {
|
||||
this.selectedSubscriptions.remove(subscriptionId)
|
||||
false
|
||||
} else {
|
||||
LinearLayoutManager(requireContext(), RecyclerView.VERTICAL, false)
|
||||
this.selectedSubscriptions.add(subscriptionId)
|
||||
true
|
||||
}
|
||||
|
||||
adapter = groupAdapter
|
||||
item.updateSelected(view, isSelected)
|
||||
updateSubscriptionSelectedCount()
|
||||
}
|
||||
}
|
||||
|
||||
if (subscriptionsListState != null) {
|
||||
layoutManager?.onRestoreInstanceState(subscriptionsListState)
|
||||
subscriptionsListState = null
|
||||
}
|
||||
private fun setupSubscriptionPicker(
|
||||
subscriptions: List<PickerSubscriptionItem>,
|
||||
selectedSubscriptions: Set<Long>
|
||||
) {
|
||||
if (!wasSubscriptionSelectionChanged) {
|
||||
this.selectedSubscriptions.addAll(selectedSubscriptions)
|
||||
}
|
||||
|
||||
groupAdapter.setOnItemClickListener { item, _ ->
|
||||
when (item) {
|
||||
is PickerSubscriptionItem -> {
|
||||
val subscriptionId = item.subscriptionEntity.uid
|
||||
updateSubscriptionSelectedCount()
|
||||
|
||||
val isSelected = if (this.selectedSubscriptions.contains(subscriptionId)) {
|
||||
this.selectedSubscriptions.remove(subscriptionId)
|
||||
false
|
||||
} else {
|
||||
this.selectedSubscriptions.add(subscriptionId)
|
||||
true
|
||||
}
|
||||
|
||||
item.isSelected = isSelected
|
||||
item.notifyChanged(PickerSubscriptionItem.UPDATE_SELECTED)
|
||||
|
||||
val subscriptionsCount = this.selectedSubscriptions.size
|
||||
val updateSelectedCountText = resources.getQuantityString(R.plurals.feed_group_dialog_selection_count, subscriptionsCount, subscriptionsCount)
|
||||
selected_subscription_count_view.text = updateSelectedCountText
|
||||
subscriptions_selector_header_info.text = updateSelectedCountText
|
||||
}
|
||||
}
|
||||
if (subscriptions.isEmpty()) {
|
||||
subscriptionEmptyFooter.clear()
|
||||
subscriptionEmptyFooter.add(EmptyPlaceholderItem())
|
||||
} else {
|
||||
subscriptionEmptyFooter.clear()
|
||||
}
|
||||
|
||||
select_channel_button.setOnClickListener {
|
||||
subscriptions.forEach {
|
||||
it.isSelected = this@FeedGroupDialog.selectedSubscriptions
|
||||
.contains(it.subscriptionEntity.uid)
|
||||
}
|
||||
|
||||
subscriptionMainSection.update(subscriptions, false)
|
||||
|
||||
if (subscriptionsListState != null) {
|
||||
subscriptions_selector_list.layoutManager?.onRestoreInstanceState(subscriptionsListState)
|
||||
subscriptionsListState = null
|
||||
} else {
|
||||
subscriptions_selector_list.scrollToPosition(0)
|
||||
showScreen(SubscriptionsPickerScreen)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateSubscriptionSelectedCount() {
|
||||
val selectedCount = this.selectedSubscriptions.size
|
||||
val selectedCountText = resources.getQuantityString(
|
||||
R.plurals.feed_group_dialog_selection_count,
|
||||
selectedCount, selectedCount)
|
||||
selected_subscription_count_view.text = selectedCountText
|
||||
subscriptions_header_info.text = selectedCountText
|
||||
}
|
||||
|
||||
private fun setupIconPicker() {
|
||||
val groupAdapter = GroupAdapter<GroupieViewHolder>()
|
||||
groupAdapter.addAll(FeedGroupIcon.values().map { PickerIconItem(requireContext(), it) })
|
||||
@@ -294,9 +394,9 @@ class FeedGroupDialog : DialogFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
/*///////////////////////////////////////////////////////////////////////////
|
||||
// Screen Selector
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////// */
|
||||
|
||||
private fun showScreen(screen: ScreenState) {
|
||||
currentScreen = screen
|
||||
@@ -320,7 +420,8 @@ class FeedGroupDialog : DialogFragment() {
|
||||
else -> View.VISIBLE
|
||||
}
|
||||
|
||||
if (currentScreen != InitialScreen) hideKeyboard()
|
||||
hideKeyboard()
|
||||
hideSearch()
|
||||
}
|
||||
|
||||
private fun View.onlyVisibleIn(vararg screens: ScreenState) {
|
||||
@@ -330,13 +431,58 @@ class FeedGroupDialog : DialogFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
/*///////////////////////////////////////////////////////////////////////////
|
||||
// Utils
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////// */
|
||||
|
||||
private fun isSearchVisible() = subscriptions_header_search_container?.visibility == View.VISIBLE
|
||||
|
||||
private fun resetSearch() {
|
||||
toolbar_search_edit_text.setText("")
|
||||
subscriptionsCurrentSearchQuery = ""
|
||||
viewModel.clearSubscriptionsFilter()
|
||||
}
|
||||
|
||||
private fun hideSearch() {
|
||||
resetSearch()
|
||||
subscriptions_header_search_container.visibility = View.GONE
|
||||
subscriptions_header_info_container.visibility = View.VISIBLE
|
||||
subscriptions_header_toolbar.menu.findItem(R.id.action_search).isVisible = true
|
||||
hideKeyboardSearch()
|
||||
}
|
||||
|
||||
private fun showSearch() {
|
||||
subscriptions_header_search_container.visibility = View.VISIBLE
|
||||
subscriptions_header_info_container.visibility = View.GONE
|
||||
subscriptions_header_toolbar.menu.findItem(R.id.action_search).isVisible = false
|
||||
showKeyboardSearch()
|
||||
}
|
||||
|
||||
private val inputMethodManager by lazy {
|
||||
requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
}
|
||||
|
||||
private fun showKeyboardSearch() {
|
||||
if (toolbar_search_edit_text.requestFocus()) {
|
||||
inputMethodManager.showSoftInput(toolbar_search_edit_text, InputMethodManager.SHOW_IMPLICIT)
|
||||
}
|
||||
}
|
||||
|
||||
private fun hideKeyboardSearch() {
|
||||
inputMethodManager.hideSoftInputFromWindow(toolbar_search_edit_text.windowToken,
|
||||
InputMethodManager.RESULT_UNCHANGED_SHOWN)
|
||||
toolbar_search_edit_text.clearFocus()
|
||||
}
|
||||
|
||||
private fun showKeyboard() {
|
||||
if (group_name_input.requestFocus()) {
|
||||
inputMethodManager.showSoftInput(group_name_input, InputMethodManager.SHOW_IMPLICIT)
|
||||
}
|
||||
}
|
||||
|
||||
private fun hideKeyboard() {
|
||||
val inputMethodManager = requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
inputMethodManager.hideSoftInputFromWindow(group_name_input.windowToken, InputMethodManager.RESULT_UNCHANGED_SHOWN)
|
||||
inputMethodManager.hideSoftInputFromWindow(group_name_input.windowToken,
|
||||
InputMethodManager.RESULT_UNCHANGED_SHOWN)
|
||||
group_name_input.clearFocus()
|
||||
}
|
||||
|
||||
@@ -363,4 +509,4 @@ class FeedGroupDialog : DialogFragment() {
|
||||
return dialog
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,43 +9,56 @@ import io.reactivex.Completable
|
||||
import io.reactivex.Flowable
|
||||
import io.reactivex.disposables.Disposable
|
||||
import io.reactivex.functions.BiFunction
|
||||
import io.reactivex.processors.BehaviorProcessor
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
|
||||
import org.schabi.newpipe.database.subscription.SubscriptionEntity
|
||||
import org.schabi.newpipe.local.feed.FeedDatabaseManager
|
||||
import org.schabi.newpipe.local.subscription.FeedGroupIcon
|
||||
import org.schabi.newpipe.local.subscription.SubscriptionManager
|
||||
import org.schabi.newpipe.local.subscription.item.PickerSubscriptionItem
|
||||
|
||||
|
||||
class FeedGroupDialogViewModel(applicationContext: Context, val groupId: Long = FeedGroupEntity.GROUP_ALL_ID) : ViewModel() {
|
||||
class Factory(val context: Context, val groupId: Long = FeedGroupEntity.GROUP_ALL_ID) : ViewModelProvider.Factory {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
|
||||
return FeedGroupDialogViewModel(context.applicationContext, groupId) as T
|
||||
}
|
||||
}
|
||||
class FeedGroupDialogViewModel(
|
||||
applicationContext: Context,
|
||||
private val groupId: Long = FeedGroupEntity.GROUP_ALL_ID,
|
||||
initialQuery: String = "",
|
||||
initialShowOnlyUngrouped: Boolean = false
|
||||
) : ViewModel() {
|
||||
|
||||
private var feedDatabaseManager: FeedDatabaseManager = FeedDatabaseManager(applicationContext)
|
||||
private var subscriptionManager = SubscriptionManager(applicationContext)
|
||||
|
||||
private var filterSubscriptions = BehaviorProcessor.create<String>()
|
||||
private var toggleShowOnlyUngrouped = BehaviorProcessor.create<Boolean>()
|
||||
|
||||
private var subscriptionsFlowable = Flowable
|
||||
.combineLatest(
|
||||
filterSubscriptions.startWith(initialQuery),
|
||||
toggleShowOnlyUngrouped.startWith(initialShowOnlyUngrouped),
|
||||
BiFunction { t1: String, t2: Boolean -> Filter(t1, t2) }
|
||||
)
|
||||
.distinctUntilChanged()
|
||||
.switchMap { filter ->
|
||||
subscriptionManager.getSubscriptions(groupId, filter.query, filter.showOnlyUngrouped)
|
||||
}.map { list -> list.map { PickerSubscriptionItem(it) } }
|
||||
|
||||
private val mutableGroupLiveData = MutableLiveData<FeedGroupEntity>()
|
||||
private val mutableSubscriptionsLiveData = MutableLiveData<Pair<List<SubscriptionEntity>, Set<Long>>>()
|
||||
private val mutableSubscriptionsLiveData = MutableLiveData<Pair<List<PickerSubscriptionItem>, Set<Long>>>()
|
||||
private val mutableDialogEventLiveData = MutableLiveData<DialogEvent>()
|
||||
val groupLiveData: LiveData<FeedGroupEntity> = mutableGroupLiveData
|
||||
val subscriptionsLiveData: LiveData<Pair<List<SubscriptionEntity>, Set<Long>>> = mutableSubscriptionsLiveData
|
||||
val subscriptionsLiveData: LiveData<Pair<List<PickerSubscriptionItem>, Set<Long>>> = mutableSubscriptionsLiveData
|
||||
val dialogEventLiveData: LiveData<DialogEvent> = mutableDialogEventLiveData
|
||||
|
||||
private var actionProcessingDisposable: Disposable? = null
|
||||
|
||||
private var feedGroupDisposable = feedDatabaseManager.getGroup(groupId)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe(mutableGroupLiveData::postValue)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe(mutableGroupLiveData::postValue)
|
||||
|
||||
private var subscriptionsDisposable = Flowable
|
||||
.combineLatest(subscriptionManager.subscriptions(), feedDatabaseManager.subscriptionIdsForGroup(groupId),
|
||||
BiFunction { t1: List<SubscriptionEntity>, t2: List<Long> -> t1 to t2.toSet() })
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe(mutableSubscriptionsLiveData::postValue)
|
||||
.combineLatest(subscriptionsFlowable, feedDatabaseManager.subscriptionIdsForGroup(groupId),
|
||||
BiFunction { t1: List<PickerSubscriptionItem>, t2: List<Long> -> t1 to t2.toSet() })
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe(mutableSubscriptionsLiveData::postValue)
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
@@ -56,14 +69,14 @@ class FeedGroupDialogViewModel(applicationContext: Context, val groupId: Long =
|
||||
|
||||
fun createGroup(name: String, selectedIcon: FeedGroupIcon, selectedSubscriptions: Set<Long>) {
|
||||
doAction(feedDatabaseManager.createGroup(name, selectedIcon)
|
||||
.flatMapCompletable {
|
||||
feedDatabaseManager.updateSubscriptionsForGroup(it, selectedSubscriptions.toList())
|
||||
})
|
||||
.flatMapCompletable {
|
||||
feedDatabaseManager.updateSubscriptionsForGroup(it, selectedSubscriptions.toList())
|
||||
})
|
||||
}
|
||||
|
||||
fun updateGroup(name: String, selectedIcon: FeedGroupIcon, selectedSubscriptions: Set<Long>, sortOrder: Long) {
|
||||
doAction(feedDatabaseManager.updateSubscriptionsForGroup(groupId, selectedSubscriptions.toList())
|
||||
.andThen(feedDatabaseManager.updateGroup(FeedGroupEntity(groupId, name, selectedIcon, sortOrder))))
|
||||
.andThen(feedDatabaseManager.updateGroup(FeedGroupEntity(groupId, name, selectedIcon, sortOrder))))
|
||||
}
|
||||
|
||||
fun deleteGroup() {
|
||||
@@ -75,13 +88,40 @@ class FeedGroupDialogViewModel(applicationContext: Context, val groupId: Long =
|
||||
mutableDialogEventLiveData.value = DialogEvent.ProcessingEvent
|
||||
|
||||
actionProcessingDisposable = completable
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe { mutableDialogEventLiveData.postValue(DialogEvent.SuccessEvent) }
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe { mutableDialogEventLiveData.postValue(DialogEvent.SuccessEvent) }
|
||||
}
|
||||
}
|
||||
|
||||
fun filterSubscriptionsBy(query: String) {
|
||||
filterSubscriptions.onNext(query)
|
||||
}
|
||||
|
||||
fun clearSubscriptionsFilter() {
|
||||
filterSubscriptions.onNext("")
|
||||
}
|
||||
|
||||
fun toggleShowOnlyUngrouped(showOnlyUngrouped: Boolean) {
|
||||
toggleShowOnlyUngrouped.onNext(showOnlyUngrouped)
|
||||
}
|
||||
|
||||
sealed class DialogEvent {
|
||||
object ProcessingEvent : DialogEvent()
|
||||
object SuccessEvent : DialogEvent()
|
||||
}
|
||||
}
|
||||
|
||||
data class Filter(val query: String, val showOnlyUngrouped: Boolean)
|
||||
|
||||
class Factory(
|
||||
private val context: Context,
|
||||
private val groupId: Long = FeedGroupEntity.GROUP_ALL_ID,
|
||||
private val initialQuery: String = "",
|
||||
private val initialShowOnlyUngrouped: Boolean = false
|
||||
) : ViewModelProvider.Factory {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
|
||||
return FeedGroupDialogViewModel(context.applicationContext,
|
||||
groupId, initialQuery, initialShowOnlyUngrouped) as T
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,15 +16,15 @@ import com.xwray.groupie.TouchCallback
|
||||
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
|
||||
import icepick.Icepick
|
||||
import icepick.State
|
||||
import kotlinx.android.synthetic.main.dialog_feed_group_reorder.*
|
||||
import java.util.Collections
|
||||
import kotlinx.android.synthetic.main.dialog_feed_group_reorder.confirm_button
|
||||
import kotlinx.android.synthetic.main.dialog_feed_group_reorder.feed_groups_list
|
||||
import org.schabi.newpipe.R
|
||||
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
|
||||
import org.schabi.newpipe.local.subscription.dialog.FeedGroupReorderDialogViewModel.DialogEvent.ProcessingEvent
|
||||
import org.schabi.newpipe.local.subscription.dialog.FeedGroupReorderDialogViewModel.DialogEvent.SuccessEvent
|
||||
import org.schabi.newpipe.local.subscription.item.FeedGroupReorderItem
|
||||
import org.schabi.newpipe.util.ThemeHelper
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
class FeedGroupReorderDialog : DialogFragment() {
|
||||
private lateinit var viewModel: FeedGroupReorderDialogViewModel
|
||||
@@ -93,8 +93,11 @@ class FeedGroupReorderDialog : DialogFragment() {
|
||||
private fun getItemTouchCallback(): SimpleCallback {
|
||||
return object : TouchCallback() {
|
||||
|
||||
override fun onMove(recyclerView: RecyclerView, source: RecyclerView.ViewHolder,
|
||||
target: RecyclerView.ViewHolder): Boolean {
|
||||
override fun onMove(
|
||||
recyclerView: RecyclerView,
|
||||
source: RecyclerView.ViewHolder,
|
||||
target: RecyclerView.ViewHolder
|
||||
): Boolean {
|
||||
val sourceIndex = source.adapterPosition
|
||||
val targetIndex = target.adapterPosition
|
||||
|
||||
@@ -109,4 +112,4 @@ class FeedGroupReorderDialog : DialogFragment() {
|
||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, swipeDir: Int) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,4 +49,4 @@ class FeedGroupReorderDialogViewModel(application: Application) : AndroidViewMod
|
||||
object ProcessingEvent : DialogEvent()
|
||||
object SuccessEvent : DialogEvent()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,19 +4,21 @@ import android.content.Context
|
||||
import com.nostra13.universalimageloader.core.ImageLoader
|
||||
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
|
||||
import com.xwray.groupie.kotlinandroidextensions.Item
|
||||
import kotlinx.android.synthetic.main.list_channel_item.*
|
||||
import kotlinx.android.synthetic.main.list_channel_item.itemAdditionalDetails
|
||||
import kotlinx.android.synthetic.main.list_channel_item.itemChannelDescriptionView
|
||||
import kotlinx.android.synthetic.main.list_channel_item.itemThumbnailView
|
||||
import kotlinx.android.synthetic.main.list_channel_item.itemTitleView
|
||||
import org.schabi.newpipe.R
|
||||
import org.schabi.newpipe.extractor.channel.ChannelInfoItem
|
||||
import org.schabi.newpipe.util.ImageDisplayConstants
|
||||
import org.schabi.newpipe.util.Localization
|
||||
import org.schabi.newpipe.util.OnClickGesture
|
||||
|
||||
|
||||
class ChannelItem(
|
||||
private val infoItem: ChannelInfoItem,
|
||||
private val subscriptionId: Long = -1L,
|
||||
var itemVersion: ItemVersion = ItemVersion.NORMAL,
|
||||
var gesturesListener: OnClickGesture<ChannelInfoItem>? = null
|
||||
private val infoItem: ChannelInfoItem,
|
||||
private val subscriptionId: Long = -1L,
|
||||
var itemVersion: ItemVersion = ItemVersion.NORMAL,
|
||||
var gesturesListener: OnClickGesture<ChannelInfoItem>? = null
|
||||
) : Item() {
|
||||
|
||||
override fun getId(): Long = if (subscriptionId == -1L) super.getId() else subscriptionId
|
||||
@@ -62,4 +64,4 @@ class ChannelItem(
|
||||
override fun getSpanSize(spanCount: Int, position: Int): Int {
|
||||
return if (itemVersion == ItemVersion.GRID) 1 else spanCount
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,4 +7,5 @@ import org.schabi.newpipe.R
|
||||
class EmptyPlaceholderItem : Item() {
|
||||
override fun getLayout(): Int = R.layout.list_empty_view
|
||||
override fun bind(viewHolder: GroupieViewHolder, position: Int) {}
|
||||
}
|
||||
override fun getSpanSize(spanCount: Int, position: Int): Int = spanCount
|
||||
}
|
||||
|
||||
@@ -7,4 +7,4 @@ import org.schabi.newpipe.R
|
||||
class FeedGroupAddItem : Item() {
|
||||
override fun getLayout(): Int = R.layout.feed_group_add_new_item
|
||||
override fun bind(viewHolder: GroupieViewHolder, position: Int) {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,15 +2,16 @@ package org.schabi.newpipe.local.subscription.item
|
||||
|
||||
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
|
||||
import com.xwray.groupie.kotlinandroidextensions.Item
|
||||
import kotlinx.android.synthetic.main.feed_group_card_item.*
|
||||
import kotlinx.android.synthetic.main.feed_group_card_item.icon
|
||||
import kotlinx.android.synthetic.main.feed_group_card_item.title
|
||||
import org.schabi.newpipe.R
|
||||
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
|
||||
import org.schabi.newpipe.local.subscription.FeedGroupIcon
|
||||
|
||||
data class FeedGroupCardItem(
|
||||
val groupId: Long = FeedGroupEntity.GROUP_ALL_ID,
|
||||
val name: String,
|
||||
val icon: FeedGroupIcon
|
||||
val groupId: Long = FeedGroupEntity.GROUP_ALL_ID,
|
||||
val name: String,
|
||||
val icon: FeedGroupIcon
|
||||
) : Item() {
|
||||
constructor (feedGroupEntity: FeedGroupEntity) : this(feedGroupEntity.uid, feedGroupEntity.name, feedGroupEntity.icon)
|
||||
|
||||
@@ -27,4 +28,4 @@ data class FeedGroupCardItem(
|
||||
viewHolder.title.text = name
|
||||
viewHolder.icon.setImageResource(icon.getDrawableRes(viewHolder.containerView.context))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import androidx.recyclerview.widget.RecyclerView
|
||||
import com.xwray.groupie.GroupAdapter
|
||||
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
|
||||
import com.xwray.groupie.kotlinandroidextensions.Item
|
||||
import kotlinx.android.synthetic.main.feed_item_carousel.*
|
||||
import kotlinx.android.synthetic.main.feed_item_carousel.recycler_view
|
||||
import org.schabi.newpipe.R
|
||||
import org.schabi.newpipe.local.subscription.decoration.FeedGroupCarouselDecoration
|
||||
|
||||
@@ -54,4 +54,4 @@ class FeedGroupCarouselItem(context: Context, private val carouselAdapter: Group
|
||||
|
||||
listState = linearLayoutManager?.onSaveInstanceState()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,19 +6,21 @@ import androidx.recyclerview.widget.ItemTouchHelper.DOWN
|
||||
import androidx.recyclerview.widget.ItemTouchHelper.UP
|
||||
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
|
||||
import com.xwray.groupie.kotlinandroidextensions.Item
|
||||
import kotlinx.android.synthetic.main.feed_group_reorder_item.*
|
||||
import kotlinx.android.synthetic.main.feed_group_reorder_item.group_icon
|
||||
import kotlinx.android.synthetic.main.feed_group_reorder_item.group_name
|
||||
import kotlinx.android.synthetic.main.feed_group_reorder_item.handle
|
||||
import org.schabi.newpipe.R
|
||||
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
|
||||
import org.schabi.newpipe.local.subscription.FeedGroupIcon
|
||||
|
||||
data class FeedGroupReorderItem(
|
||||
val groupId: Long = FeedGroupEntity.GROUP_ALL_ID,
|
||||
val name: String,
|
||||
val icon: FeedGroupIcon,
|
||||
val dragCallback: ItemTouchHelper
|
||||
val groupId: Long = FeedGroupEntity.GROUP_ALL_ID,
|
||||
val name: String,
|
||||
val icon: FeedGroupIcon,
|
||||
val dragCallback: ItemTouchHelper
|
||||
) : Item() {
|
||||
constructor (feedGroupEntity: FeedGroupEntity, dragCallback: ItemTouchHelper)
|
||||
: this(feedGroupEntity.uid, feedGroupEntity.name, feedGroupEntity.icon, dragCallback)
|
||||
constructor (feedGroupEntity: FeedGroupEntity, dragCallback: ItemTouchHelper) :
|
||||
this(feedGroupEntity.uid, feedGroupEntity.name, feedGroupEntity.icon, dragCallback)
|
||||
|
||||
override fun getId(): Long {
|
||||
return when (groupId) {
|
||||
@@ -45,4 +47,4 @@ data class FeedGroupReorderItem(
|
||||
override fun getDragDirs(): Int {
|
||||
return UP or DOWN
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,11 @@ import android.widget.TextView
|
||||
import androidx.annotation.DrawableRes
|
||||
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
|
||||
import com.xwray.groupie.kotlinandroidextensions.Item
|
||||
import kotlinx.android.synthetic.main.feed_import_export_group.*
|
||||
import kotlinx.android.synthetic.main.feed_import_export_group.export_to_options
|
||||
import kotlinx.android.synthetic.main.feed_import_export_group.import_export
|
||||
import kotlinx.android.synthetic.main.feed_import_export_group.import_export_expand_icon
|
||||
import kotlinx.android.synthetic.main.feed_import_export_group.import_export_options
|
||||
import kotlinx.android.synthetic.main.feed_import_export_group.import_from_options
|
||||
import org.schabi.newpipe.R
|
||||
import org.schabi.newpipe.extractor.NewPipe
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException
|
||||
@@ -19,10 +23,10 @@ import org.schabi.newpipe.util.ThemeHelper
|
||||
import org.schabi.newpipe.views.CollapsibleView
|
||||
|
||||
class FeedImportExportItem(
|
||||
val onImportPreviousSelected: () -> Unit,
|
||||
val onImportFromServiceSelected: (Int) -> Unit,
|
||||
val onExportSelected: () -> Unit,
|
||||
var isExpanded: Boolean = false
|
||||
val onImportPreviousSelected: () -> Unit,
|
||||
val onImportFromServiceSelected: (Int) -> Unit,
|
||||
val onExportSelected: () -> Unit,
|
||||
var isExpanded: Boolean = false
|
||||
) : Item() {
|
||||
companion object {
|
||||
const val REFRESH_EXPANDED_STATUS = 123
|
||||
@@ -104,7 +108,6 @@ class FeedImportExportItem(
|
||||
} catch (e: ExtractionException) {
|
||||
throw RuntimeException("Services array contains an entry that it's not a valid service name ($serviceName)", e)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,4 +116,4 @@ class FeedImportExportItem(
|
||||
ThemeHelper.resolveResourceIdFromAttr(listHolder.context, R.attr.ic_save), listHolder)
|
||||
previousBackupItem.setOnClickListener { onExportSelected() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package org.schabi.newpipe.local.subscription.item
|
||||
import android.view.View.OnClickListener
|
||||
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
|
||||
import com.xwray.groupie.kotlinandroidextensions.Item
|
||||
import kotlinx.android.synthetic.main.header_item.*
|
||||
import kotlinx.android.synthetic.main.header_item.header_title
|
||||
import org.schabi.newpipe.R
|
||||
|
||||
class HeaderItem(val title: String, private val onClickListener: (() -> Unit)? = null) : Item() {
|
||||
@@ -16,4 +16,4 @@ class HeaderItem(val title: String, private val onClickListener: (() -> Unit)? =
|
||||
val listener: OnClickListener? = if (onClickListener != null) OnClickListener { onClickListener.invoke() } else null
|
||||
viewHolder.root.setOnClickListener(listener)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
package org.schabi.newpipe.local.subscription.item
|
||||
|
||||
import android.view.View.*
|
||||
import android.view.View.GONE
|
||||
import android.view.View.OnClickListener
|
||||
import android.view.View.VISIBLE
|
||||
import androidx.annotation.DrawableRes
|
||||
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
|
||||
import com.xwray.groupie.kotlinandroidextensions.Item
|
||||
import kotlinx.android.synthetic.main.header_with_menu_item.*
|
||||
import kotlinx.android.synthetic.main.header_with_menu_item.header_menu_item
|
||||
import kotlinx.android.synthetic.main.header_with_menu_item.header_title
|
||||
import org.schabi.newpipe.R
|
||||
|
||||
class HeaderWithMenuItem(
|
||||
val title: String,
|
||||
@DrawableRes val itemIcon: Int = 0,
|
||||
var showMenuItem: Boolean = true,
|
||||
private val onClickListener: (() -> Unit)? = null,
|
||||
private val menuItemOnClickListener: (() -> Unit)? = null
|
||||
val title: String,
|
||||
@DrawableRes val itemIcon: Int = 0,
|
||||
var showMenuItem: Boolean = true,
|
||||
private val onClickListener: (() -> Unit)? = null,
|
||||
private val menuItemOnClickListener: (() -> Unit)? = null
|
||||
) : Item() {
|
||||
companion object {
|
||||
const val PAYLOAD_UPDATE_VISIBILITY_MENU_ITEM = 1
|
||||
@@ -46,4 +49,4 @@ class HeaderWithMenuItem(
|
||||
private fun updateMenuItemVisibility(viewHolder: GroupieViewHolder) {
|
||||
viewHolder.header_menu_item.visibility = if (showMenuItem) VISIBLE else GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import android.content.Context
|
||||
import androidx.annotation.DrawableRes
|
||||
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
|
||||
import com.xwray.groupie.kotlinandroidextensions.Item
|
||||
import kotlinx.android.synthetic.main.picker_icon_item.*
|
||||
import kotlinx.android.synthetic.main.picker_icon_item.icon_view
|
||||
import org.schabi.newpipe.R
|
||||
import org.schabi.newpipe.local.subscription.FeedGroupIcon
|
||||
|
||||
@@ -17,4 +17,4 @@ class PickerIconItem(context: Context, val icon: FeedGroupIcon) : Item() {
|
||||
override fun bind(viewHolder: GroupieViewHolder, position: Int) {
|
||||
viewHolder.icon_view.setImageResource(iconRes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,37 +1,28 @@
|
||||
package org.schabi.newpipe.local.subscription.item
|
||||
|
||||
import android.view.View
|
||||
import com.nostra13.universalimageloader.core.DisplayImageOptions
|
||||
import com.nostra13.universalimageloader.core.ImageLoader
|
||||
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
|
||||
import com.xwray.groupie.kotlinandroidextensions.Item
|
||||
import kotlinx.android.synthetic.main.picker_subscription_item.*
|
||||
import kotlinx.android.synthetic.main.picker_subscription_item.view.*
|
||||
import org.schabi.newpipe.R
|
||||
import org.schabi.newpipe.database.subscription.SubscriptionEntity
|
||||
import org.schabi.newpipe.util.AnimationUtils
|
||||
import org.schabi.newpipe.util.AnimationUtils.animateView
|
||||
import org.schabi.newpipe.util.ImageDisplayConstants
|
||||
|
||||
data class PickerSubscriptionItem(val subscriptionEntity: SubscriptionEntity, var isSelected: Boolean = false) : Item() {
|
||||
companion object {
|
||||
const val UPDATE_SELECTED = 123
|
||||
|
||||
val IMAGE_LOADING_OPTIONS: DisplayImageOptions = ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS
|
||||
}
|
||||
|
||||
data class PickerSubscriptionItem(
|
||||
val subscriptionEntity: SubscriptionEntity,
|
||||
var isSelected: Boolean = false
|
||||
) : Item() {
|
||||
override fun getId(): Long = subscriptionEntity.uid
|
||||
override fun getLayout(): Int = R.layout.picker_subscription_item
|
||||
|
||||
override fun bind(viewHolder: GroupieViewHolder, position: Int, payloads: MutableList<Any>) {
|
||||
if (payloads.contains(UPDATE_SELECTED)) {
|
||||
animateView(viewHolder.selected_highlight, AnimationUtils.Type.LIGHT_SCALE_AND_ALPHA, isSelected, 150)
|
||||
return
|
||||
}
|
||||
|
||||
super.bind(viewHolder, position, payloads)
|
||||
}
|
||||
override fun getSpanSize(spanCount: Int, position: Int): Int = 1
|
||||
|
||||
override fun bind(viewHolder: GroupieViewHolder, position: Int) {
|
||||
ImageLoader.getInstance().displayImage(subscriptionEntity.avatarUrl, viewHolder.thumbnail_view, IMAGE_LOADING_OPTIONS)
|
||||
ImageLoader.getInstance().displayImage(subscriptionEntity.avatarUrl,
|
||||
viewHolder.thumbnail_view, ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS)
|
||||
|
||||
viewHolder.title_view.text = subscriptionEntity.name
|
||||
viewHolder.selected_highlight.visibility = if (isSelected) View.VISIBLE else View.GONE
|
||||
@@ -45,7 +36,9 @@ data class PickerSubscriptionItem(val subscriptionEntity: SubscriptionEntity, va
|
||||
viewHolder.selected_highlight.alpha = 1F
|
||||
}
|
||||
|
||||
override fun getId(): Long {
|
||||
return subscriptionEntity.uid
|
||||
fun updateSelected(containerView: View, isSelected: Boolean) {
|
||||
this.isSelected = isSelected
|
||||
animateView(containerView.selected_highlight,
|
||||
AnimationUtils.Type.LIGHT_SCALE_AND_ALPHA, isSelected, 150)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,6 @@ import org.schabi.newpipe.BuildConfig;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.player.event.PlayerEventListener;
|
||||
import org.schabi.newpipe.player.helper.LockManager;
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
||||
import org.schabi.newpipe.player.resolver.AudioPlaybackResolver;
|
||||
import org.schabi.newpipe.player.resolver.MediaSourceTag;
|
||||
@@ -91,7 +90,6 @@ public final class BackgroundPlayer extends Service {
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Service-Activity Binder
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
private LockManager lockManager;
|
||||
private SharedPreferences sharedPreferences;
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
@@ -116,7 +114,6 @@ public final class BackgroundPlayer extends Service {
|
||||
Log.d(TAG, "onCreate() called");
|
||||
}
|
||||
notificationManager = ((NotificationManager) getSystemService(NOTIFICATION_SERVICE));
|
||||
lockManager = new LockManager(this);
|
||||
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
|
||||
assureCorrectAppLanguage(this);
|
||||
ThemeHelper.setTheme(this);
|
||||
@@ -166,9 +163,6 @@ public final class BackgroundPlayer extends Service {
|
||||
Log.d(TAG, "onClose() called");
|
||||
}
|
||||
|
||||
if (lockManager != null) {
|
||||
lockManager.releaseWifiAndCpu();
|
||||
}
|
||||
if (basePlayerImpl != null) {
|
||||
basePlayerImpl.savePlaybackState();
|
||||
basePlayerImpl.stopActivityBinding();
|
||||
@@ -179,7 +173,6 @@ public final class BackgroundPlayer extends Service {
|
||||
}
|
||||
mBinder = null;
|
||||
basePlayerImpl = null;
|
||||
lockManager = null;
|
||||
|
||||
stopForeground(true);
|
||||
stopSelf();
|
||||
@@ -208,9 +201,10 @@ public final class BackgroundPlayer extends Service {
|
||||
}
|
||||
|
||||
private NotificationCompat.Builder createNotification() {
|
||||
notRemoteView = new RemoteViews(BuildConfig.APPLICATION_ID, R.layout.player_notification);
|
||||
notRemoteView = new RemoteViews(BuildConfig.APPLICATION_ID,
|
||||
R.layout.player_background_notification);
|
||||
bigNotRemoteView = new RemoteViews(BuildConfig.APPLICATION_ID,
|
||||
R.layout.player_notification_expanded);
|
||||
R.layout.player_background_notification_expanded);
|
||||
|
||||
setupNotification(notRemoteView);
|
||||
setupNotification(bigNotRemoteView);
|
||||
@@ -662,8 +656,7 @@ public final class BackgroundPlayer extends Service {
|
||||
super.onPlaying();
|
||||
resetNotification();
|
||||
updateNotificationThumbnail();
|
||||
updateNotification(R.drawable.ic_pause_white);
|
||||
lockManager.acquireWifiAndCpu();
|
||||
updateNotification(R.drawable.exo_controls_pause);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -671,8 +664,7 @@ public final class BackgroundPlayer extends Service {
|
||||
super.onPaused();
|
||||
resetNotification();
|
||||
updateNotificationThumbnail();
|
||||
updateNotification(R.drawable.ic_play_arrow_white);
|
||||
lockManager.releaseWifiAndCpu();
|
||||
updateNotification(R.drawable.exo_controls_play);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -686,8 +678,7 @@ public final class BackgroundPlayer extends Service {
|
||||
notRemoteView.setProgressBar(R.id.notificationProgressBar, 100, 100, false);
|
||||
}
|
||||
updateNotificationThumbnail();
|
||||
updateNotification(R.drawable.ic_replay_white);
|
||||
lockManager.releaseWifiAndCpu();
|
||||
updateNotification(R.drawable.ic_replay_white_24dp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,9 +35,9 @@ import android.widget.Toast;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.DefaultRenderersFactory;
|
||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||
import com.google.android.exoplayer2.ExoPlayerFactory;
|
||||
import com.google.android.exoplayer2.LoadControl;
|
||||
import com.google.android.exoplayer2.PlaybackParameters;
|
||||
import com.google.android.exoplayer2.Player;
|
||||
@@ -64,7 +64,6 @@ import org.schabi.newpipe.player.helper.LoadController;
|
||||
import org.schabi.newpipe.player.helper.MediaSessionManager;
|
||||
import org.schabi.newpipe.player.helper.PlayerDataSource;
|
||||
import org.schabi.newpipe.player.helper.PlayerHelper;
|
||||
import org.schabi.newpipe.player.mediasource.FailedMediaSource;
|
||||
import org.schabi.newpipe.player.playback.BasePlayerMediaSession;
|
||||
import org.schabi.newpipe.player.playback.CustomTrackSelector;
|
||||
import org.schabi.newpipe.player.playback.MediaSourceManager;
|
||||
@@ -77,7 +76,6 @@ import org.schabi.newpipe.util.ImageDisplayConstants;
|
||||
import org.schabi.newpipe.util.SerializedCache;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
@@ -217,7 +215,7 @@ public abstract class BasePlayer implements
|
||||
|
||||
final TrackSelection.Factory trackSelectionFactory = PlayerHelper
|
||||
.getQualitySelector(context);
|
||||
this.trackSelector = new CustomTrackSelector(trackSelectionFactory);
|
||||
this.trackSelector = new CustomTrackSelector(context, trackSelectionFactory);
|
||||
|
||||
this.loadControl = new LoadController();
|
||||
this.renderFactory = new DefaultRenderersFactory(context);
|
||||
@@ -232,14 +230,18 @@ public abstract class BasePlayer implements
|
||||
|
||||
public void initPlayer(final boolean playOnReady) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "initPlayer() called with: context = [" + context + "]");
|
||||
Log.d(TAG, "initPlayer() called with: playOnReady = [" + playOnReady + "]");
|
||||
}
|
||||
|
||||
simpleExoPlayer = ExoPlayerFactory
|
||||
.newSimpleInstance(context, renderFactory, trackSelector, loadControl);
|
||||
simpleExoPlayer = new SimpleExoPlayer.Builder(context, renderFactory)
|
||||
.setTrackSelector(trackSelector)
|
||||
.setLoadControl(loadControl)
|
||||
.build();
|
||||
simpleExoPlayer.addListener(this);
|
||||
simpleExoPlayer.setPlayWhenReady(playOnReady);
|
||||
simpleExoPlayer.setSeekParameters(PlayerHelper.getSeekParameters(context));
|
||||
simpleExoPlayer.setWakeMode(C.WAKE_MODE_NETWORK);
|
||||
simpleExoPlayer.setHandleAudioBecomingNoisy(true);
|
||||
|
||||
audioReactor = new AudioReactor(context, simpleExoPlayer);
|
||||
mediaSessionManager = new MediaSessionManager(context, simpleExoPlayer,
|
||||
@@ -329,13 +331,12 @@ public abstract class BasePlayer implements
|
||||
final SharedPreferences preferences =
|
||||
PreferenceManager.getDefaultSharedPreferences(context);
|
||||
|
||||
final float speed = preferences
|
||||
.getFloat(context.getString(R.string.playback_speed_key), getPlaybackSpeed());
|
||||
final float pitch = preferences.getFloat(context.getString(R.string.playback_pitch_key),
|
||||
getPlaybackPitch());
|
||||
final boolean skipSilence = preferences
|
||||
.getBoolean(context.getString(R.string.playback_skip_silence_key),
|
||||
getPlaybackSkipSilence());
|
||||
final float speed = preferences.getFloat(
|
||||
context.getString(R.string.playback_speed_key), getPlaybackSpeed());
|
||||
final float pitch = preferences.getFloat(
|
||||
context.getString(R.string.playback_pitch_key), getPlaybackPitch());
|
||||
final boolean skipSilence = preferences.getBoolean(
|
||||
context.getString(R.string.playback_skip_silence_key), getPlaybackSkipSilence());
|
||||
return new PlaybackParameters(speed, pitch, skipSilence);
|
||||
}
|
||||
|
||||
@@ -666,11 +667,9 @@ public abstract class BasePlayer implements
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void onTimelineChanged(final Timeline timeline, final Object manifest,
|
||||
@Player.TimelineChangeReason final int reason) {
|
||||
public void onTimelineChanged(final Timeline timeline, final int reason) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "ExoPlayer - onTimelineChanged() called with "
|
||||
+ (manifest == null ? "no manifest" : "available manifest") + ", "
|
||||
+ "timeline size = [" + timeline.getWindowCount() + "], "
|
||||
+ "reason = [" + reason + "]");
|
||||
}
|
||||
@@ -833,16 +832,8 @@ public abstract class BasePlayer implements
|
||||
final Throwable cause = error.getCause();
|
||||
if (error instanceof BehindLiveWindowException) {
|
||||
reload();
|
||||
} else if (cause instanceof UnknownHostException) {
|
||||
playQueue.error(/*isNetworkProblem=*/true);
|
||||
} else if (isCurrentWindowValid()) {
|
||||
playQueue.error(/*isTransitioningToBadStream=*/true);
|
||||
} else if (cause instanceof FailedMediaSource.MediaSourceResolutionException) {
|
||||
playQueue.error(/*recoverableWithNoAvailableStream=*/false);
|
||||
} else if (cause instanceof FailedMediaSource.StreamInfoLoadException) {
|
||||
playQueue.error(/*recoverableIfLoadFailsWhenNetworkIsFine=*/false);
|
||||
} else {
|
||||
playQueue.error(/*noIdeaWhatHappenedAndLetUserChooseWhatToDo=*/true);
|
||||
playQueue.error();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1129,6 +1120,7 @@ public abstract class BasePlayer implements
|
||||
Log.d(TAG, "onFastRewind() called");
|
||||
}
|
||||
seekBy(-getSeekDuration());
|
||||
triggerProgressUpdate();
|
||||
}
|
||||
|
||||
public void onFastForward() {
|
||||
@@ -1136,6 +1128,7 @@ public abstract class BasePlayer implements
|
||||
Log.d(TAG, "onFastForward() called");
|
||||
}
|
||||
seekBy(getSeekDuration());
|
||||
triggerProgressUpdate();
|
||||
}
|
||||
|
||||
private int getSeekDuration() {
|
||||
@@ -1477,10 +1470,21 @@ public abstract class BasePlayer implements
|
||||
return parameters == null ? PlaybackParameters.DEFAULT : parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the playback parameters of the player, and also saves them to shared preferences.
|
||||
* Speed and pitch are rounded up to 2 decimal places before being used or saved.
|
||||
* @param speed the playback speed, will be rounded to up to 2 decimal places
|
||||
* @param pitch the playback pitch, will be rounded to up to 2 decimal places
|
||||
* @param skipSilence skip silence during playback
|
||||
*/
|
||||
public void setPlaybackParameters(final float speed, final float pitch,
|
||||
final boolean skipSilence) {
|
||||
savePlaybackParametersToPreferences(speed, pitch, skipSilence);
|
||||
simpleExoPlayer.setPlaybackParameters(new PlaybackParameters(speed, pitch, skipSilence));
|
||||
final float roundedSpeed = Math.round(speed * 100.0f) / 100.0f;
|
||||
final float roundedPitch = Math.round(pitch * 100.0f) / 100.0f;
|
||||
|
||||
savePlaybackParametersToPreferences(roundedSpeed, roundedPitch, skipSilence);
|
||||
simpleExoPlayer.setPlaybackParameters(
|
||||
new PlaybackParameters(roundedSpeed, roundedPitch, skipSilence));
|
||||
}
|
||||
|
||||
private void savePlaybackParametersToPreferences(final float speed, final float pitch,
|
||||
|
||||
@@ -80,8 +80,8 @@ import org.schabi.newpipe.player.playqueue.PlayQueueItemHolder;
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueueItemTouchCallback;
|
||||
import org.schabi.newpipe.player.resolver.MediaSourceTag;
|
||||
import org.schabi.newpipe.player.resolver.VideoPlaybackResolver;
|
||||
import org.schabi.newpipe.util.AnimationUtils;
|
||||
import org.schabi.newpipe.util.AndroidTvUtils;
|
||||
import org.schabi.newpipe.util.AnimationUtils;
|
||||
import org.schabi.newpipe.util.KoreUtil;
|
||||
import org.schabi.newpipe.util.ListHelper;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
@@ -491,7 +491,7 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||
|
||||
protected void setMuteButton(final ImageButton muteButton, final boolean isMuted) {
|
||||
muteButton.setImageDrawable(AppCompatResources.getDrawable(getApplicationContext(), isMuted
|
||||
? R.drawable.ic_volume_off_white_72dp : R.drawable.ic_volume_up_white_72dp));
|
||||
? R.drawable.ic_volume_off_white_24dp : R.drawable.ic_volume_up_white_24dp));
|
||||
}
|
||||
|
||||
|
||||
@@ -593,9 +593,6 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||
|
||||
titleTextView.setSelected(true);
|
||||
channelTextView.setSelected(true);
|
||||
boolean showKodiButton = PreferenceManager.getDefaultSharedPreferences(this.context)
|
||||
.getBoolean(this.context.getString(R.string.show_play_with_kodi_key), false);
|
||||
kodiButton.setVisibility(showKodiButton ? View.VISIBLE : View.GONE);
|
||||
|
||||
getRootView().setKeepScreenOn(true);
|
||||
}
|
||||
@@ -712,6 +709,13 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||
protected void onMetadataChanged(@NonNull final MediaSourceTag tag) {
|
||||
super.onMetadataChanged(tag);
|
||||
|
||||
// show kodi button if it supports the current service and it is enabled in settings
|
||||
final boolean showKodiButton =
|
||||
KoreUtil.isServiceSupportedByKore(tag.getMetadata().getServiceId())
|
||||
&& PreferenceManager.getDefaultSharedPreferences(context)
|
||||
.getBoolean(context.getString(R.string.show_play_with_kodi_key), false);
|
||||
kodiButton.setVisibility(showKodiButton ? View.VISIBLE : View.GONE);
|
||||
|
||||
titleTextView.setText(tag.getMetadata().getName());
|
||||
channelTextView.setText(tag.getMetadata().getUploaderName());
|
||||
}
|
||||
@@ -725,13 +729,12 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||
public void onKodiShare() {
|
||||
onPause();
|
||||
try {
|
||||
NavigationHelper.playWithKore(this.context,
|
||||
Uri.parse(playerImpl.getVideoUrl().replace("https", "http")));
|
||||
NavigationHelper.playWithKore(context, Uri.parse(playerImpl.getVideoUrl()));
|
||||
} catch (Exception e) {
|
||||
if (DEBUG) {
|
||||
Log.i(TAG, "Failed to start kore", e);
|
||||
}
|
||||
KoreUtil.showInstallKoreDialog(this.context);
|
||||
KoreUtil.showInstallKoreDialog(context);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -986,7 +989,7 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||
@Override
|
||||
public void onBlocked() {
|
||||
super.onBlocked();
|
||||
playPauseButton.setImageResource(R.drawable.ic_pause_white);
|
||||
playPauseButton.setImageResource(R.drawable.ic_pause_white_24dp);
|
||||
animatePlayButtons(false, 100);
|
||||
animateView(closeButton, false, DEFAULT_CONTROLS_DURATION);
|
||||
getRootView().setKeepScreenOn(true);
|
||||
@@ -1002,7 +1005,7 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||
public void onPlaying() {
|
||||
super.onPlaying();
|
||||
animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false, 80, 0, () -> {
|
||||
playPauseButton.setImageResource(R.drawable.ic_pause_white);
|
||||
playPauseButton.setImageResource(R.drawable.ic_pause_white_24dp);
|
||||
animatePlayButtons(true, 200);
|
||||
playPauseButton.requestFocus();
|
||||
animateView(closeButton, false, DEFAULT_CONTROLS_DURATION);
|
||||
@@ -1015,7 +1018,7 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||
public void onPaused() {
|
||||
super.onPaused();
|
||||
animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false, 80, 0, () -> {
|
||||
playPauseButton.setImageResource(R.drawable.ic_play_arrow_white);
|
||||
playPauseButton.setImageResource(R.drawable.ic_play_arrow_white_24dp);
|
||||
animatePlayButtons(true, 200);
|
||||
playPauseButton.requestFocus();
|
||||
animateView(closeButton, false, DEFAULT_CONTROLS_DURATION);
|
||||
@@ -1036,7 +1039,7 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||
@Override
|
||||
public void onCompleted() {
|
||||
animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false, 0, 0, () -> {
|
||||
playPauseButton.setImageResource(R.drawable.ic_replay_white);
|
||||
playPauseButton.setImageResource(R.drawable.ic_replay_white_24dp);
|
||||
animatePlayButtons(true, DEFAULT_CONTROLS_DURATION);
|
||||
animateView(closeButton, true, DEFAULT_CONTROLS_DURATION);
|
||||
});
|
||||
@@ -1322,6 +1325,13 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||
return false;
|
||||
}
|
||||
|
||||
final boolean isTouchingStatusBar = initialEvent.getY() < getStatusBarHeight();
|
||||
final boolean isTouchingNavigationBar = initialEvent.getY()
|
||||
> playerImpl.getRootView().getHeight() - getNavigationBarHeight();
|
||||
if (isTouchingStatusBar || isTouchingNavigationBar) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// if (DEBUG) {
|
||||
// Log.d(TAG, "MainVideoPlayer.onScroll = " +
|
||||
// "e1.getRaw = [" + initialEvent.getRawX() + ", "
|
||||
@@ -1358,12 +1368,12 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||
}
|
||||
|
||||
final int resId = currentProgressPercent <= 0
|
||||
? R.drawable.ic_volume_off_white_72dp
|
||||
? R.drawable.ic_volume_off_white_24dp
|
||||
: currentProgressPercent < 0.25
|
||||
? R.drawable.ic_volume_mute_white_72dp
|
||||
? R.drawable.ic_volume_mute_white_24dp
|
||||
: currentProgressPercent < 0.75
|
||||
? R.drawable.ic_volume_down_white_72dp
|
||||
: R.drawable.ic_volume_up_white_72dp;
|
||||
? R.drawable.ic_volume_down_white_24dp
|
||||
: R.drawable.ic_volume_up_white_24dp;
|
||||
|
||||
playerImpl.getVolumeImageView().setImageDrawable(
|
||||
AppCompatResources.getDrawable(getApplicationContext(), resId)
|
||||
@@ -1390,10 +1400,10 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||
}
|
||||
|
||||
final int resId = currentProgressPercent < 0.25
|
||||
? R.drawable.ic_brightness_low_white_72dp
|
||||
? R.drawable.ic_brightness_low_white_24dp
|
||||
: currentProgressPercent < 0.75
|
||||
? R.drawable.ic_brightness_medium_white_72dp
|
||||
: R.drawable.ic_brightness_high_white_72dp;
|
||||
? R.drawable.ic_brightness_medium_white_24dp
|
||||
: R.drawable.ic_brightness_high_white_24dp;
|
||||
|
||||
playerImpl.getBrightnessImageView().setImageDrawable(
|
||||
AppCompatResources.getDrawable(getApplicationContext(), resId)
|
||||
@@ -1410,6 +1420,22 @@ public final class MainVideoPlayer extends AppCompatActivity
|
||||
return true;
|
||||
}
|
||||
|
||||
private int getNavigationBarHeight() {
|
||||
int resId = getResources().getIdentifier("navigation_bar_height", "dimen", "android");
|
||||
if (resId > 0) {
|
||||
return getResources().getDimensionPixelSize(resId);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private int getStatusBarHeight() {
|
||||
int resId = getResources().getIdentifier("status_bar_height", "dimen", "android");
|
||||
if (resId > 0) {
|
||||
return getResources().getDimensionPixelSize(resId);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private void onScrollEnd() {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onScrollEnd() called");
|
||||
|
||||
@@ -68,7 +68,6 @@ import org.schabi.newpipe.BuildConfig;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||
import org.schabi.newpipe.player.event.PlayerEventListener;
|
||||
import org.schabi.newpipe.player.helper.LockManager;
|
||||
import org.schabi.newpipe.player.helper.PlayerHelper;
|
||||
import org.schabi.newpipe.player.resolver.MediaSourceTag;
|
||||
import org.schabi.newpipe.player.resolver.VideoPlaybackResolver;
|
||||
@@ -132,7 +131,6 @@ public final class PopupVideoPlayer extends Service {
|
||||
private RemoteViews notRemoteView;
|
||||
|
||||
private VideoPlayerImpl playerImpl;
|
||||
private LockManager lockManager;
|
||||
private boolean isPopupClosing = false;
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
@@ -152,7 +150,6 @@ public final class PopupVideoPlayer extends Service {
|
||||
windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
|
||||
notificationManager = ((NotificationManager) getSystemService(NOTIFICATION_SERVICE));
|
||||
|
||||
lockManager = new LockManager(this);
|
||||
playerImpl = new VideoPlayerImpl(this);
|
||||
ThemeHelper.setTheme(this);
|
||||
|
||||
@@ -169,9 +166,6 @@ public final class PopupVideoPlayer extends Service {
|
||||
initPopup();
|
||||
initPopupCloseOverlay();
|
||||
}
|
||||
if (!playerImpl.isPlaying()) {
|
||||
playerImpl.getPlayer().setPlayWhenReady(true);
|
||||
}
|
||||
|
||||
playerImpl.handleIntent(intent);
|
||||
|
||||
@@ -378,9 +372,6 @@ public final class PopupVideoPlayer extends Service {
|
||||
}
|
||||
|
||||
mBinder = null;
|
||||
if (lockManager != null) {
|
||||
lockManager.releaseWifiAndCpu();
|
||||
}
|
||||
if (notificationManager != null) {
|
||||
notificationManager.cancel(NOTIFICATION_ID);
|
||||
}
|
||||
@@ -898,7 +889,7 @@ public final class PopupVideoPlayer extends Service {
|
||||
public void onBlocked() {
|
||||
super.onBlocked();
|
||||
resetNotification();
|
||||
updateNotification(R.drawable.ic_play_arrow_white);
|
||||
updateNotification(R.drawable.exo_controls_play);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -908,20 +899,19 @@ public final class PopupVideoPlayer extends Service {
|
||||
updateWindowFlags(ONGOING_PLAYBACK_WINDOW_FLAGS);
|
||||
|
||||
resetNotification();
|
||||
updateNotification(R.drawable.ic_pause_white);
|
||||
updateNotification(R.drawable.exo_controls_pause);
|
||||
|
||||
videoPlayPause.setBackgroundResource(R.drawable.ic_pause_white);
|
||||
videoPlayPause.setBackgroundResource(R.drawable.exo_controls_pause);
|
||||
hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME);
|
||||
|
||||
startForeground(NOTIFICATION_ID, notBuilder.build());
|
||||
lockManager.acquireWifiAndCpu();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBuffering() {
|
||||
super.onBuffering();
|
||||
resetNotification();
|
||||
updateNotification(R.drawable.ic_play_arrow_white);
|
||||
updateNotification(R.drawable.exo_controls_play);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -931,10 +921,8 @@ public final class PopupVideoPlayer extends Service {
|
||||
updateWindowFlags(IDLE_WINDOW_FLAGS);
|
||||
|
||||
resetNotification();
|
||||
updateNotification(R.drawable.ic_play_arrow_white);
|
||||
|
||||
videoPlayPause.setBackgroundResource(R.drawable.ic_play_arrow_white);
|
||||
lockManager.releaseWifiAndCpu();
|
||||
updateNotification(R.drawable.exo_controls_play);
|
||||
videoPlayPause.setBackgroundResource(R.drawable.exo_controls_play);
|
||||
|
||||
stopForeground(false);
|
||||
}
|
||||
@@ -943,9 +931,9 @@ public final class PopupVideoPlayer extends Service {
|
||||
public void onPausedSeek() {
|
||||
super.onPausedSeek();
|
||||
resetNotification();
|
||||
updateNotification(R.drawable.ic_play_arrow_white);
|
||||
updateNotification(R.drawable.exo_controls_play);
|
||||
|
||||
videoPlayPause.setBackgroundResource(R.drawable.ic_pause_white);
|
||||
videoPlayPause.setBackgroundResource(R.drawable.exo_controls_play);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -955,10 +943,8 @@ public final class PopupVideoPlayer extends Service {
|
||||
updateWindowFlags(IDLE_WINDOW_FLAGS);
|
||||
|
||||
resetNotification();
|
||||
updateNotification(R.drawable.ic_replay_white);
|
||||
|
||||
videoPlayPause.setBackgroundResource(R.drawable.ic_replay_white);
|
||||
lockManager.releaseWifiAndCpu();
|
||||
updateNotification(R.drawable.ic_replay_white_24dp);
|
||||
videoPlayPause.setBackgroundResource(R.drawable.ic_replay_white_24dp);
|
||||
|
||||
stopForeground(false);
|
||||
}
|
||||
|
||||
@@ -45,7 +45,6 @@ import org.schabi.newpipe.util.ThemeHelper;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.schabi.newpipe.player.helper.PlayerHelper.formatPitch;
|
||||
import static org.schabi.newpipe.player.helper.PlayerHelper.formatSpeed;
|
||||
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
|
||||
|
||||
@@ -84,14 +83,13 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
||||
|
||||
private ImageButton repeatButton;
|
||||
private ImageButton backwardButton;
|
||||
private ImageButton fastRewindButton;
|
||||
private ImageButton playPauseButton;
|
||||
private ImageButton fastForwardButton;
|
||||
private ImageButton forwardButton;
|
||||
private ImageButton shuffleButton;
|
||||
private ProgressBar progressBar;
|
||||
|
||||
private TextView playbackSpeedButton;
|
||||
private TextView playbackPitchButton;
|
||||
|
||||
private Menu menu;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
@@ -166,6 +164,9 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
||||
case R.id.action_append_playlist:
|
||||
appendAllToPlaylist();
|
||||
return true;
|
||||
case R.id.action_playback_speed:
|
||||
openPlaybackParameterDialog();
|
||||
return true;
|
||||
case R.id.action_mute:
|
||||
player.onMuteUnmuteButtonClicked();
|
||||
return true;
|
||||
@@ -310,20 +311,20 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
||||
private void buildControls() {
|
||||
repeatButton = rootView.findViewById(R.id.control_repeat);
|
||||
backwardButton = rootView.findViewById(R.id.control_backward);
|
||||
fastRewindButton = rootView.findViewById(R.id.control_fast_rewind);
|
||||
playPauseButton = rootView.findViewById(R.id.control_play_pause);
|
||||
fastForwardButton = rootView.findViewById(R.id.control_fast_forward);
|
||||
forwardButton = rootView.findViewById(R.id.control_forward);
|
||||
shuffleButton = rootView.findViewById(R.id.control_shuffle);
|
||||
playbackSpeedButton = rootView.findViewById(R.id.control_playback_speed);
|
||||
playbackPitchButton = rootView.findViewById(R.id.control_playback_pitch);
|
||||
progressBar = rootView.findViewById(R.id.control_progress_bar);
|
||||
|
||||
repeatButton.setOnClickListener(this);
|
||||
backwardButton.setOnClickListener(this);
|
||||
fastRewindButton.setOnClickListener(this);
|
||||
playPauseButton.setOnClickListener(this);
|
||||
fastForwardButton.setOnClickListener(this);
|
||||
forwardButton.setOnClickListener(this);
|
||||
shuffleButton.setOnClickListener(this);
|
||||
playbackSpeedButton.setOnClickListener(this);
|
||||
playbackPitchButton.setOnClickListener(this);
|
||||
}
|
||||
|
||||
private void buildItemPopupMenu(final PlayQueueItem item, final View view) {
|
||||
@@ -473,16 +474,16 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
||||
player.onRepeatClicked();
|
||||
} else if (view.getId() == backwardButton.getId()) {
|
||||
player.onPlayPrevious();
|
||||
} else if (view.getId() == fastRewindButton.getId()) {
|
||||
player.onFastRewind();
|
||||
} else if (view.getId() == playPauseButton.getId()) {
|
||||
player.onPlayPause();
|
||||
} else if (view.getId() == fastForwardButton.getId()) {
|
||||
player.onFastForward();
|
||||
} else if (view.getId() == forwardButton.getId()) {
|
||||
player.onPlayNext();
|
||||
} else if (view.getId() == shuffleButton.getId()) {
|
||||
player.onShuffleClicked();
|
||||
} else if (view.getId() == playbackSpeedButton.getId()) {
|
||||
openPlaybackParameterDialog();
|
||||
} else if (view.getId() == playbackPitchButton.getId()) {
|
||||
openPlaybackParameterDialog();
|
||||
} else if (view.getId() == metadata.getId()) {
|
||||
scrollToSelected();
|
||||
} else if (view.getId() == progressLiveSync.getId()) {
|
||||
@@ -643,13 +644,13 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
||||
private void onStateChanged(final int state) {
|
||||
switch (state) {
|
||||
case BasePlayer.STATE_PAUSED:
|
||||
playPauseButton.setImageResource(R.drawable.ic_play_arrow_white);
|
||||
playPauseButton.setImageResource(R.drawable.ic_play_arrow_white_24dp);
|
||||
break;
|
||||
case BasePlayer.STATE_PLAYING:
|
||||
playPauseButton.setImageResource(R.drawable.ic_pause_white);
|
||||
playPauseButton.setImageResource(R.drawable.ic_pause_white_24dp);
|
||||
break;
|
||||
case BasePlayer.STATE_COMPLETED:
|
||||
playPauseButton.setImageResource(R.drawable.ic_replay_white);
|
||||
playPauseButton.setImageResource(R.drawable.ic_replay_white_24dp);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -690,8 +691,10 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
||||
|
||||
private void onPlaybackParameterChanged(final PlaybackParameters parameters) {
|
||||
if (parameters != null) {
|
||||
playbackSpeedButton.setText(formatSpeed(parameters.speed));
|
||||
playbackPitchButton.setText(formatPitch(parameters.pitch));
|
||||
if (menu != null && player != null) {
|
||||
final MenuItem item = menu.findItem(R.id.action_playback_speed);
|
||||
item.setTitle(formatSpeed(parameters.speed));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -717,9 +720,9 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
||||
// using rootView.getContext() because getApplicationContext() didn't work
|
||||
item.setIcon(player.isMuted()
|
||||
? ThemeHelper.resolveResourceIdFromAttr(rootView.getContext(),
|
||||
R.attr.volume_off)
|
||||
R.attr.ic_volume_off)
|
||||
: ThemeHelper.resolveResourceIdFromAttr(rootView.getContext(),
|
||||
R.attr.volume_on));
|
||||
R.attr.ic_volume_up));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.appcompat.content.res.AppCompatResources;
|
||||
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.PlaybackParameters;
|
||||
@@ -682,13 +682,13 @@ public abstract class VideoPlayer extends BasePlayer
|
||||
@Override
|
||||
public void onFastRewind() {
|
||||
super.onFastRewind();
|
||||
showAndAnimateControl(R.drawable.ic_action_av_fast_rewind, true);
|
||||
showAndAnimateControl(R.drawable.ic_fast_rewind_white_24dp, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFastForward() {
|
||||
super.onFastForward();
|
||||
showAndAnimateControl(R.drawable.ic_action_av_fast_forward, true);
|
||||
showAndAnimateControl(R.drawable.ic_fast_forward_white_24dp, true);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
@@ -956,7 +956,7 @@ public abstract class VideoPlayer extends BasePlayer
|
||||
|
||||
|
||||
controlAnimationView.setVisibility(View.VISIBLE);
|
||||
controlAnimationView.setImageDrawable(ContextCompat.getDrawable(context, drawableId));
|
||||
controlAnimationView.setImageDrawable(AppCompatResources.getDrawable(context, drawableId));
|
||||
controlViewAnimator.start();
|
||||
}
|
||||
|
||||
|
||||
@@ -36,9 +36,9 @@ public class LoadedMediaSource implements ManagedMediaSource {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepareSource(final SourceInfoRefreshListener listener,
|
||||
public void prepareSource(final MediaSourceCaller mediaSourceCaller,
|
||||
@Nullable final TransferListener mediaTransferListener) {
|
||||
source.prepareSource(listener, mediaTransferListener);
|
||||
source.prepareSource(mediaSourceCaller, mediaTransferListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -46,6 +46,11 @@ public class LoadedMediaSource implements ManagedMediaSource {
|
||||
source.maybeThrowSourceInfoRefreshError();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enable(final MediaSourceCaller caller) {
|
||||
source.enable(caller);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MediaPeriod createPeriod(final MediaPeriodId id, final Allocator allocator,
|
||||
final long startPositionUs) {
|
||||
@@ -58,8 +63,13 @@ public class LoadedMediaSource implements ManagedMediaSource {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void releaseSource(final SourceInfoRefreshListener listener) {
|
||||
source.releaseSource(listener);
|
||||
public void disable(final MediaSourceCaller caller) {
|
||||
source.disable(caller);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void releaseSource(final MediaSourceCaller mediaSourceCaller) {
|
||||
source.releaseSource(mediaSourceCaller);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.schabi.newpipe.player.mediasource;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.android.exoplayer2.source.MediaSource;
|
||||
|
||||
@@ -27,4 +28,10 @@ public interface ManagedMediaSource extends MediaSource {
|
||||
* @return whether this source is for the specified stream
|
||||
*/
|
||||
boolean isStreamEqual(@NonNull PlayQueueItem stream);
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
default Object getTag() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ public class ManagedMediaSourcePlaylist {
|
||||
@Nullable
|
||||
public ManagedMediaSource get(final int index) {
|
||||
return (index < 0 || index >= size())
|
||||
? null : (ManagedMediaSource) internalSource.getMediaSource(index);
|
||||
? null : (ManagedMediaSource) internalSource.getMediaSource(index).getTag();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.schabi.newpipe.player.playback;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Pair;
|
||||
|
||||
@@ -8,6 +9,7 @@ import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.RendererCapabilities.Capabilities;
|
||||
import com.google.android.exoplayer2.source.TrackGroup;
|
||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
|
||||
@@ -25,8 +27,9 @@ import com.google.android.exoplayer2.util.Assertions;
|
||||
public class CustomTrackSelector extends DefaultTrackSelector {
|
||||
private String preferredTextLanguage;
|
||||
|
||||
public CustomTrackSelector(final TrackSelection.Factory adaptiveTrackSelectionFactory) {
|
||||
super(adaptiveTrackSelectionFactory);
|
||||
public CustomTrackSelector(final Context context,
|
||||
final TrackSelection.Factory adaptiveTrackSelectionFactory) {
|
||||
super(context, adaptiveTrackSelectionFactory);
|
||||
}
|
||||
|
||||
private static boolean formatHasLanguage(final Format format, final String language) {
|
||||
@@ -48,27 +51,31 @@ public class CustomTrackSelector extends DefaultTrackSelector {
|
||||
@Override
|
||||
@Nullable
|
||||
protected Pair<TrackSelection.Definition, TextTrackScore> selectTextTrack(
|
||||
final TrackGroupArray groups, final int[][] formatSupport, final Parameters params,
|
||||
final TrackGroupArray groups,
|
||||
@NonNull final int[][] formatSupport,
|
||||
@NonNull final Parameters params,
|
||||
@Nullable final String selectedAudioLanguage) {
|
||||
TrackGroup selectedGroup = null;
|
||||
int selectedTrackIndex = C.INDEX_UNSET;
|
||||
int newPipeTrackScore = 0;
|
||||
TextTrackScore selectedTrackScore = null;
|
||||
|
||||
for (int groupIndex = 0; groupIndex < groups.length; groupIndex++) {
|
||||
TrackGroup trackGroup = groups.get(groupIndex);
|
||||
int[] trackFormatSupport = formatSupport[groupIndex];
|
||||
@Capabilities int[] trackFormatSupport = formatSupport[groupIndex];
|
||||
|
||||
for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
|
||||
if (isSupported(trackFormatSupport[trackIndex],
|
||||
params.exceedRendererCapabilitiesIfNecessary)) {
|
||||
Format format = trackGroup.getFormat(trackIndex);
|
||||
TextTrackScore trackScore = new TextTrackScore(format, params,
|
||||
trackFormatSupport[trackIndex], selectedAudioLanguage);
|
||||
|
||||
if (formatHasLanguage(format, preferredTextLanguage)) {
|
||||
selectedGroup = trackGroup;
|
||||
selectedTrackIndex = trackIndex;
|
||||
selectedTrackScore = trackScore;
|
||||
// found user selected match (perfect!)
|
||||
break;
|
||||
break; // found user selected match (perfect!)
|
||||
|
||||
} else if (trackScore.isWithinConstraints && (selectedTrackScore == null
|
||||
|| trackScore.compareTo(selectedTrackScore) > 0)) {
|
||||
selectedGroup = trackGroup;
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.util.Log;
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.ListExtractor;
|
||||
import org.schabi.newpipe.extractor.ListInfo;
|
||||
import org.schabi.newpipe.extractor.Page;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -21,7 +22,7 @@ abstract class AbstractInfoPlayQueue<T extends ListInfo, U extends InfoItem> ext
|
||||
|
||||
final int serviceId;
|
||||
final String baseUrl;
|
||||
String nextUrl;
|
||||
Page nextPage;
|
||||
|
||||
private transient Disposable fetchReactor;
|
||||
|
||||
@@ -29,16 +30,16 @@ abstract class AbstractInfoPlayQueue<T extends ListInfo, U extends InfoItem> ext
|
||||
this(item.getServiceId(), item.getUrl(), null, Collections.emptyList(), 0);
|
||||
}
|
||||
|
||||
AbstractInfoPlayQueue(final int serviceId, final String url, final String nextPageUrl,
|
||||
AbstractInfoPlayQueue(final int serviceId, final String url, final Page nextPage,
|
||||
final List<StreamInfoItem> streams, final int index) {
|
||||
super(index, extractListItems(streams));
|
||||
|
||||
this.baseUrl = url;
|
||||
this.nextUrl = nextPageUrl;
|
||||
this.nextPage = nextPage;
|
||||
this.serviceId = serviceId;
|
||||
|
||||
this.isInitial = streams.isEmpty();
|
||||
this.isComplete = !isInitial && (nextPageUrl == null || nextPageUrl.isEmpty());
|
||||
this.isComplete = !isInitial && !Page.isValid(nextPage);
|
||||
}
|
||||
|
||||
protected abstract String getTag();
|
||||
@@ -66,7 +67,7 @@ abstract class AbstractInfoPlayQueue<T extends ListInfo, U extends InfoItem> ext
|
||||
if (!result.hasNextPage()) {
|
||||
isComplete = true;
|
||||
}
|
||||
nextUrl = result.getNextPageUrl();
|
||||
nextPage = result.getNextPage();
|
||||
|
||||
append(extractListItems(result.getRelatedItems()));
|
||||
|
||||
@@ -100,7 +101,7 @@ abstract class AbstractInfoPlayQueue<T extends ListInfo, U extends InfoItem> ext
|
||||
if (!result.hasNextPage()) {
|
||||
isComplete = true;
|
||||
}
|
||||
nextUrl = result.getNextPageUrl();
|
||||
nextPage = result.getNextPage();
|
||||
|
||||
append(extractListItems(result.getItems()));
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.schabi.newpipe.player.playqueue;
|
||||
|
||||
|
||||
import org.schabi.newpipe.extractor.Page;
|
||||
import org.schabi.newpipe.extractor.channel.ChannelInfo;
|
||||
import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
@@ -17,15 +18,15 @@ public final class ChannelPlayQueue extends AbstractInfoPlayQueue<ChannelInfo, C
|
||||
}
|
||||
|
||||
public ChannelPlayQueue(final ChannelInfo info) {
|
||||
this(info.getServiceId(), info.getUrl(), info.getNextPageUrl(), info.getRelatedItems(), 0);
|
||||
this(info.getServiceId(), info.getUrl(), info.getNextPage(), info.getRelatedItems(), 0);
|
||||
}
|
||||
|
||||
public ChannelPlayQueue(final int serviceId,
|
||||
final String url,
|
||||
final String nextPageUrl,
|
||||
final Page nextPage,
|
||||
final List<StreamInfoItem> streams,
|
||||
final int index) {
|
||||
super(serviceId, url, nextPageUrl, streams, index);
|
||||
super(serviceId, url, nextPage, streams, index);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -41,7 +42,7 @@ public final class ChannelPlayQueue extends AbstractInfoPlayQueue<ChannelInfo, C
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(getHeadListObserver());
|
||||
} else {
|
||||
ExtractorHelper.getMoreChannelItems(this.serviceId, this.baseUrl, this.nextUrl)
|
||||
ExtractorHelper.getMoreChannelItems(this.serviceId, this.baseUrl, this.nextPage)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(getNextPageObserver());
|
||||
|
||||
@@ -305,25 +305,16 @@ public abstract class PlayQueue implements Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Report an exception for the item at the current index in order and the course of action:
|
||||
* if the error can be skipped or the current item should be removed.
|
||||
* Report an exception for the item at the current index in order and skip to the next one
|
||||
* <p>
|
||||
* This is done as a separate event as the underlying manager may have
|
||||
* different implementation regarding exceptions.
|
||||
* </p>
|
||||
*
|
||||
* @param skippable whether the error could be skipped
|
||||
*/
|
||||
public synchronized void error(final boolean skippable) {
|
||||
final int index = getIndex();
|
||||
|
||||
if (skippable) {
|
||||
queueIndex.incrementAndGet();
|
||||
} else {
|
||||
removeInternal(index);
|
||||
}
|
||||
|
||||
broadcast(new ErrorEvent(index, getIndex(), skippable));
|
||||
public synchronized void error() {
|
||||
final int oldIndex = getIndex();
|
||||
queueIndex.incrementAndGet();
|
||||
broadcast(new ErrorEvent(oldIndex, getIndex()));
|
||||
}
|
||||
|
||||
private synchronized void removeInternal(final int removeIndex) {
|
||||
|
||||
@@ -115,9 +115,6 @@ public class PlayQueueAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
|
||||
break;
|
||||
case ERROR:
|
||||
final ErrorEvent errorEvent = (ErrorEvent) message;
|
||||
if (!errorEvent.isSkippable()) {
|
||||
notifyItemRemoved(errorEvent.getErrorIndex());
|
||||
}
|
||||
notifyItemChanged(errorEvent.getErrorIndex());
|
||||
notifyItemChanged(errorEvent.getQueueIndex());
|
||||
break;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.schabi.newpipe.player.playqueue;
|
||||
|
||||
import org.schabi.newpipe.extractor.Page;
|
||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
|
||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
@@ -16,15 +17,15 @@ public final class PlaylistPlayQueue extends AbstractInfoPlayQueue<PlaylistInfo,
|
||||
}
|
||||
|
||||
public PlaylistPlayQueue(final PlaylistInfo info) {
|
||||
this(info.getServiceId(), info.getUrl(), info.getNextPageUrl(), info.getRelatedItems(), 0);
|
||||
this(info.getServiceId(), info.getUrl(), info.getNextPage(), info.getRelatedItems(), 0);
|
||||
}
|
||||
|
||||
public PlaylistPlayQueue(final int serviceId,
|
||||
final String url,
|
||||
final String nextPageUrl,
|
||||
final Page nextPage,
|
||||
final List<StreamInfoItem> streams,
|
||||
final int index) {
|
||||
super(serviceId, url, nextPageUrl, streams, index);
|
||||
super(serviceId, url, nextPage, streams, index);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -40,7 +41,7 @@ public final class PlaylistPlayQueue extends AbstractInfoPlayQueue<PlaylistInfo,
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(getHeadListObserver());
|
||||
} else {
|
||||
ExtractorHelper.getMorePlaylistItems(this.serviceId, this.baseUrl, this.nextUrl)
|
||||
ExtractorHelper.getMorePlaylistItems(this.serviceId, this.baseUrl, this.nextPage)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(getNextPageObserver());
|
||||
|
||||
@@ -3,12 +3,10 @@ package org.schabi.newpipe.player.playqueue.events;
|
||||
public class ErrorEvent implements PlayQueueEvent {
|
||||
private final int errorIndex;
|
||||
private final int queueIndex;
|
||||
private final boolean skippable;
|
||||
|
||||
public ErrorEvent(final int errorIndex, final int queueIndex, final boolean skippable) {
|
||||
public ErrorEvent(final int errorIndex, final int queueIndex) {
|
||||
this.errorIndex = errorIndex;
|
||||
this.queueIndex = queueIndex;
|
||||
this.skippable = skippable;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -23,8 +21,4 @@ public class ErrorEvent implements PlayQueueEvent {
|
||||
public int getQueueIndex() {
|
||||
return queueIndex;
|
||||
}
|
||||
|
||||
public boolean isSkippable() {
|
||||
return skippable;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.acra.collector.CrashReportData;
|
||||
import org.acra.data.CrashReportData;
|
||||
import org.acra.sender.ReportSender;
|
||||
import org.schabi.newpipe.R;
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.acra.config.ACRAConfiguration;
|
||||
import org.acra.config.CoreConfiguration;
|
||||
import org.acra.sender.ReportSender;
|
||||
import org.acra.sender.ReportSenderFactory;
|
||||
|
||||
@@ -31,7 +31,7 @@ import org.acra.sender.ReportSenderFactory;
|
||||
public class AcraReportSenderFactory implements ReportSenderFactory {
|
||||
@NonNull
|
||||
public ReportSender create(@NonNull final Context context,
|
||||
@NonNull final ACRAConfiguration config) {
|
||||
@NonNull final CoreConfiguration config) {
|
||||
return new AcraReportSender();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
@@ -20,6 +19,7 @@ import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
@@ -32,11 +32,13 @@ import com.google.android.material.snackbar.Snackbar;
|
||||
import com.grack.nanojson.JsonWriter;
|
||||
|
||||
import org.acra.ReportField;
|
||||
import org.acra.collector.CrashReportData;
|
||||
import org.acra.data.CrashReportData;
|
||||
import org.schabi.newpipe.ActivityCommunicator;
|
||||
import org.schabi.newpipe.BuildConfig;
|
||||
import org.schabi.newpipe.MainActivity;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.util.Localization;
|
||||
import org.schabi.newpipe.util.ShareUtils;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
@@ -45,7 +47,6 @@ import java.text.SimpleDateFormat;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
import java.util.Vector;
|
||||
|
||||
@@ -81,6 +82,10 @@ public class ErrorActivity extends AppCompatActivity {
|
||||
public static final String ERROR_EMAIL_ADDRESS = "crashreport@newpipe.schabi.org";
|
||||
public static final String ERROR_EMAIL_SUBJECT
|
||||
= "Exception in NewPipe " + BuildConfig.VERSION_NAME;
|
||||
|
||||
public static final String ERROR_GITHUB_ISSUE_URL
|
||||
= "https://github.com/TeamNewPipe/NewPipe/issues";
|
||||
|
||||
private String[] errorList;
|
||||
private ErrorInfo errorInfo;
|
||||
private Class returnActivity;
|
||||
@@ -149,14 +154,7 @@ public class ErrorActivity extends AppCompatActivity {
|
||||
|
||||
public static void reportError(final Context context, final CrashReportData report,
|
||||
final ErrorInfo errorInfo) {
|
||||
// get key first (don't ask about this solution)
|
||||
ReportField key = null;
|
||||
for (ReportField k : report.keySet()) {
|
||||
if (k.toString().equals("STACK_TRACE")) {
|
||||
key = k;
|
||||
}
|
||||
}
|
||||
String[] el = new String[]{report.get(key).toString()};
|
||||
String[] el = new String[]{report.getString(ReportField.STACK_TRACE)};
|
||||
|
||||
Intent intent = new Intent(context, ErrorActivity.class);
|
||||
intent.putExtra(ERROR_INFO, errorInfo);
|
||||
@@ -200,7 +198,10 @@ public class ErrorActivity extends AppCompatActivity {
|
||||
actionBar.setDisplayShowTitleEnabled(true);
|
||||
}
|
||||
|
||||
Button reportButton = findViewById(R.id.errorReportButton);
|
||||
final Button reportEmailButton = findViewById(R.id.errorReportEmailButton);
|
||||
final Button copyButton = findViewById(R.id.errorReportCopyButton);
|
||||
final Button reportGithubButton = findViewById(R.id.errorReportGitHubButton);
|
||||
|
||||
userCommentBox = findViewById(R.id.errorCommentBox);
|
||||
TextView errorView = findViewById(R.id.errorView);
|
||||
TextView infoView = findViewById(R.id.errorInfosView);
|
||||
@@ -212,37 +213,23 @@ public class ErrorActivity extends AppCompatActivity {
|
||||
errorList = intent.getStringArrayExtra(ERROR_LIST);
|
||||
|
||||
// important add guru meditation
|
||||
addGuruMeditaion();
|
||||
addGuruMeditation();
|
||||
currentTimeStamp = getCurrentTimeStamp();
|
||||
|
||||
reportButton.setOnClickListener((View v) -> {
|
||||
Context context = this;
|
||||
new AlertDialog.Builder(context)
|
||||
.setIcon(android.R.drawable.ic_dialog_alert)
|
||||
.setTitle(R.string.privacy_policy_title)
|
||||
.setMessage(R.string.start_accept_privacy_policy)
|
||||
.setCancelable(false)
|
||||
.setNeutralButton(R.string.read_privacy_policy, (dialog, which) -> {
|
||||
Intent webIntent = new Intent(Intent.ACTION_VIEW,
|
||||
Uri.parse(context.getString(R.string.privacy_policy_url))
|
||||
);
|
||||
context.startActivity(webIntent);
|
||||
})
|
||||
.setPositiveButton(R.string.accept, (dialog, which) -> {
|
||||
Intent i = new Intent(Intent.ACTION_SENDTO);
|
||||
i.setData(Uri.parse("mailto:" + ERROR_EMAIL_ADDRESS))
|
||||
.putExtra(Intent.EXTRA_SUBJECT, ERROR_EMAIL_SUBJECT)
|
||||
.putExtra(Intent.EXTRA_TEXT, buildJson());
|
||||
|
||||
startActivity(Intent.createChooser(i, "Send Email"));
|
||||
})
|
||||
.setNegativeButton(R.string.decline, (dialog, which) -> {
|
||||
// do nothing
|
||||
})
|
||||
.show();
|
||||
|
||||
reportEmailButton.setOnClickListener((View v) -> {
|
||||
openPrivacyPolicyDialog(this, "EMAIL");
|
||||
});
|
||||
|
||||
copyButton.setOnClickListener((View v) -> {
|
||||
ShareUtils.copyToClipboard(this, buildMarkdown());
|
||||
Toast.makeText(this, R.string.msg_copied, Toast.LENGTH_SHORT).show();
|
||||
});
|
||||
|
||||
reportGithubButton.setOnClickListener((View v) -> {
|
||||
openPrivacyPolicyDialog(this, "GITHUB");
|
||||
});
|
||||
|
||||
|
||||
// normal bugreport
|
||||
buildInfo(errorInfo);
|
||||
if (errorInfo.message != 0) {
|
||||
@@ -254,7 +241,7 @@ public class ErrorActivity extends AppCompatActivity {
|
||||
|
||||
errorView.setText(formErrorText(errorList));
|
||||
|
||||
//print stack trace once again for debugging:
|
||||
// print stack trace once again for debugging:
|
||||
for (String e : errorList) {
|
||||
Log.e(TAG, e);
|
||||
}
|
||||
@@ -285,6 +272,37 @@ public class ErrorActivity extends AppCompatActivity {
|
||||
return false;
|
||||
}
|
||||
|
||||
private void openPrivacyPolicyDialog(final Context context, final String action) {
|
||||
new AlertDialog.Builder(context)
|
||||
.setIcon(android.R.drawable.ic_dialog_alert)
|
||||
.setTitle(R.string.privacy_policy_title)
|
||||
.setMessage(R.string.start_accept_privacy_policy)
|
||||
.setCancelable(false)
|
||||
.setNeutralButton(R.string.read_privacy_policy, (dialog, which) -> {
|
||||
ShareUtils.openUrlInBrowser(context,
|
||||
context.getString(R.string.privacy_policy_url));
|
||||
})
|
||||
.setPositiveButton(R.string.accept, (dialog, which) -> {
|
||||
if (action.equals("EMAIL")) { // send on email
|
||||
final Intent i = new Intent(Intent.ACTION_SENDTO)
|
||||
.setData(Uri.parse("mailto:")) // only email apps should handle this
|
||||
.putExtra(Intent.EXTRA_EMAIL, new String[]{ERROR_EMAIL_ADDRESS})
|
||||
.putExtra(Intent.EXTRA_SUBJECT, ERROR_EMAIL_SUBJECT)
|
||||
.putExtra(Intent.EXTRA_TEXT, buildJson());
|
||||
if (i.resolveActivity(getPackageManager()) != null) {
|
||||
startActivity(i);
|
||||
}
|
||||
} else if (action.equals("GITHUB")) { // open the NewPipe issue page on GitHub
|
||||
ShareUtils.openUrlInBrowser(this, ERROR_GITHUB_ISSUE_URL);
|
||||
}
|
||||
|
||||
})
|
||||
.setNegativeButton(R.string.decline, (dialog, which) -> {
|
||||
// do nothing
|
||||
})
|
||||
.show();
|
||||
}
|
||||
|
||||
private String formErrorText(final String[] el) {
|
||||
StringBuilder text = new StringBuilder();
|
||||
if (el != null) {
|
||||
@@ -335,7 +353,9 @@ public class ErrorActivity extends AppCompatActivity {
|
||||
|
||||
text += getUserActionString(info.userAction) + "\n"
|
||||
+ info.request + "\n"
|
||||
+ getContentLangString() + "\n"
|
||||
+ getContentLanguageString() + "\n"
|
||||
+ getContentCountryString() + "\n"
|
||||
+ getAppLanguage() + "\n"
|
||||
+ info.serviceName + "\n"
|
||||
+ currentTimeStamp + "\n"
|
||||
+ getPackageName() + "\n"
|
||||
@@ -351,7 +371,9 @@ public class ErrorActivity extends AppCompatActivity {
|
||||
.object()
|
||||
.value("user_action", getUserActionString(errorInfo.userAction))
|
||||
.value("request", errorInfo.request)
|
||||
.value("content_language", getContentLangString())
|
||||
.value("content_language", getContentLanguageString())
|
||||
.value("content_country", getContentCountryString())
|
||||
.value("app_language", getAppLanguage())
|
||||
.value("service", errorInfo.serviceName)
|
||||
.value("package", getPackageName())
|
||||
.value("version", BuildConfig.VERSION_NAME)
|
||||
@@ -369,6 +391,63 @@ public class ErrorActivity extends AppCompatActivity {
|
||||
return "";
|
||||
}
|
||||
|
||||
private String buildMarkdown() {
|
||||
try {
|
||||
final StringBuilder htmlErrorReport = new StringBuilder();
|
||||
|
||||
final String userComment = userCommentBox.getText().toString();
|
||||
if (!userComment.isEmpty()) {
|
||||
htmlErrorReport.append(userComment).append("\n");
|
||||
}
|
||||
|
||||
// basic error info
|
||||
htmlErrorReport
|
||||
.append("## Exception")
|
||||
.append("\n* __User Action:__ ")
|
||||
.append(getUserActionString(errorInfo.userAction))
|
||||
.append("\n* __Request:__ ").append(errorInfo.request)
|
||||
.append("\n* __Content Country:__ ").append(getContentCountryString())
|
||||
.append("\n* __Content Language:__ ").append(getContentLanguageString())
|
||||
.append("\n* __App Language:__ ").append(getAppLanguage())
|
||||
.append("\n* __Service:__ ").append(errorInfo.serviceName)
|
||||
.append("\n* __Version:__ ").append(BuildConfig.VERSION_NAME)
|
||||
.append("\n* __OS:__ ").append(getOsString()).append("\n");
|
||||
|
||||
|
||||
// Collapse all logs to a single paragraph when there are more than one
|
||||
// to keep the GitHub issue clean.
|
||||
if (errorList.length > 1) {
|
||||
htmlErrorReport
|
||||
.append("<details><summary><b>Exceptions (")
|
||||
.append(errorList.length)
|
||||
.append(")</b></summary><p>\n");
|
||||
}
|
||||
|
||||
// add the logs
|
||||
for (int i = 0; i < errorList.length; i++) {
|
||||
htmlErrorReport.append("<details><summary><b>Crash log ");
|
||||
if (errorList.length > 1) {
|
||||
htmlErrorReport.append(i + 1);
|
||||
}
|
||||
htmlErrorReport.append("</b>")
|
||||
.append("</summary><p>\n")
|
||||
.append("\n```\n").append(errorList[i]).append("\n```\n")
|
||||
.append("</details>\n");
|
||||
}
|
||||
|
||||
// make sure to close everything
|
||||
if (errorList.length > 1) {
|
||||
htmlErrorReport.append("</p></details>\n");
|
||||
}
|
||||
htmlErrorReport.append("<hr>\n");
|
||||
return htmlErrorReport.toString();
|
||||
} catch (Throwable e) {
|
||||
Log.e(TAG, "Error while erroring: Could not build markdown");
|
||||
e.printStackTrace();
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private String getUserActionString(final UserAction userAction) {
|
||||
if (userAction == null) {
|
||||
return "Your description is in another castle.";
|
||||
@@ -377,24 +456,27 @@ public class ErrorActivity extends AppCompatActivity {
|
||||
}
|
||||
}
|
||||
|
||||
private String getContentLangString() {
|
||||
String contentLanguage = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
.getString(this.getString(R.string.content_country_key), "none");
|
||||
if (contentLanguage.equals(getString(R.string.default_localization_key))) {
|
||||
contentLanguage = Locale.getDefault().toString();
|
||||
}
|
||||
return contentLanguage;
|
||||
private String getContentCountryString() {
|
||||
return Localization.getPreferredContentCountry(this).getCountryCode();
|
||||
}
|
||||
|
||||
private String getContentLanguageString() {
|
||||
return Localization.getPreferredLocalization(this).getLocalizationCode();
|
||||
}
|
||||
|
||||
private String getAppLanguage() {
|
||||
return Localization.getAppLocale(getApplicationContext()).toString();
|
||||
}
|
||||
|
||||
private String getOsString() {
|
||||
String osBase = Build.VERSION.SDK_INT >= 23 ? Build.VERSION.BASE_OS : "Android";
|
||||
final String osBase = Build.VERSION.SDK_INT >= 23 ? Build.VERSION.BASE_OS : "Android";
|
||||
return System.getProperty("os.name")
|
||||
+ " " + (osBase.isEmpty() ? "Android" : osBase)
|
||||
+ " " + Build.VERSION.RELEASE
|
||||
+ " - " + Build.VERSION.SDK_INT;
|
||||
}
|
||||
|
||||
private void addGuruMeditaion() {
|
||||
private void addGuruMeditation() {
|
||||
//just an easter egg
|
||||
TextView sorryView = findViewById(R.id.errorSorryView);
|
||||
String text = sorryView.getText().toString();
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.schabi.newpipe.settings;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
@@ -17,6 +18,7 @@ import androidx.preference.Preference;
|
||||
import com.nononsenseapps.filepicker.Utils;
|
||||
import com.nostra13.universalimageloader.core.ImageLoader;
|
||||
|
||||
import org.schabi.newpipe.DownloaderImpl;
|
||||
import org.schabi.newpipe.NewPipeDatabase;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
@@ -56,6 +58,7 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
|
||||
private File newpipeSettings;
|
||||
|
||||
private String thumbnailLoadToggleKey;
|
||||
private String youtubeRestrictedModeEnabledKey;
|
||||
|
||||
private Localization initialSelectedLocalization;
|
||||
private ContentCountry initialSelectedContentCountry;
|
||||
@@ -65,6 +68,7 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
|
||||
public void onCreate(@Nullable final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
thumbnailLoadToggleKey = getString(R.string.download_thumbnail_key);
|
||||
youtubeRestrictedModeEnabledKey = getString(R.string.youtube_restricted_mode_enabled);
|
||||
|
||||
initialSelectedLocalization = org.schabi.newpipe.util.Localization
|
||||
.getPreferredLocalization(requireContext());
|
||||
@@ -86,6 +90,15 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
if (preference.getKey().equals(youtubeRestrictedModeEnabledKey)) {
|
||||
Context context = getContext();
|
||||
if (context != null) {
|
||||
DownloaderImpl.getInstance().updateYoutubeRestrictedModeCookies(context);
|
||||
} else {
|
||||
Log.w(TAG, "onPreferenceTreeClick: null context");
|
||||
}
|
||||
}
|
||||
|
||||
return super.onPreferenceTreeClick(preference);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
@@ -22,6 +23,7 @@ import org.schabi.newpipe.database.subscription.SubscriptionEntity;
|
||||
import org.schabi.newpipe.local.subscription.SubscriptionManager;
|
||||
import org.schabi.newpipe.report.ErrorActivity;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Vector;
|
||||
@@ -62,7 +64,7 @@ public class SelectChannelFragment extends DialogFragment {
|
||||
|
||||
private final ImageLoader imageLoader = ImageLoader.getInstance();
|
||||
|
||||
private OnSelectedLisener onSelectedLisener = null;
|
||||
private OnSelectedListener onSelectedListener = null;
|
||||
private OnCancelListener onCancelListener = null;
|
||||
|
||||
private ProgressBar progressBar;
|
||||
@@ -71,8 +73,8 @@ public class SelectChannelFragment extends DialogFragment {
|
||||
|
||||
private List<SubscriptionEntity> subscriptions = new Vector<>();
|
||||
|
||||
public void setOnSelectedLisener(final OnSelectedLisener listener) {
|
||||
onSelectedLisener = listener;
|
||||
public void setOnSelectedListener(final OnSelectedListener listener) {
|
||||
onSelectedListener = listener;
|
||||
}
|
||||
|
||||
public void setOnCancelListener(final OnCancelListener listener) {
|
||||
@@ -83,6 +85,12 @@ public class SelectChannelFragment extends DialogFragment {
|
||||
// Init
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setStyle(STYLE_NO_TITLE, ThemeHelper.getMinWidthDialogTheme(requireContext()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup container,
|
||||
final Bundle savedInstanceState) {
|
||||
@@ -121,9 +129,9 @@ public class SelectChannelFragment extends DialogFragment {
|
||||
}
|
||||
|
||||
private void clickedItem(final int position) {
|
||||
if (onSelectedLisener != null) {
|
||||
if (onSelectedListener != null) {
|
||||
SubscriptionEntity entry = subscriptions.get(position);
|
||||
onSelectedLisener
|
||||
onSelectedListener
|
||||
.onChannelSelected(entry.getServiceId(), entry.getUrl(), entry.getName());
|
||||
}
|
||||
dismiss();
|
||||
@@ -178,7 +186,7 @@ public class SelectChannelFragment extends DialogFragment {
|
||||
// Interfaces
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
public interface OnSelectedLisener {
|
||||
public interface OnSelectedListener {
|
||||
void onChannelSelected(int serviceId, String url, String name);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,8 @@ import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.content.res.AppCompatResources;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
@@ -21,6 +22,7 @@ import org.schabi.newpipe.report.ErrorActivity;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
import org.schabi.newpipe.util.KioskTranslator;
|
||||
import org.schabi.newpipe.util.ServiceHelper;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Vector;
|
||||
@@ -50,17 +52,27 @@ public class SelectKioskFragment extends DialogFragment {
|
||||
private RecyclerView recyclerView = null;
|
||||
private SelectKioskAdapter selectKioskAdapter = null;
|
||||
|
||||
private OnSelectedLisener onSelectedLisener = null;
|
||||
private OnSelectedListener onSelectedListener = null;
|
||||
private OnCancelListener onCancelListener = null;
|
||||
|
||||
public void setOnSelectedLisener(final OnSelectedLisener listener) {
|
||||
onSelectedLisener = listener;
|
||||
public void setOnSelectedListener(final OnSelectedListener listener) {
|
||||
onSelectedListener = listener;
|
||||
}
|
||||
|
||||
public void setOnCancelListener(final OnCancelListener listener) {
|
||||
onCancelListener = listener;
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Init
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setStyle(STYLE_NO_TITLE, ThemeHelper.getMinWidthDialogTheme(requireContext()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
|
||||
final Bundle savedInstanceState) {
|
||||
@@ -90,8 +102,8 @@ public class SelectKioskFragment extends DialogFragment {
|
||||
}
|
||||
|
||||
private void clickedItem(final SelectKioskAdapter.Entry entry) {
|
||||
if (onSelectedLisener != null) {
|
||||
onSelectedLisener.onKioskSelected(entry.serviceId, entry.kioskId, entry.kioskName);
|
||||
if (onSelectedListener != null) {
|
||||
onSelectedListener.onKioskSelected(entry.serviceId, entry.kioskId, entry.kioskName);
|
||||
}
|
||||
dismiss();
|
||||
}
|
||||
@@ -110,7 +122,7 @@ public class SelectKioskFragment extends DialogFragment {
|
||||
// Interfaces
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
public interface OnSelectedLisener {
|
||||
public interface OnSelectedListener {
|
||||
void onKioskSelected(int serviceId, String kioskId, String kioskName);
|
||||
}
|
||||
|
||||
@@ -148,13 +160,8 @@ public class SelectKioskFragment extends DialogFragment {
|
||||
final Entry entry = kioskList.get(position);
|
||||
holder.titleView.setText(entry.kioskName);
|
||||
holder.thumbnailView
|
||||
.setImageDrawable(ContextCompat.getDrawable(getContext(), entry.icon));
|
||||
holder.view.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(final View view) {
|
||||
clickedItem(entry);
|
||||
}
|
||||
});
|
||||
.setImageDrawable(AppCompatResources.getDrawable(requireContext(), entry.icon));
|
||||
holder.view.setOnClickListener(view -> clickedItem(entry));
|
||||
}
|
||||
|
||||
class Entry {
|
||||
|
||||
@@ -0,0 +1,225 @@
|
||||
package org.schabi.newpipe.settings;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.nostra13.universalimageloader.core.DisplayImageOptions;
|
||||
import com.nostra13.universalimageloader.core.ImageLoader;
|
||||
|
||||
import org.schabi.newpipe.NewPipeDatabase;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.database.AppDatabase;
|
||||
import org.schabi.newpipe.database.LocalItem;
|
||||
import org.schabi.newpipe.database.playlist.PlaylistLocalItem;
|
||||
import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry;
|
||||
import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity;
|
||||
import org.schabi.newpipe.local.playlist.LocalPlaylistManager;
|
||||
import org.schabi.newpipe.local.playlist.RemotePlaylistManager;
|
||||
import org.schabi.newpipe.report.ErrorActivity;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Vector;
|
||||
|
||||
import io.reactivex.Flowable;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
|
||||
public class SelectPlaylistFragment extends DialogFragment {
|
||||
/**
|
||||
* This contains the base display options for images.
|
||||
*/
|
||||
private static final DisplayImageOptions DISPLAY_IMAGE_OPTIONS
|
||||
= new DisplayImageOptions.Builder().cacheInMemory(true).build();
|
||||
|
||||
private final ImageLoader imageLoader = ImageLoader.getInstance();
|
||||
|
||||
private OnSelectedListener onSelectedListener = null;
|
||||
private OnCancelListener onCancelListener = null;
|
||||
|
||||
private ProgressBar progressBar;
|
||||
private TextView emptyView;
|
||||
private RecyclerView recyclerView;
|
||||
private Disposable playlistsSubscriber;
|
||||
|
||||
private List<PlaylistLocalItem> playlists = new Vector<>();
|
||||
|
||||
public void setOnSelectedListener(final OnSelectedListener listener) {
|
||||
onSelectedListener = listener;
|
||||
}
|
||||
|
||||
public void setOnCancelListener(final OnCancelListener listener) {
|
||||
onCancelListener = listener;
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Fragment's Lifecycle
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup container,
|
||||
final Bundle savedInstanceState) {
|
||||
final View v =
|
||||
inflater.inflate(R.layout.select_playlist_fragment, container, false);
|
||||
recyclerView = v.findViewById(R.id.items_list);
|
||||
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
SelectPlaylistAdapter playlistAdapter = new SelectPlaylistAdapter();
|
||||
recyclerView.setAdapter(playlistAdapter);
|
||||
|
||||
progressBar = v.findViewById(R.id.progressBar);
|
||||
emptyView = v.findViewById(R.id.empty_state_view);
|
||||
progressBar.setVisibility(View.VISIBLE);
|
||||
recyclerView.setVisibility(View.GONE);
|
||||
emptyView.setVisibility(View.GONE);
|
||||
|
||||
final AppDatabase database = NewPipeDatabase.getInstance(requireContext());
|
||||
final LocalPlaylistManager localPlaylistManager = new LocalPlaylistManager(database);
|
||||
final RemotePlaylistManager remotePlaylistManager = new RemotePlaylistManager(database);
|
||||
|
||||
playlistsSubscriber = Flowable.combineLatest(localPlaylistManager.getPlaylists(),
|
||||
remotePlaylistManager.getPlaylists(), PlaylistLocalItem::merge)
|
||||
.subscribe(this::displayPlaylists, this::onError);
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
if (playlistsSubscriber != null) {
|
||||
playlistsSubscriber.dispose();
|
||||
playlistsSubscriber = null;
|
||||
}
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Handle actions
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public void onCancel(final DialogInterface dialogInterface) {
|
||||
super.onCancel(dialogInterface);
|
||||
if (onCancelListener != null) {
|
||||
onCancelListener.onCancel();
|
||||
}
|
||||
}
|
||||
|
||||
private void clickedItem(final int position) {
|
||||
if (onSelectedListener != null) {
|
||||
final LocalItem selectedItem = playlists.get(position);
|
||||
|
||||
if (selectedItem instanceof PlaylistMetadataEntry) {
|
||||
final PlaylistMetadataEntry entry = ((PlaylistMetadataEntry) selectedItem);
|
||||
onSelectedListener
|
||||
.onLocalPlaylistSelected(entry.uid, entry.name);
|
||||
|
||||
} else if (selectedItem instanceof PlaylistRemoteEntity) {
|
||||
final PlaylistRemoteEntity entry = ((PlaylistRemoteEntity) selectedItem);
|
||||
onSelectedListener.onRemotePlaylistSelected(
|
||||
entry.getServiceId(), entry.getUrl(), entry.getName());
|
||||
}
|
||||
}
|
||||
dismiss();
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Item handling
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
private void displayPlaylists(final List<PlaylistLocalItem> newPlaylists) {
|
||||
this.playlists = newPlaylists;
|
||||
progressBar.setVisibility(View.GONE);
|
||||
if (newPlaylists.isEmpty()) {
|
||||
emptyView.setVisibility(View.VISIBLE);
|
||||
return;
|
||||
}
|
||||
recyclerView.setVisibility(View.VISIBLE);
|
||||
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Error
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
protected void onError(final Throwable e) {
|
||||
final Activity activity = getActivity();
|
||||
ErrorActivity.reportError(activity, e, activity.getClass(), null, ErrorActivity.ErrorInfo
|
||||
.make(UserAction.UI_ERROR, "none", "", R.string.app_ui_crash));
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Interfaces
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
public interface OnSelectedListener {
|
||||
void onLocalPlaylistSelected(long id, String name);
|
||||
void onRemotePlaylistSelected(int serviceId, String url, String name);
|
||||
}
|
||||
|
||||
public interface OnCancelListener {
|
||||
void onCancel();
|
||||
}
|
||||
|
||||
private class SelectPlaylistAdapter
|
||||
extends RecyclerView.Adapter<SelectPlaylistAdapter.SelectPlaylistItemHolder> {
|
||||
@Override
|
||||
public SelectPlaylistItemHolder onCreateViewHolder(final ViewGroup parent,
|
||||
final int viewType) {
|
||||
final View item = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.list_playlist_mini_item, parent, false);
|
||||
return new SelectPlaylistItemHolder(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(final SelectPlaylistItemHolder holder, final int position) {
|
||||
final PlaylistLocalItem selectedItem = playlists.get(position);
|
||||
|
||||
if (selectedItem instanceof PlaylistMetadataEntry) {
|
||||
final PlaylistMetadataEntry entry = ((PlaylistMetadataEntry) selectedItem);
|
||||
|
||||
holder.titleView.setText(entry.name);
|
||||
holder.view.setOnClickListener(view -> clickedItem(position));
|
||||
imageLoader.displayImage(entry.thumbnailUrl, holder.thumbnailView,
|
||||
DISPLAY_IMAGE_OPTIONS);
|
||||
|
||||
} else if (selectedItem instanceof PlaylistRemoteEntity) {
|
||||
final PlaylistRemoteEntity entry = ((PlaylistRemoteEntity) selectedItem);
|
||||
|
||||
holder.titleView.setText(entry.getName());
|
||||
holder.view.setOnClickListener(view -> clickedItem(position));
|
||||
imageLoader.displayImage(entry.getThumbnailUrl(), holder.thumbnailView,
|
||||
DISPLAY_IMAGE_OPTIONS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return playlists.size();
|
||||
}
|
||||
|
||||
public class SelectPlaylistItemHolder extends RecyclerView.ViewHolder {
|
||||
public final View view;
|
||||
final ImageView thumbnailView;
|
||||
final TextView titleView;
|
||||
|
||||
SelectPlaylistItemHolder(final View v) {
|
||||
super(v);
|
||||
this.view = v;
|
||||
thumbnailView = v.findViewById(R.id.itemThumbnailView);
|
||||
titleView = v.findViewById(R.id.itemTitleView);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,4 +43,4 @@ class DurationListPreference : ListPreference {
|
||||
|
||||
entries = newEntryTitles
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ public final class AddTabDialog {
|
||||
private DialogListAdapter(final Context context, final ChooseTabListItem[] items) {
|
||||
this.inflater = LayoutInflater.from(context);
|
||||
this.items = items;
|
||||
this.fallbackIcon = ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_hot);
|
||||
this.fallbackIcon = ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_kiosk_hot);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -34,6 +34,7 @@ import org.schabi.newpipe.report.ErrorActivity;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
import org.schabi.newpipe.settings.SelectChannelFragment;
|
||||
import org.schabi.newpipe.settings.SelectKioskFragment;
|
||||
import org.schabi.newpipe.settings.SelectPlaylistFragment;
|
||||
import org.schabi.newpipe.settings.tabs.AddTabDialog.ChooseTabListItem;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
@@ -48,7 +49,7 @@ public class ChooseTabsFragment extends Fragment {
|
||||
|
||||
private TabsManager tabsManager;
|
||||
|
||||
private List<Tab> tabList = new ArrayList<>();
|
||||
private final List<Tab> tabList = new ArrayList<>();
|
||||
private ChooseTabsFragment.SelectedTabsAdapter selectedTabsAdapter;
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
@@ -78,10 +79,10 @@ public class ChooseTabsFragment extends Fragment {
|
||||
|
||||
initButton(rootView);
|
||||
|
||||
RecyclerView listSelectedTabs = rootView.findViewById(R.id.selectedTabs);
|
||||
final RecyclerView listSelectedTabs = rootView.findViewById(R.id.selectedTabs);
|
||||
listSelectedTabs.setLayoutManager(new LinearLayoutManager(requireContext()));
|
||||
|
||||
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(getItemTouchCallback());
|
||||
final ItemTouchHelper itemTouchHelper = new ItemTouchHelper(getItemTouchCallback());
|
||||
itemTouchHelper.attachToRecyclerView(listSelectedTabs);
|
||||
|
||||
selectedTabsAdapter = new SelectedTabsAdapter(requireContext(), itemTouchHelper);
|
||||
@@ -138,7 +139,7 @@ public class ChooseTabsFragment extends Fragment {
|
||||
|
||||
private void updateTitle() {
|
||||
if (getActivity() instanceof AppCompatActivity) {
|
||||
ActionBar actionBar = ((AppCompatActivity) getActivity()).getSupportActionBar();
|
||||
final ActionBar actionBar = ((AppCompatActivity) getActivity()).getSupportActionBar();
|
||||
if (actionBar != null) {
|
||||
actionBar.setTitle(R.string.main_page_content);
|
||||
}
|
||||
@@ -201,16 +202,33 @@ public class ChooseTabsFragment extends Fragment {
|
||||
switch (type) {
|
||||
case KIOSK:
|
||||
SelectKioskFragment selectKioskFragment = new SelectKioskFragment();
|
||||
selectKioskFragment.setOnSelectedLisener((serviceId, kioskId, kioskName) ->
|
||||
selectKioskFragment.setOnSelectedListener((serviceId, kioskId, kioskName) ->
|
||||
addTab(new Tab.KioskTab(serviceId, kioskId)));
|
||||
selectKioskFragment.show(requireFragmentManager(), "select_kiosk");
|
||||
return;
|
||||
case CHANNEL:
|
||||
SelectChannelFragment selectChannelFragment = new SelectChannelFragment();
|
||||
selectChannelFragment.setOnSelectedLisener((serviceId, url, name) ->
|
||||
selectChannelFragment.setOnSelectedListener((serviceId, url, name) ->
|
||||
addTab(new Tab.ChannelTab(serviceId, url, name)));
|
||||
selectChannelFragment.show(requireFragmentManager(), "select_channel");
|
||||
return;
|
||||
case PLAYLIST:
|
||||
SelectPlaylistFragment selectPlaylistFragment = new SelectPlaylistFragment();
|
||||
selectPlaylistFragment.setOnSelectedListener(
|
||||
new SelectPlaylistFragment.OnSelectedListener() {
|
||||
@Override
|
||||
public void onLocalPlaylistSelected(final long id, final String name) {
|
||||
addTab(new Tab.PlaylistTab(id, name));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRemotePlaylistSelected(
|
||||
final int serviceId, final String url, final String name) {
|
||||
addTab(new Tab.PlaylistTab(serviceId, url, name));
|
||||
}
|
||||
});
|
||||
selectPlaylistFragment.show(requireFragmentManager(), "select_playlist");
|
||||
return;
|
||||
default:
|
||||
addTab(type.getTab());
|
||||
break;
|
||||
@@ -233,7 +251,7 @@ public class ChooseTabsFragment extends Fragment {
|
||||
case KIOSK:
|
||||
returnList.add(new ChooseTabListItem(tab.getTabId(),
|
||||
getString(R.string.kiosk_page_summary),
|
||||
ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_hot)));
|
||||
ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_kiosk_hot)));
|
||||
break;
|
||||
case CHANNEL:
|
||||
returnList.add(new ChooseTabListItem(tab.getTabId(),
|
||||
@@ -244,9 +262,15 @@ public class ChooseTabsFragment extends Fragment {
|
||||
if (!tabList.contains(tab)) {
|
||||
returnList.add(new ChooseTabListItem(tab.getTabId(),
|
||||
getString(R.string.default_kiosk_page_summary),
|
||||
ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_hot)));
|
||||
ThemeHelper.resolveResourceIdFromAttr(context,
|
||||
R.attr.ic_kiosk_hot)));
|
||||
}
|
||||
break;
|
||||
case PLAYLIST:
|
||||
returnList.add(new ChooseTabListItem(tab.getTabId(),
|
||||
getString(R.string.playlist_page_summary),
|
||||
tab.getTabIconRes(context)));
|
||||
break;
|
||||
default:
|
||||
if (!tabList.contains(tab)) {
|
||||
returnList.add(new ChooseTabListItem(context, tab));
|
||||
@@ -336,7 +360,7 @@ public class ChooseTabsFragment extends Fragment {
|
||||
@Override
|
||||
public ChooseTabsFragment.SelectedTabsAdapter.TabViewHolder onCreateViewHolder(
|
||||
@NonNull final ViewGroup parent, final int viewType) {
|
||||
View view = inflater.inflate(R.layout.list_choose_tabs, parent, false);
|
||||
final View view = inflater.inflate(R.layout.list_choose_tabs, parent, false);
|
||||
return new ChooseTabsFragment.SelectedTabsAdapter.TabViewHolder(view);
|
||||
}
|
||||
|
||||
@@ -392,6 +416,13 @@ public class ChooseTabsFragment extends Fragment {
|
||||
tabName = NewPipe.getNameOfService(((Tab.ChannelTab) tab)
|
||||
.getChannelServiceId()) + "/" + tab.getTabName(requireContext());
|
||||
break;
|
||||
case PLAYLIST:
|
||||
final int serviceId = ((Tab.PlaylistTab) tab).getPlaylistServiceId();
|
||||
final String serviceName = serviceId == -1
|
||||
? getString(R.string.local)
|
||||
: NewPipe.getNameOfService(serviceId);
|
||||
tabName = serviceName + "/" + tab.getTabName(requireContext());
|
||||
break;
|
||||
default:
|
||||
tabName = tab.getTabName(requireContext());
|
||||
break;
|
||||
|
||||
@@ -11,6 +11,7 @@ import com.grack.nanojson.JsonObject;
|
||||
import com.grack.nanojson.JsonSink;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.database.LocalItem.LocalItemType;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
@@ -18,9 +19,11 @@ import org.schabi.newpipe.fragments.BlankFragment;
|
||||
import org.schabi.newpipe.fragments.list.channel.ChannelFragment;
|
||||
import org.schabi.newpipe.fragments.list.kiosk.DefaultKioskFragment;
|
||||
import org.schabi.newpipe.fragments.list.kiosk.KioskFragment;
|
||||
import org.schabi.newpipe.fragments.list.playlist.PlaylistFragment;
|
||||
import org.schabi.newpipe.local.bookmark.BookmarkFragment;
|
||||
import org.schabi.newpipe.local.feed.FeedFragment;
|
||||
import org.schabi.newpipe.local.history.StatisticsPlaylistFragment;
|
||||
import org.schabi.newpipe.local.playlist.LocalPlaylistFragment;
|
||||
import org.schabi.newpipe.local.subscription.SubscriptionFragment;
|
||||
import org.schabi.newpipe.report.ErrorActivity;
|
||||
import org.schabi.newpipe.report.UserAction;
|
||||
@@ -33,7 +36,8 @@ import java.util.Objects;
|
||||
public abstract class Tab {
|
||||
private static final String JSON_TAB_ID_KEY = "tab_id";
|
||||
|
||||
Tab() { }
|
||||
Tab() {
|
||||
}
|
||||
|
||||
Tab(@NonNull final JsonObject jsonObject) {
|
||||
readDataFromJson(jsonObject);
|
||||
@@ -83,6 +87,8 @@ public abstract class Tab {
|
||||
return new KioskTab(jsonObject);
|
||||
case CHANNEL:
|
||||
return new ChannelTab(jsonObject);
|
||||
case PLAYLIST:
|
||||
return new PlaylistTab(jsonObject);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,7 +153,8 @@ public abstract class Tab {
|
||||
BOOKMARKS(new BookmarksTab()),
|
||||
HISTORY(new HistoryTab()),
|
||||
KIOSK(new KioskTab()),
|
||||
CHANNEL(new ChannelTab());
|
||||
CHANNEL(new ChannelTab()),
|
||||
PLAYLIST(new PlaylistTab());
|
||||
|
||||
private Tab tab;
|
||||
|
||||
@@ -231,7 +238,7 @@ public abstract class Tab {
|
||||
@DrawableRes
|
||||
@Override
|
||||
public int getTabIconRes(final Context context) {
|
||||
return ThemeHelper.resolveResourceIdFromAttr(context, R.attr.rss);
|
||||
return ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_rss);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -281,7 +288,7 @@ public abstract class Tab {
|
||||
@DrawableRes
|
||||
@Override
|
||||
public int getTabIconRes(final Context context) {
|
||||
return ThemeHelper.resolveResourceIdFromAttr(context, R.attr.history);
|
||||
return ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_history);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -323,7 +330,7 @@ public abstract class Tab {
|
||||
@DrawableRes
|
||||
@Override
|
||||
public int getTabIconRes(final Context context) {
|
||||
final int kioskIcon = KioskTranslator.getKioskIcons(kioskId, context);
|
||||
final int kioskIcon = KioskTranslator.getKioskIcon(kioskId, context);
|
||||
|
||||
if (kioskIcon <= 0) {
|
||||
throw new IllegalStateException("Kiosk ID is not valid: \"" + kioskId + "\"");
|
||||
@@ -459,7 +466,7 @@ public abstract class Tab {
|
||||
@DrawableRes
|
||||
@Override
|
||||
public int getTabIconRes(final Context context) {
|
||||
return KioskTranslator.getKioskIcons(getDefaultKioskId(context), context);
|
||||
return KioskTranslator.getKioskIcon(getDefaultKioskId(context), context);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -482,4 +489,123 @@ public abstract class Tab {
|
||||
return kioskId;
|
||||
}
|
||||
}
|
||||
|
||||
public static class PlaylistTab extends Tab {
|
||||
public static final int ID = 8;
|
||||
private static final String JSON_PLAYLIST_SERVICE_ID_KEY = "playlist_service_id";
|
||||
private static final String JSON_PLAYLIST_URL_KEY = "playlist_url";
|
||||
private static final String JSON_PLAYLIST_NAME_KEY = "playlist_name";
|
||||
private static final String JSON_PLAYLIST_ID_KEY = "playlist_id";
|
||||
private static final String JSON_PLAYLIST_TYPE_KEY = "playlist_type";
|
||||
private int playlistServiceId;
|
||||
private String playlistUrl;
|
||||
private String playlistName;
|
||||
private long playlistId;
|
||||
private LocalItemType playlistType;
|
||||
|
||||
private PlaylistTab() {
|
||||
this(-1, "<no-name>");
|
||||
}
|
||||
|
||||
public PlaylistTab(final long playlistId, final String playlistName) {
|
||||
this.playlistName = playlistName;
|
||||
this.playlistId = playlistId;
|
||||
this.playlistType = LocalItemType.PLAYLIST_LOCAL_ITEM;
|
||||
this.playlistServiceId = -1;
|
||||
this.playlistUrl = "<no-url>";
|
||||
}
|
||||
|
||||
public PlaylistTab(final int playlistServiceId, final String playlistUrl,
|
||||
final String playlistName) {
|
||||
this.playlistServiceId = playlistServiceId;
|
||||
this.playlistUrl = playlistUrl;
|
||||
this.playlistName = playlistName;
|
||||
this.playlistType = LocalItemType.PLAYLIST_REMOTE_ITEM;
|
||||
this.playlistId = -1;
|
||||
}
|
||||
|
||||
public PlaylistTab(final JsonObject jsonObject) {
|
||||
super(jsonObject);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTabId() {
|
||||
return ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTabName(final Context context) {
|
||||
return playlistName;
|
||||
}
|
||||
|
||||
@DrawableRes
|
||||
@Override
|
||||
public int getTabIconRes(final Context context) {
|
||||
return ThemeHelper.resolveResourceIdFromAttr(context, R.attr.ic_bookmark);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Fragment getFragment(final Context context) {
|
||||
if (playlistType == LocalItemType.PLAYLIST_LOCAL_ITEM) {
|
||||
return LocalPlaylistFragment.getInstance(playlistId, playlistName);
|
||||
|
||||
} else { // playlistType == LocalItemType.PLAYLIST_REMOTE_ITEM
|
||||
return PlaylistFragment.getInstance(playlistServiceId, playlistUrl, playlistName);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeDataToJson(final JsonSink writerSink) {
|
||||
writerSink.value(JSON_PLAYLIST_SERVICE_ID_KEY, playlistServiceId)
|
||||
.value(JSON_PLAYLIST_URL_KEY, playlistUrl)
|
||||
.value(JSON_PLAYLIST_NAME_KEY, playlistName)
|
||||
.value(JSON_PLAYLIST_ID_KEY, playlistId)
|
||||
.value(JSON_PLAYLIST_TYPE_KEY, playlistType.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void readDataFromJson(final JsonObject jsonObject) {
|
||||
playlistServiceId = jsonObject.getInt(JSON_PLAYLIST_SERVICE_ID_KEY, -1);
|
||||
playlistUrl = jsonObject.getString(JSON_PLAYLIST_URL_KEY, "<no-url>");
|
||||
playlistName = jsonObject.getString(JSON_PLAYLIST_NAME_KEY, "<no-name>");
|
||||
playlistId = jsonObject.getInt(JSON_PLAYLIST_ID_KEY, -1);
|
||||
playlistType = LocalItemType.valueOf(
|
||||
jsonObject.getString(JSON_PLAYLIST_TYPE_KEY,
|
||||
LocalItemType.PLAYLIST_LOCAL_ITEM.toString())
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (!(super.equals(obj)
|
||||
&& Objects.equals(playlistType, ((PlaylistTab) obj).playlistType)
|
||||
&& Objects.equals(playlistName, ((PlaylistTab) obj).playlistName))) {
|
||||
return false; // base objects are different
|
||||
}
|
||||
|
||||
return (playlistId == ((PlaylistTab) obj).playlistId) // local
|
||||
|| (playlistServiceId == ((PlaylistTab) obj).playlistServiceId // remote
|
||||
&& Objects.equals(playlistUrl, ((PlaylistTab) obj).playlistUrl));
|
||||
}
|
||||
|
||||
public int getPlaylistServiceId() {
|
||||
return playlistServiceId;
|
||||
}
|
||||
|
||||
public String getPlaylistUrl() {
|
||||
return playlistUrl;
|
||||
}
|
||||
|
||||
public String getPlaylistName() {
|
||||
return playlistName;
|
||||
}
|
||||
|
||||
public long getPlaylistId() {
|
||||
return playlistId;
|
||||
}
|
||||
|
||||
public LocalItemType getPlaylistType() {
|
||||
return playlistType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,8 @@ public class DataReader {
|
||||
return readBuffer[readOffset++] & 0xFF;
|
||||
}
|
||||
|
||||
public long skipBytes(long amount) throws IOException {
|
||||
public long skipBytes(final long byteAmount) throws IOException {
|
||||
long amount = byteAmount;
|
||||
if (readCount < 0) {
|
||||
return 0;
|
||||
} else if (readCount == 0) {
|
||||
@@ -90,7 +91,10 @@ public class DataReader {
|
||||
return read(buffer, 0, buffer.length);
|
||||
}
|
||||
|
||||
public int read(final byte[] buffer, int offset, int count) throws IOException {
|
||||
public int read(final byte[] buffer, final int off, final int c) throws IOException {
|
||||
int offset = off;
|
||||
int count = c;
|
||||
|
||||
if (readCount < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ import org.schabi.newpipe.streams.Mp4DashReader.Mdia;
|
||||
import org.schabi.newpipe.streams.Mp4DashReader.Mp4DashChunk;
|
||||
import org.schabi.newpipe.streams.Mp4DashReader.Mp4DashSample;
|
||||
import org.schabi.newpipe.streams.Mp4DashReader.Mp4Track;
|
||||
import org.schabi.newpipe.streams.Mp4DashReader.TrunEntry;
|
||||
import org.schabi.newpipe.streams.Mp4DashReader.TrackKind;
|
||||
import org.schabi.newpipe.streams.Mp4DashReader.TrunEntry;
|
||||
import org.schabi.newpipe.streams.io.SharpStream;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -464,16 +464,16 @@ public class Mp4FromDashWriter {
|
||||
}
|
||||
|
||||
private void initChunkTables(final TablesInfo tables, final int firstCount,
|
||||
final int succesiveCount) {
|
||||
final int successiveCount) {
|
||||
// tables.stsz holds amount of samples of the track (total)
|
||||
int totalSamples = (tables.stsz - firstCount);
|
||||
float chunkAmount = totalSamples / (float) succesiveCount;
|
||||
float chunkAmount = totalSamples / (float) successiveCount;
|
||||
int remainChunkOffset = (int) Math.ceil(chunkAmount);
|
||||
boolean remain = remainChunkOffset != (int) chunkAmount;
|
||||
int index = 0;
|
||||
|
||||
tables.stsc = 1;
|
||||
if (firstCount != succesiveCount) {
|
||||
if (firstCount != successiveCount) {
|
||||
tables.stsc++;
|
||||
}
|
||||
if (remain) {
|
||||
@@ -488,15 +488,15 @@ public class Mp4FromDashWriter {
|
||||
tables.stscBEntries[index++] = firstCount;
|
||||
tables.stscBEntries[index++] = 1;
|
||||
|
||||
if (firstCount != succesiveCount) {
|
||||
if (firstCount != successiveCount) {
|
||||
tables.stscBEntries[index++] = 2;
|
||||
tables.stscBEntries[index++] = succesiveCount;
|
||||
tables.stscBEntries[index++] = successiveCount;
|
||||
tables.stscBEntries[index++] = 1;
|
||||
}
|
||||
|
||||
if (remain) {
|
||||
tables.stscBEntries[index++] = remainChunkOffset + 1;
|
||||
tables.stscBEntries[index++] = totalSamples % succesiveCount;
|
||||
tables.stscBEntries[index++] = totalSamples % successiveCount;
|
||||
tables.stscBEntries[index] = 1;
|
||||
}
|
||||
}
|
||||
@@ -640,19 +640,20 @@ public class Mp4FromDashWriter {
|
||||
return size;
|
||||
}
|
||||
|
||||
private byte[] makeMdat(long refSize, final boolean is64) {
|
||||
private byte[] makeMdat(final long refSize, final boolean is64) {
|
||||
long size = refSize;
|
||||
if (is64) {
|
||||
refSize += 16;
|
||||
size += 16;
|
||||
} else {
|
||||
refSize += 8;
|
||||
size += 8;
|
||||
}
|
||||
|
||||
ByteBuffer buffer = ByteBuffer.allocate(is64 ? 16 : 8)
|
||||
.putInt(is64 ? 0x01 : (int) refSize)
|
||||
.putInt(is64 ? 0x01 : (int) size)
|
||||
.putInt(0x6D646174); // mdat
|
||||
|
||||
if (is64) {
|
||||
buffer.putLong(refSize);
|
||||
buffer.putLong(size);
|
||||
}
|
||||
|
||||
return buffer.array();
|
||||
@@ -711,23 +712,12 @@ public class Mp4FromDashWriter {
|
||||
|
||||
for (int i = 0; i < tracks.length; i++) {
|
||||
if (tracks[i].trak.tkhd.matrix.length != 36) {
|
||||
throw new RuntimeException("bad track matrix length (expected 36) in track n°" + i);
|
||||
throw
|
||||
new RuntimeException("bad track matrix length (expected 36) in track n°" + i);
|
||||
}
|
||||
makeTrak(i, durations[i], defaultMediaTime[i], tablesInfo[i], is64);
|
||||
}
|
||||
|
||||
// udta/meta/ilst/©too
|
||||
auxWrite(new byte[]{
|
||||
0x00, 0x00, 0x00, 0x5C, 0x75, 0x64, 0x74, 0x61, 0x00, 0x00, 0x00, 0x54, 0x6D, 0x65,
|
||||
0x74, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x68, 0x64, 0x6C, 0x72,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6D, 0x64, 0x69, 0x72, 0x61, 0x70,
|
||||
0x70, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x27, 0x69, 0x6C, 0x73, 0x74, 0x00, 0x00, 0x00,
|
||||
0x1F, (byte) 0xA9, 0x74, 0x6F, 0x6F, 0x00, 0x00, 0x00, 0x17, 0x64, 0x61, 0x74, 0x61,
|
||||
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
|
||||
0x4E, 0x65, 0x77, 0x50, 0x69, 0x70, 0x65 // "NewPipe" binary string
|
||||
});
|
||||
|
||||
return lengthFor(start);
|
||||
}
|
||||
|
||||
@@ -849,20 +839,10 @@ public class Mp4FromDashWriter {
|
||||
|
||||
private byte[] makeHdlr(final Hdlr hdlr) {
|
||||
ByteBuffer buffer = ByteBuffer.wrap(new byte[]{
|
||||
0x00, 0x00, 0x00, 0x77, 0x68, 0x64, 0x6C, 0x72, // hdlr
|
||||
0x00, 0x00, 0x00, 0x21, 0x68, 0x64, 0x6C, 0x72, // hdlr
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// binary string
|
||||
// "ISO Media file created in NewPipe (
|
||||
// A libre lightweight streaming frontend for Android)."
|
||||
0x49, 0x53, 0x4F, 0x20, 0x4D, 0x65, 0x64, 0x69, 0x61, 0x20, 0x66, 0x69, 0x6C, 0x65,
|
||||
0x20, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x20, 0x69, 0x6E, 0x20, 0x4E, 0x65,
|
||||
0x77, 0x50, 0x69, 0x70, 0x65, 0x20, 0x28, 0x41, 0x20, 0x6C, 0x69, 0x62, 0x72, 0x65,
|
||||
0x20, 0x6C, 0x69, 0x67, 0x68, 0x74, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x20, 0x73,
|
||||
0x74, 0x72, 0x65, 0x61, 0x6D, 0x69, 0x6E, 0x67,
|
||||
0x20, 0x66, 0x72, 0x6F, 0x6E, 0x74, 0x65, 0x6E, 0x64, 0x20, 0x66, 0x6F, 0x72, 0x20,
|
||||
0x41, 0x6E,
|
||||
0x64, 0x72, 0x6F, 0x69, 0x64, 0x29, 0x2E
|
||||
0x00// null string character
|
||||
});
|
||||
|
||||
buffer.position(12);
|
||||
@@ -898,7 +878,7 @@ public class Mp4FromDashWriter {
|
||||
* characteristics of sample groups. The descriptive information is any other
|
||||
* information needed to define or characterize the sample group.
|
||||
*
|
||||
* ¿is replicabled this box?
|
||||
* ¿is replicable this box?
|
||||
* NO due lacks of documentation about this box but...
|
||||
* most of m4a encoders and ffmpeg uses this box with dummy values (same values)
|
||||
*/
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user