mirror of
https://github.com/CappielloAntonio/tempo.git
synced 2026-01-31 14:43:36 +00:00
Compare commits
170 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8f6e775ca9 | ||
|
|
bad0fa6c23 | ||
|
|
d4caa6f209 | ||
|
|
723bdf9771 | ||
|
|
49fbd85bb4 | ||
|
|
f040fbf0cf | ||
|
|
efb2213ab7 | ||
|
|
742ac6b17d | ||
|
|
ae7761cb96 | ||
|
|
c977982d64 | ||
|
|
28fc3dca36 | ||
|
|
f1cf65a371 | ||
|
|
beb1d29e8f | ||
|
|
1eda7cef9e | ||
|
|
1af92ad949 | ||
|
|
3fc9b35fe4 | ||
|
|
56b48dbd4d | ||
|
|
1cb371dc5a | ||
|
|
499001a269 | ||
|
|
4c9e47379d | ||
|
|
a0dbb5c81f | ||
|
|
dab53c6bbf | ||
|
|
c0a665c00a | ||
|
|
41b5c57240 | ||
|
|
1d65a79c20 | ||
|
|
efb6e72636 | ||
|
|
af83ffd608 | ||
|
|
0201077bc4 | ||
|
|
91d91d3024 | ||
|
|
9784a2b6c5 | ||
|
|
87a3301912 | ||
|
|
295795edc9 | ||
|
|
6120ab66ba | ||
|
|
7bea180c58 | ||
|
|
a29cee488e | ||
|
|
74b4b04693 | ||
|
|
b8b9c80bdc | ||
|
|
a50fc74117 | ||
|
|
c1af438a3a | ||
|
|
80b251cddc | ||
|
|
7d9a48818e | ||
|
|
ca3da0839b | ||
|
|
cf463d8fa1 | ||
|
|
635fdc4c5c | ||
|
|
3e913931c4 | ||
|
|
46420da038 | ||
|
|
d5b7619dd1 | ||
|
|
89b39123da | ||
|
|
5a43137984 | ||
|
|
761b07450f | ||
|
|
a0b67a06f4 | ||
|
|
59d7adb66d | ||
|
|
0c2f0d23cd | ||
|
|
dc201e6c8f | ||
|
|
9bf3399371 | ||
|
|
28565b691a | ||
|
|
7c6faf66c1 | ||
|
|
b160274859 | ||
|
|
3a1ced65d5 | ||
|
|
afeb72803d | ||
|
|
600c28c2a3 | ||
|
|
ec799fff96 | ||
|
|
60da08cdbc | ||
|
|
bdda3743db | ||
|
|
59fcf11ae3 | ||
|
|
77c80b7695 | ||
|
|
13f168f78c | ||
|
|
324eed7e02 | ||
|
|
4a99c7e9b1 | ||
|
|
01e5917642 | ||
|
|
ae00f4279e | ||
|
|
642c69eb96 | ||
|
|
ac69361735 | ||
|
|
e24efc4948 | ||
|
|
f8ad18ed5a | ||
|
|
17345372a2 | ||
|
|
db76494525 | ||
|
|
e87eda2757 | ||
|
|
06e2729aca | ||
|
|
37ffb88d67 | ||
|
|
3d4437151a | ||
|
|
af1961b185 | ||
|
|
c983e33522 | ||
|
|
b18daec708 | ||
|
|
6d20995e70 | ||
|
|
14cacd1bbc | ||
|
|
9d7acdb892 | ||
|
|
05325913f7 | ||
|
|
5733dca68a | ||
|
|
838d4496e5 | ||
|
|
4967363116 | ||
|
|
fc61308be5 | ||
|
|
5c4a292542 | ||
|
|
aa68c2c3d8 | ||
|
|
9c0ebca66f | ||
|
|
888f177597 | ||
|
|
c69fbcfa68 | ||
|
|
f044db142c | ||
|
|
9af7bc3ac8 | ||
|
|
cd87fcde26 | ||
|
|
9e1a6c804f | ||
|
|
4c15f6eb01 | ||
|
|
4ad2722e81 | ||
|
|
b267b904cc | ||
|
|
1ebe9ff8ba | ||
|
|
623a4956a5 | ||
|
|
6572b846c8 | ||
|
|
fe3ba9fb89 | ||
|
|
c4b9db303a | ||
|
|
aed52fdbf8 | ||
|
|
f9573b3eab | ||
|
|
7a8880ee68 | ||
|
|
68aae32d06 | ||
|
|
4b07f37378 | ||
|
|
4d573c6b9d | ||
|
|
10dcb2380c | ||
|
|
fcbe4377aa | ||
|
|
84db4060e6 | ||
|
|
b73a1c532b | ||
|
|
4fe27067e9 | ||
|
|
9c6981ed19 | ||
|
|
560ac2df68 | ||
|
|
260e6e9daa | ||
|
|
38a1368c76 | ||
|
|
9bf3139bd2 | ||
|
|
d389d1d62a | ||
|
|
e60c0e312c | ||
|
|
dc13beca49 | ||
|
|
6399488819 | ||
|
|
69c1b93d28 | ||
|
|
50c240793a | ||
|
|
80f7783b7d | ||
|
|
259c8b3599 | ||
|
|
ba1295440b | ||
|
|
9e6178037c | ||
|
|
47302815c1 | ||
|
|
c81bfe1457 | ||
|
|
f413b5d498 | ||
|
|
feae1b8bdd | ||
|
|
57bb51fb8d | ||
|
|
0f1364502a | ||
|
|
a508ec0af6 | ||
|
|
a9b264004c | ||
|
|
803b61430f | ||
|
|
25367512c0 | ||
|
|
e3720f4a50 | ||
|
|
1168cc58f4 | ||
|
|
c711b387bb | ||
|
|
a3fe0de233 | ||
|
|
922153837d | ||
|
|
960dd6c76a | ||
|
|
fcb7135c3d | ||
|
|
3102bff729 | ||
|
|
992b16cc1d | ||
|
|
2046872d79 | ||
|
|
7312ae20d1 | ||
|
|
e8f2385928 | ||
|
|
204f9ff4c7 | ||
|
|
17e64f3dd0 | ||
|
|
d09e7e7981 | ||
|
|
95e9d06e47 | ||
|
|
f147c762d5 | ||
|
|
200ce6555b | ||
|
|
b5e5537691 | ||
|
|
3b80725673 | ||
|
|
d7c5be834e | ||
|
|
4a5672fc77 | ||
|
|
34fa2f456e | ||
|
|
663285611b | ||
|
|
a62cc78e18 |
22
.github/workflows/github_release.yml
vendored
22
.github/workflows/github_release.yml
vendored
@@ -6,19 +6,15 @@ on:
|
|||||||
- '[0-9]+.[0-9]+.[0-9]+'
|
- '[0-9]+.[0-9]+.[0-9]+'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
setup:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Setup JDK 8
|
|
||||||
uses: actions/setup-java@v3
|
|
||||||
with:
|
|
||||||
distribution: 'temurin'
|
|
||||||
java-version: 8
|
|
||||||
|
|
||||||
build:
|
build:
|
||||||
needs: setup
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
- name: Setup JDK 17
|
||||||
|
uses: actions/setup-java@v2
|
||||||
|
with:
|
||||||
|
distribution: 'zulu'
|
||||||
|
java-version: '17'
|
||||||
|
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Cache Gradle and wrapper
|
- name: Cache Gradle and wrapper
|
||||||
@@ -34,13 +30,13 @@ jobs:
|
|||||||
|
|
||||||
- name: Build APK
|
- name: Build APK
|
||||||
id: build
|
id: build
|
||||||
run: bash ./gradlew assembleRelease
|
run: bash ./gradlew assembleTempoRelease
|
||||||
|
|
||||||
- name: Sign APK
|
- name: Sign APK
|
||||||
id: sign_apk
|
id: sign_apk
|
||||||
uses: r0adkll/sign-android-release@v1
|
uses: r0adkll/sign-android-release@v1
|
||||||
with:
|
with:
|
||||||
releaseDirectory: app/build/outputs/apk/release
|
releaseDirectory: app/build/outputs/apk/tempo/release
|
||||||
signingKeyBase64: ${{ secrets.KEYSTORE_BASE64 }}
|
signingKeyBase64: ${{ secrets.KEYSTORE_BASE64 }}
|
||||||
alias: ${{ secrets.KEY_ALIAS_GITHUB }}
|
alias: ${{ secrets.KEY_ALIAS_GITHUB }}
|
||||||
keyStorePassword: ${{ secrets.KEYSTORE_PASSWORD }}
|
keyStorePassword: ${{ secrets.KEYSTORE_PASSWORD }}
|
||||||
@@ -98,7 +94,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
asset_path: ${{steps.sign_apk.outputs.signedReleaseFile}}
|
asset_path: ${{steps.sign_apk.outputs.signedReleaseFile}}
|
||||||
asset_name: app-release.apk
|
asset_name: app-tempo-release.apk
|
||||||
asset_content_type: application/zip
|
asset_content_type: application/zip
|
||||||
|
|
||||||
# - name: Upload AAB
|
# - name: Upload AAB
|
||||||
|
|||||||
2
.idea/gradle.xml
generated
2
.idea/gradle.xml
generated
@@ -7,7 +7,7 @@
|
|||||||
<option name="testRunner" value="GRADLE" />
|
<option name="testRunner" value="GRADLE" />
|
||||||
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
||||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||||
<option name="gradleJvm" value="11" />
|
<option name="gradleJvm" value="17" />
|
||||||
<option name="modules">
|
<option name="modules">
|
||||||
<set>
|
<set>
|
||||||
<option value="$PROJECT_DIR$" />
|
<option value="$PROJECT_DIR$" />
|
||||||
|
|||||||
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@@ -191,7 +191,7 @@
|
|||||||
</map>
|
</map>
|
||||||
</option>
|
</option>
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="17" project-jdk-type="JavaSDK">
|
||||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectType">
|
<component name="ProjectType">
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
<h1 align="center"> Tempo </h1>
|
|
||||||
<br>
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img alt="Tempo" title="Tempo" src="mockup/svg/horizontal_logo.svg" width="250">
|
<img alt="Tempo" title="Tempo" src="mockup/svg/horizontal_logo.svg" width="250">
|
||||||
</p>
|
</p>
|
||||||
@@ -35,12 +33,8 @@ Tempo does not rely on magic algorithms to decide what you should listen to. Ins
|
|||||||
<img src="mockup/feat/6_screenshot.png" width=200>
|
<img src="mockup/feat/6_screenshot.png" width=200>
|
||||||
<img src="mockup/feat/7_screenshot.png" width=200>
|
<img src="mockup/feat/7_screenshot.png" width=200>
|
||||||
<img src="mockup/feat/8_screenshot.png" width=200>
|
<img src="mockup/feat/8_screenshot.png" width=200>
|
||||||
<img src="mockup/feat/9_screenshot.png" width=200>
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
## Disclaimer
|
|
||||||
Tempo is currently under active development and is in alpha state. This means that the app may contain stability issues, bugs, or incomplete features. While we strive to provide a smooth and reliable experience, please be aware that using Tempo in its current state may not be as stable as a fully released version. I appreciate your understanding and patience as I work towards improving the app.
|
|
||||||
|
|
||||||
## Sponsors
|
## Sponsors
|
||||||
Tempo is an open-source project developed and maintained solely by me. I would like to express my heartfelt thanks to all the users who have shown their love and support for Tempo. Your contributions and encouragement mean a lot to me, and they help drive the development and improvement of the app.
|
Tempo is an open-source project developed and maintained solely by me. I would like to express my heartfelt thanks to all the users who have shown their love and support for Tempo. Your contributions and encouragement mean a lot to me, and they help drive the development and improvement of the app.
|
||||||
|
|
||||||
|
|||||||
@@ -3,15 +3,12 @@ apply plugin: 'kotlin-android'
|
|||||||
apply plugin: 'kotlin-parcelize'
|
apply plugin: 'kotlin-parcelize'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 34
|
compileSdk 34
|
||||||
buildToolsVersion '33.0.0'
|
buildToolsVersion "34.0.0"
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId 'com.cappielloantonio.tempo'
|
|
||||||
minSdkVersion 24
|
minSdkVersion 24
|
||||||
targetSdkVersion 34
|
targetSdkVersion 34
|
||||||
versionCode 5
|
|
||||||
versionName '3.3.0'
|
|
||||||
|
|
||||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||||
|
|
||||||
@@ -25,6 +22,24 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
flavorDimensions "default"
|
||||||
|
|
||||||
|
productFlavors {
|
||||||
|
tempo {
|
||||||
|
dimension "default"
|
||||||
|
applicationId 'com.cappielloantonio.tempo'
|
||||||
|
versionCode 19
|
||||||
|
versionName '3.5.4'
|
||||||
|
}
|
||||||
|
|
||||||
|
notquitemy {
|
||||||
|
dimension "default"
|
||||||
|
applicationId "com.cappielloantonio.notquitemy.tempo"
|
||||||
|
versionCode 1
|
||||||
|
versionName "1.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
shrinkResources true
|
shrinkResources true
|
||||||
@@ -54,51 +69,35 @@ dependencies {
|
|||||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
|
|
||||||
// AndroidX
|
// AndroidX
|
||||||
implementation 'androidx.core:core-ktx:1.10.1'
|
|
||||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||||
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.2.0'
|
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.2.0'
|
||||||
implementation 'androidx.preference:preference-ktx:1.2.0'
|
implementation 'androidx.preference:preference-ktx:1.2.1'
|
||||||
implementation 'androidx.navigation:navigation-fragment-ktx:2.6.0'
|
implementation 'androidx.navigation:navigation-fragment-ktx:2.7.1'
|
||||||
implementation 'androidx.navigation:navigation-ui-ktx:2.6.0'
|
implementation 'androidx.navigation:navigation-ui-ktx:2.7.1'
|
||||||
implementation 'androidx.recyclerview:recyclerview:1.3.0'
|
implementation 'androidx.recyclerview:recyclerview:1.3.1'
|
||||||
implementation 'androidx.room:room-runtime:2.5.1'
|
implementation 'androidx.room:room-runtime:2.5.2'
|
||||||
implementation 'androidx.cardview:cardview:1.0.0'
|
|
||||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
|
||||||
implementation 'androidx.core:core-splashscreen:1.0.1'
|
implementation 'androidx.core:core-splashscreen:1.0.1'
|
||||||
|
implementation "androidx.appcompat:appcompat:1.6.1"
|
||||||
// Google GMS
|
|
||||||
implementation 'com.google.android.gms:play-services-cast-framework:21.3.0'
|
|
||||||
|
|
||||||
// Android Material
|
// Android Material
|
||||||
implementation 'com.google.android.material:material:1.9.0'
|
implementation 'com.google.android.material:material:1.9.0'
|
||||||
|
|
||||||
// SearchBar
|
|
||||||
implementation 'com.paulrybitskyi.persistentsearchview:persistentsearchview:1.1.4'
|
|
||||||
implementation 'com.arthurivanets.adapster:adapster:1.0.13'
|
|
||||||
|
|
||||||
// Glide
|
// Glide
|
||||||
implementation 'com.github.bumptech.glide:glide:4.15.1'
|
implementation 'com.github.bumptech.glide:glide:4.16.0'
|
||||||
implementation 'com.github.bumptech.glide:annotations:4.15.1'
|
implementation 'com.github.bumptech.glide:annotations:4.16.0'
|
||||||
|
|
||||||
// Media3
|
// Media3
|
||||||
implementation 'androidx.media3:media3-session:1.0.2'
|
implementation 'androidx.media3:media3-session:1.1.1'
|
||||||
implementation 'androidx.media3:media3-common:1.0.2'
|
implementation 'androidx.media3:media3-common:1.1.1'
|
||||||
implementation 'androidx.media3:media3-exoplayer:1.0.2'
|
implementation 'androidx.media3:media3-exoplayer:1.1.1'
|
||||||
implementation 'androidx.media3:media3-ui:1.0.2'
|
implementation 'androidx.media3:media3-ui:1.1.1'
|
||||||
implementation 'androidx.media3:media3-cast:1.0.2'
|
tempoImplementation 'androidx.media3:media3-cast:1.1.1'
|
||||||
|
|
||||||
annotationProcessor 'com.github.bumptech.glide:compiler:4.15.1'
|
annotationProcessor 'com.github.bumptech.glide:compiler:4.16.0'
|
||||||
annotationProcessor 'androidx.room:room-compiler:2.5.1'
|
annotationProcessor 'androidx.room:room-compiler:2.5.2'
|
||||||
|
|
||||||
// Retrofit
|
// Retrofit
|
||||||
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
|
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
|
||||||
implementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.6'
|
implementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.11'
|
||||||
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
|
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
|
||||||
|
|
||||||
// Crash Report
|
|
||||||
// debugImplementation 'com.balsikandar.android:crashreporter:1.1.0'
|
|
||||||
|
|
||||||
// DB debug
|
|
||||||
// debugImplementation 'com.amitshekhar.android:debug-db:1.0.6'
|
|
||||||
}
|
}
|
||||||
3
app/proguard-rules.pro
vendored
3
app/proguard-rules.pro
vendored
@@ -21,4 +21,5 @@
|
|||||||
#-renamesourcefileattribute SourceFile
|
#-renamesourcefileattribute SourceFile
|
||||||
|
|
||||||
-keepattributes SourceFile, LineNumberTable
|
-keepattributes SourceFile, LineNumberTable
|
||||||
-keep public class * extends java.lang.Exception
|
-keep public class * extends java.lang.Exception
|
||||||
|
-keep class retrofit2.** { *; }
|
||||||
@@ -0,0 +1,746 @@
|
|||||||
|
{
|
||||||
|
"formatVersion": 1,
|
||||||
|
"database": {
|
||||||
|
"version": 1,
|
||||||
|
"identityHash": "1f4e50f90f58fb9cb53c89747d142fd9",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"tableName": "queue",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `track_order` INTEGER NOT NULL, `last_play` INTEGER NOT NULL, `playing_changed` INTEGER NOT NULL, `stream_id` TEXT, `parent_id` TEXT, `is_dir` INTEGER NOT NULL, `title` TEXT, `album` TEXT, `artist` TEXT, `track` INTEGER, `year` INTEGER, `genre` TEXT, `cover_art_id` TEXT, `size` INTEGER, `content_type` TEXT, `suffix` TEXT, `transcoding_content_type` TEXT, `transcoded_suffix` TEXT, `duration` INTEGER, `bitrate` INTEGER, `path` TEXT, `is_video` INTEGER NOT NULL, `user_rating` INTEGER, `average_rating` REAL, `play_count` INTEGER, `disc_number` INTEGER, `created` INTEGER, `starred` INTEGER, `album_id` TEXT, `artist_id` TEXT, `type` TEXT, `bookmark_position` INTEGER, `original_width` INTEGER, `original_height` INTEGER, PRIMARY KEY(`track_order`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "trackOrder",
|
||||||
|
"columnName": "track_order",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lastPlay",
|
||||||
|
"columnName": "last_play",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "playingChanged",
|
||||||
|
"columnName": "playing_changed",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "streamId",
|
||||||
|
"columnName": "stream_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "parentId",
|
||||||
|
"columnName": "parent_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isDir",
|
||||||
|
"columnName": "is_dir",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "title",
|
||||||
|
"columnName": "title",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "album",
|
||||||
|
"columnName": "album",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "artist",
|
||||||
|
"columnName": "artist",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "track",
|
||||||
|
"columnName": "track",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "year",
|
||||||
|
"columnName": "year",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "genre",
|
||||||
|
"columnName": "genre",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "coverArtId",
|
||||||
|
"columnName": "cover_art_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "size",
|
||||||
|
"columnName": "size",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "contentType",
|
||||||
|
"columnName": "content_type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "suffix",
|
||||||
|
"columnName": "suffix",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "transcodedContentType",
|
||||||
|
"columnName": "transcoding_content_type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "transcodedSuffix",
|
||||||
|
"columnName": "transcoded_suffix",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "duration",
|
||||||
|
"columnName": "duration",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "bitrate",
|
||||||
|
"columnName": "bitrate",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "path",
|
||||||
|
"columnName": "path",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isVideo",
|
||||||
|
"columnName": "is_video",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "userRating",
|
||||||
|
"columnName": "user_rating",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "averageRating",
|
||||||
|
"columnName": "average_rating",
|
||||||
|
"affinity": "REAL",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "playCount",
|
||||||
|
"columnName": "play_count",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "discNumber",
|
||||||
|
"columnName": "disc_number",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "created",
|
||||||
|
"columnName": "created",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "starred",
|
||||||
|
"columnName": "starred",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "albumId",
|
||||||
|
"columnName": "album_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "artistId",
|
||||||
|
"columnName": "artist_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "type",
|
||||||
|
"columnName": "type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "bookmarkPosition",
|
||||||
|
"columnName": "bookmark_position",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "originalWidth",
|
||||||
|
"columnName": "original_width",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "originalHeight",
|
||||||
|
"columnName": "original_height",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"track_order"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "server",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `server_name` TEXT NOT NULL, `username` TEXT NOT NULL, `password` TEXT NOT NULL, `address` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, `low_security` INTEGER NOT NULL DEFAULT false, PRIMARY KEY(`id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "serverId",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "serverName",
|
||||||
|
"columnName": "server_name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "username",
|
||||||
|
"columnName": "username",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "password",
|
||||||
|
"columnName": "password",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "address",
|
||||||
|
"columnName": "address",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "timestamp",
|
||||||
|
"columnName": "timestamp",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isLowSecurity",
|
||||||
|
"columnName": "low_security",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "recent_search",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`search` TEXT NOT NULL, PRIMARY KEY(`search`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "search",
|
||||||
|
"columnName": "search",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"search"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "download",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `playlist_id` TEXT, `playlist_name` TEXT, `download_state` INTEGER NOT NULL DEFAULT 1, `parent_id` TEXT, `is_dir` INTEGER NOT NULL, `title` TEXT, `album` TEXT, `artist` TEXT, `track` INTEGER, `year` INTEGER, `genre` TEXT, `cover_art_id` TEXT, `size` INTEGER, `content_type` TEXT, `suffix` TEXT, `transcoding_content_type` TEXT, `transcoded_suffix` TEXT, `duration` INTEGER, `bitrate` INTEGER, `path` TEXT, `is_video` INTEGER NOT NULL, `user_rating` INTEGER, `average_rating` REAL, `play_count` INTEGER, `disc_number` INTEGER, `created` INTEGER, `starred` INTEGER, `album_id` TEXT, `artist_id` TEXT, `type` TEXT, `bookmark_position` INTEGER, `original_width` INTEGER, `original_height` INTEGER, PRIMARY KEY(`id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "playlistId",
|
||||||
|
"columnName": "playlist_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "playlistName",
|
||||||
|
"columnName": "playlist_name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "downloadState",
|
||||||
|
"columnName": "download_state",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "parentId",
|
||||||
|
"columnName": "parent_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isDir",
|
||||||
|
"columnName": "is_dir",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "title",
|
||||||
|
"columnName": "title",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "album",
|
||||||
|
"columnName": "album",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "artist",
|
||||||
|
"columnName": "artist",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "track",
|
||||||
|
"columnName": "track",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "year",
|
||||||
|
"columnName": "year",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "genre",
|
||||||
|
"columnName": "genre",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "coverArtId",
|
||||||
|
"columnName": "cover_art_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "size",
|
||||||
|
"columnName": "size",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "contentType",
|
||||||
|
"columnName": "content_type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "suffix",
|
||||||
|
"columnName": "suffix",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "transcodedContentType",
|
||||||
|
"columnName": "transcoding_content_type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "transcodedSuffix",
|
||||||
|
"columnName": "transcoded_suffix",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "duration",
|
||||||
|
"columnName": "duration",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "bitrate",
|
||||||
|
"columnName": "bitrate",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "path",
|
||||||
|
"columnName": "path",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isVideo",
|
||||||
|
"columnName": "is_video",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "userRating",
|
||||||
|
"columnName": "user_rating",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "averageRating",
|
||||||
|
"columnName": "average_rating",
|
||||||
|
"affinity": "REAL",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "playCount",
|
||||||
|
"columnName": "play_count",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "discNumber",
|
||||||
|
"columnName": "disc_number",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "created",
|
||||||
|
"columnName": "created",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "starred",
|
||||||
|
"columnName": "starred",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "albumId",
|
||||||
|
"columnName": "album_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "artistId",
|
||||||
|
"columnName": "artist_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "type",
|
||||||
|
"columnName": "type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "bookmarkPosition",
|
||||||
|
"columnName": "bookmark_position",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "originalWidth",
|
||||||
|
"columnName": "original_width",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "originalHeight",
|
||||||
|
"columnName": "original_height",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "chronology",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, `server` TEXT, `parent_id` TEXT, `is_dir` INTEGER NOT NULL, `title` TEXT, `album` TEXT, `artist` TEXT, `track` INTEGER, `year` INTEGER, `genre` TEXT, `cover_art_id` TEXT, `size` INTEGER, `content_type` TEXT, `suffix` TEXT, `transcoding_content_type` TEXT, `transcoded_suffix` TEXT, `duration` INTEGER, `bitrate` INTEGER, `path` TEXT, `is_video` INTEGER NOT NULL, `user_rating` INTEGER, `average_rating` REAL, `play_count` INTEGER, `disc_number` INTEGER, `created` INTEGER, `starred` INTEGER, `album_id` TEXT, `artist_id` TEXT, `type` TEXT, `bookmark_position` INTEGER, `original_width` INTEGER, `original_height` INTEGER, PRIMARY KEY(`id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "timestamp",
|
||||||
|
"columnName": "timestamp",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "server",
|
||||||
|
"columnName": "server",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "parentId",
|
||||||
|
"columnName": "parent_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isDir",
|
||||||
|
"columnName": "is_dir",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "title",
|
||||||
|
"columnName": "title",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "album",
|
||||||
|
"columnName": "album",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "artist",
|
||||||
|
"columnName": "artist",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "track",
|
||||||
|
"columnName": "track",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "year",
|
||||||
|
"columnName": "year",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "genre",
|
||||||
|
"columnName": "genre",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "coverArtId",
|
||||||
|
"columnName": "cover_art_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "size",
|
||||||
|
"columnName": "size",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "contentType",
|
||||||
|
"columnName": "content_type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "suffix",
|
||||||
|
"columnName": "suffix",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "transcodedContentType",
|
||||||
|
"columnName": "transcoding_content_type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "transcodedSuffix",
|
||||||
|
"columnName": "transcoded_suffix",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "duration",
|
||||||
|
"columnName": "duration",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "bitrate",
|
||||||
|
"columnName": "bitrate",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "path",
|
||||||
|
"columnName": "path",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isVideo",
|
||||||
|
"columnName": "is_video",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "userRating",
|
||||||
|
"columnName": "user_rating",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "averageRating",
|
||||||
|
"columnName": "average_rating",
|
||||||
|
"affinity": "REAL",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "playCount",
|
||||||
|
"columnName": "play_count",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "discNumber",
|
||||||
|
"columnName": "disc_number",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "created",
|
||||||
|
"columnName": "created",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "starred",
|
||||||
|
"columnName": "starred",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "albumId",
|
||||||
|
"columnName": "album_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "artistId",
|
||||||
|
"columnName": "artist_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "type",
|
||||||
|
"columnName": "type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "bookmarkPosition",
|
||||||
|
"columnName": "bookmark_position",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "originalWidth",
|
||||||
|
"columnName": "original_width",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "originalHeight",
|
||||||
|
"columnName": "original_height",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"views": [],
|
||||||
|
"setupQueries": [
|
||||||
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||||
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '1f4e50f90f58fb9cb53c89747d142fd9')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,790 @@
|
|||||||
|
{
|
||||||
|
"formatVersion": 1,
|
||||||
|
"database": {
|
||||||
|
"version": 2,
|
||||||
|
"identityHash": "ff99e331b4c34a82c560588c4dd5735f",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"tableName": "queue",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `track_order` INTEGER NOT NULL, `last_play` INTEGER NOT NULL, `playing_changed` INTEGER NOT NULL, `stream_id` TEXT, `parent_id` TEXT, `is_dir` INTEGER NOT NULL, `title` TEXT, `album` TEXT, `artist` TEXT, `track` INTEGER, `year` INTEGER, `genre` TEXT, `cover_art_id` TEXT, `size` INTEGER, `content_type` TEXT, `suffix` TEXT, `transcoding_content_type` TEXT, `transcoded_suffix` TEXT, `duration` INTEGER, `bitrate` INTEGER, `path` TEXT, `is_video` INTEGER NOT NULL, `user_rating` INTEGER, `average_rating` REAL, `play_count` INTEGER, `disc_number` INTEGER, `created` INTEGER, `starred` INTEGER, `album_id` TEXT, `artist_id` TEXT, `type` TEXT, `bookmark_position` INTEGER, `original_width` INTEGER, `original_height` INTEGER, PRIMARY KEY(`track_order`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "trackOrder",
|
||||||
|
"columnName": "track_order",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lastPlay",
|
||||||
|
"columnName": "last_play",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "playingChanged",
|
||||||
|
"columnName": "playing_changed",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "streamId",
|
||||||
|
"columnName": "stream_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "parentId",
|
||||||
|
"columnName": "parent_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isDir",
|
||||||
|
"columnName": "is_dir",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "title",
|
||||||
|
"columnName": "title",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "album",
|
||||||
|
"columnName": "album",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "artist",
|
||||||
|
"columnName": "artist",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "track",
|
||||||
|
"columnName": "track",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "year",
|
||||||
|
"columnName": "year",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "genre",
|
||||||
|
"columnName": "genre",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "coverArtId",
|
||||||
|
"columnName": "cover_art_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "size",
|
||||||
|
"columnName": "size",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "contentType",
|
||||||
|
"columnName": "content_type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "suffix",
|
||||||
|
"columnName": "suffix",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "transcodedContentType",
|
||||||
|
"columnName": "transcoding_content_type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "transcodedSuffix",
|
||||||
|
"columnName": "transcoded_suffix",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "duration",
|
||||||
|
"columnName": "duration",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "bitrate",
|
||||||
|
"columnName": "bitrate",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "path",
|
||||||
|
"columnName": "path",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isVideo",
|
||||||
|
"columnName": "is_video",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "userRating",
|
||||||
|
"columnName": "user_rating",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "averageRating",
|
||||||
|
"columnName": "average_rating",
|
||||||
|
"affinity": "REAL",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "playCount",
|
||||||
|
"columnName": "play_count",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "discNumber",
|
||||||
|
"columnName": "disc_number",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "created",
|
||||||
|
"columnName": "created",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "starred",
|
||||||
|
"columnName": "starred",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "albumId",
|
||||||
|
"columnName": "album_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "artistId",
|
||||||
|
"columnName": "artist_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "type",
|
||||||
|
"columnName": "type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "bookmarkPosition",
|
||||||
|
"columnName": "bookmark_position",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "originalWidth",
|
||||||
|
"columnName": "original_width",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "originalHeight",
|
||||||
|
"columnName": "original_height",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"track_order"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "server",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `server_name` TEXT NOT NULL, `username` TEXT NOT NULL, `password` TEXT NOT NULL, `address` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, `low_security` INTEGER NOT NULL DEFAULT false, PRIMARY KEY(`id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "serverId",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "serverName",
|
||||||
|
"columnName": "server_name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "username",
|
||||||
|
"columnName": "username",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "password",
|
||||||
|
"columnName": "password",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "address",
|
||||||
|
"columnName": "address",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "timestamp",
|
||||||
|
"columnName": "timestamp",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isLowSecurity",
|
||||||
|
"columnName": "low_security",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "recent_search",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`search` TEXT NOT NULL, PRIMARY KEY(`search`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "search",
|
||||||
|
"columnName": "search",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"search"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "download",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `playlist_id` TEXT, `playlist_name` TEXT, `download_state` INTEGER NOT NULL DEFAULT 1, `parent_id` TEXT, `is_dir` INTEGER NOT NULL, `title` TEXT, `album` TEXT, `artist` TEXT, `track` INTEGER, `year` INTEGER, `genre` TEXT, `cover_art_id` TEXT, `size` INTEGER, `content_type` TEXT, `suffix` TEXT, `transcoding_content_type` TEXT, `transcoded_suffix` TEXT, `duration` INTEGER, `bitrate` INTEGER, `path` TEXT, `is_video` INTEGER NOT NULL, `user_rating` INTEGER, `average_rating` REAL, `play_count` INTEGER, `disc_number` INTEGER, `created` INTEGER, `starred` INTEGER, `album_id` TEXT, `artist_id` TEXT, `type` TEXT, `bookmark_position` INTEGER, `original_width` INTEGER, `original_height` INTEGER, PRIMARY KEY(`id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "playlistId",
|
||||||
|
"columnName": "playlist_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "playlistName",
|
||||||
|
"columnName": "playlist_name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "downloadState",
|
||||||
|
"columnName": "download_state",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "parentId",
|
||||||
|
"columnName": "parent_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isDir",
|
||||||
|
"columnName": "is_dir",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "title",
|
||||||
|
"columnName": "title",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "album",
|
||||||
|
"columnName": "album",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "artist",
|
||||||
|
"columnName": "artist",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "track",
|
||||||
|
"columnName": "track",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "year",
|
||||||
|
"columnName": "year",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "genre",
|
||||||
|
"columnName": "genre",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "coverArtId",
|
||||||
|
"columnName": "cover_art_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "size",
|
||||||
|
"columnName": "size",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "contentType",
|
||||||
|
"columnName": "content_type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "suffix",
|
||||||
|
"columnName": "suffix",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "transcodedContentType",
|
||||||
|
"columnName": "transcoding_content_type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "transcodedSuffix",
|
||||||
|
"columnName": "transcoded_suffix",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "duration",
|
||||||
|
"columnName": "duration",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "bitrate",
|
||||||
|
"columnName": "bitrate",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "path",
|
||||||
|
"columnName": "path",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isVideo",
|
||||||
|
"columnName": "is_video",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "userRating",
|
||||||
|
"columnName": "user_rating",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "averageRating",
|
||||||
|
"columnName": "average_rating",
|
||||||
|
"affinity": "REAL",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "playCount",
|
||||||
|
"columnName": "play_count",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "discNumber",
|
||||||
|
"columnName": "disc_number",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "created",
|
||||||
|
"columnName": "created",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "starred",
|
||||||
|
"columnName": "starred",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "albumId",
|
||||||
|
"columnName": "album_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "artistId",
|
||||||
|
"columnName": "artist_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "type",
|
||||||
|
"columnName": "type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "bookmarkPosition",
|
||||||
|
"columnName": "bookmark_position",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "originalWidth",
|
||||||
|
"columnName": "original_width",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "originalHeight",
|
||||||
|
"columnName": "original_height",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "chronology",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, `server` TEXT, `parent_id` TEXT, `is_dir` INTEGER NOT NULL, `title` TEXT, `album` TEXT, `artist` TEXT, `track` INTEGER, `year` INTEGER, `genre` TEXT, `cover_art_id` TEXT, `size` INTEGER, `content_type` TEXT, `suffix` TEXT, `transcoding_content_type` TEXT, `transcoded_suffix` TEXT, `duration` INTEGER, `bitrate` INTEGER, `path` TEXT, `is_video` INTEGER NOT NULL, `user_rating` INTEGER, `average_rating` REAL, `play_count` INTEGER, `disc_number` INTEGER, `created` INTEGER, `starred` INTEGER, `album_id` TEXT, `artist_id` TEXT, `type` TEXT, `bookmark_position` INTEGER, `original_width` INTEGER, `original_height` INTEGER, PRIMARY KEY(`id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "timestamp",
|
||||||
|
"columnName": "timestamp",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "server",
|
||||||
|
"columnName": "server",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "parentId",
|
||||||
|
"columnName": "parent_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isDir",
|
||||||
|
"columnName": "is_dir",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "title",
|
||||||
|
"columnName": "title",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "album",
|
||||||
|
"columnName": "album",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "artist",
|
||||||
|
"columnName": "artist",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "track",
|
||||||
|
"columnName": "track",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "year",
|
||||||
|
"columnName": "year",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "genre",
|
||||||
|
"columnName": "genre",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "coverArtId",
|
||||||
|
"columnName": "cover_art_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "size",
|
||||||
|
"columnName": "size",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "contentType",
|
||||||
|
"columnName": "content_type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "suffix",
|
||||||
|
"columnName": "suffix",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "transcodedContentType",
|
||||||
|
"columnName": "transcoding_content_type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "transcodedSuffix",
|
||||||
|
"columnName": "transcoded_suffix",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "duration",
|
||||||
|
"columnName": "duration",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "bitrate",
|
||||||
|
"columnName": "bitrate",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "path",
|
||||||
|
"columnName": "path",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isVideo",
|
||||||
|
"columnName": "is_video",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "userRating",
|
||||||
|
"columnName": "user_rating",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "averageRating",
|
||||||
|
"columnName": "average_rating",
|
||||||
|
"affinity": "REAL",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "playCount",
|
||||||
|
"columnName": "play_count",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "discNumber",
|
||||||
|
"columnName": "disc_number",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "created",
|
||||||
|
"columnName": "created",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "starred",
|
||||||
|
"columnName": "starred",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "albumId",
|
||||||
|
"columnName": "album_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "artistId",
|
||||||
|
"columnName": "artist_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "type",
|
||||||
|
"columnName": "type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "bookmarkPosition",
|
||||||
|
"columnName": "bookmark_position",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "originalWidth",
|
||||||
|
"columnName": "original_width",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "originalHeight",
|
||||||
|
"columnName": "original_height",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "favorite",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`timestamp` INTEGER NOT NULL, `songId` TEXT, `albumId` TEXT, `artistId` TEXT, `toStar` INTEGER NOT NULL, PRIMARY KEY(`timestamp`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "timestamp",
|
||||||
|
"columnName": "timestamp",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "songId",
|
||||||
|
"columnName": "songId",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "albumId",
|
||||||
|
"columnName": "albumId",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "artistId",
|
||||||
|
"columnName": "artistId",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "toStar",
|
||||||
|
"columnName": "toStar",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"timestamp"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"views": [],
|
||||||
|
"setupQueries": [
|
||||||
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||||
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'ff99e331b4c34a82c560588c4dd5735f')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,797 @@
|
|||||||
|
{
|
||||||
|
"formatVersion": 1,
|
||||||
|
"database": {
|
||||||
|
"version": 3,
|
||||||
|
"identityHash": "6ea111217793c58d54eabb1190dd92ec",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"tableName": "queue",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `track_order` INTEGER NOT NULL, `last_play` INTEGER NOT NULL, `playing_changed` INTEGER NOT NULL, `stream_id` TEXT, `parent_id` TEXT, `is_dir` INTEGER NOT NULL, `title` TEXT, `album` TEXT, `artist` TEXT, `track` INTEGER, `year` INTEGER, `genre` TEXT, `cover_art_id` TEXT, `size` INTEGER, `content_type` TEXT, `suffix` TEXT, `transcoding_content_type` TEXT, `transcoded_suffix` TEXT, `duration` INTEGER, `bitrate` INTEGER, `path` TEXT, `is_video` INTEGER NOT NULL, `user_rating` INTEGER, `average_rating` REAL, `play_count` INTEGER, `disc_number` INTEGER, `created` INTEGER, `starred` INTEGER, `album_id` TEXT, `artist_id` TEXT, `type` TEXT, `bookmark_position` INTEGER, `original_width` INTEGER, `original_height` INTEGER, PRIMARY KEY(`track_order`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "trackOrder",
|
||||||
|
"columnName": "track_order",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lastPlay",
|
||||||
|
"columnName": "last_play",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "playingChanged",
|
||||||
|
"columnName": "playing_changed",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "streamId",
|
||||||
|
"columnName": "stream_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "parentId",
|
||||||
|
"columnName": "parent_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isDir",
|
||||||
|
"columnName": "is_dir",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "title",
|
||||||
|
"columnName": "title",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "album",
|
||||||
|
"columnName": "album",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "artist",
|
||||||
|
"columnName": "artist",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "track",
|
||||||
|
"columnName": "track",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "year",
|
||||||
|
"columnName": "year",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "genre",
|
||||||
|
"columnName": "genre",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "coverArtId",
|
||||||
|
"columnName": "cover_art_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "size",
|
||||||
|
"columnName": "size",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "contentType",
|
||||||
|
"columnName": "content_type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "suffix",
|
||||||
|
"columnName": "suffix",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "transcodedContentType",
|
||||||
|
"columnName": "transcoding_content_type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "transcodedSuffix",
|
||||||
|
"columnName": "transcoded_suffix",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "duration",
|
||||||
|
"columnName": "duration",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "bitrate",
|
||||||
|
"columnName": "bitrate",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "path",
|
||||||
|
"columnName": "path",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isVideo",
|
||||||
|
"columnName": "is_video",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "userRating",
|
||||||
|
"columnName": "user_rating",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "averageRating",
|
||||||
|
"columnName": "average_rating",
|
||||||
|
"affinity": "REAL",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "playCount",
|
||||||
|
"columnName": "play_count",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "discNumber",
|
||||||
|
"columnName": "disc_number",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "created",
|
||||||
|
"columnName": "created",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "starred",
|
||||||
|
"columnName": "starred",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "albumId",
|
||||||
|
"columnName": "album_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "artistId",
|
||||||
|
"columnName": "artist_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "type",
|
||||||
|
"columnName": "type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "bookmarkPosition",
|
||||||
|
"columnName": "bookmark_position",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "originalWidth",
|
||||||
|
"columnName": "original_width",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "originalHeight",
|
||||||
|
"columnName": "original_height",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"track_order"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "server",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `server_name` TEXT NOT NULL, `username` TEXT NOT NULL, `password` TEXT NOT NULL, `address` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, `low_security` INTEGER NOT NULL DEFAULT false, PRIMARY KEY(`id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "serverId",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "serverName",
|
||||||
|
"columnName": "server_name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "username",
|
||||||
|
"columnName": "username",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "password",
|
||||||
|
"columnName": "password",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "address",
|
||||||
|
"columnName": "address",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "timestamp",
|
||||||
|
"columnName": "timestamp",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isLowSecurity",
|
||||||
|
"columnName": "low_security",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "recent_search",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`search` TEXT NOT NULL, PRIMARY KEY(`search`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "search",
|
||||||
|
"columnName": "search",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"search"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "download",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `playlist_id` TEXT, `playlist_name` TEXT, `download_state` INTEGER NOT NULL DEFAULT 1, `download_uri` TEXT DEFAULT '', `parent_id` TEXT, `is_dir` INTEGER NOT NULL, `title` TEXT, `album` TEXT, `artist` TEXT, `track` INTEGER, `year` INTEGER, `genre` TEXT, `cover_art_id` TEXT, `size` INTEGER, `content_type` TEXT, `suffix` TEXT, `transcoding_content_type` TEXT, `transcoded_suffix` TEXT, `duration` INTEGER, `bitrate` INTEGER, `path` TEXT, `is_video` INTEGER NOT NULL, `user_rating` INTEGER, `average_rating` REAL, `play_count` INTEGER, `disc_number` INTEGER, `created` INTEGER, `starred` INTEGER, `album_id` TEXT, `artist_id` TEXT, `type` TEXT, `bookmark_position` INTEGER, `original_width` INTEGER, `original_height` INTEGER, PRIMARY KEY(`id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "playlistId",
|
||||||
|
"columnName": "playlist_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "playlistName",
|
||||||
|
"columnName": "playlist_name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "downloadState",
|
||||||
|
"columnName": "download_state",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "downloadUri",
|
||||||
|
"columnName": "download_uri",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "parentId",
|
||||||
|
"columnName": "parent_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isDir",
|
||||||
|
"columnName": "is_dir",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "title",
|
||||||
|
"columnName": "title",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "album",
|
||||||
|
"columnName": "album",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "artist",
|
||||||
|
"columnName": "artist",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "track",
|
||||||
|
"columnName": "track",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "year",
|
||||||
|
"columnName": "year",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "genre",
|
||||||
|
"columnName": "genre",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "coverArtId",
|
||||||
|
"columnName": "cover_art_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "size",
|
||||||
|
"columnName": "size",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "contentType",
|
||||||
|
"columnName": "content_type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "suffix",
|
||||||
|
"columnName": "suffix",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "transcodedContentType",
|
||||||
|
"columnName": "transcoding_content_type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "transcodedSuffix",
|
||||||
|
"columnName": "transcoded_suffix",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "duration",
|
||||||
|
"columnName": "duration",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "bitrate",
|
||||||
|
"columnName": "bitrate",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "path",
|
||||||
|
"columnName": "path",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isVideo",
|
||||||
|
"columnName": "is_video",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "userRating",
|
||||||
|
"columnName": "user_rating",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "averageRating",
|
||||||
|
"columnName": "average_rating",
|
||||||
|
"affinity": "REAL",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "playCount",
|
||||||
|
"columnName": "play_count",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "discNumber",
|
||||||
|
"columnName": "disc_number",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "created",
|
||||||
|
"columnName": "created",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "starred",
|
||||||
|
"columnName": "starred",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "albumId",
|
||||||
|
"columnName": "album_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "artistId",
|
||||||
|
"columnName": "artist_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "type",
|
||||||
|
"columnName": "type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "bookmarkPosition",
|
||||||
|
"columnName": "bookmark_position",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "originalWidth",
|
||||||
|
"columnName": "original_width",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "originalHeight",
|
||||||
|
"columnName": "original_height",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "chronology",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, `server` TEXT, `parent_id` TEXT, `is_dir` INTEGER NOT NULL, `title` TEXT, `album` TEXT, `artist` TEXT, `track` INTEGER, `year` INTEGER, `genre` TEXT, `cover_art_id` TEXT, `size` INTEGER, `content_type` TEXT, `suffix` TEXT, `transcoding_content_type` TEXT, `transcoded_suffix` TEXT, `duration` INTEGER, `bitrate` INTEGER, `path` TEXT, `is_video` INTEGER NOT NULL, `user_rating` INTEGER, `average_rating` REAL, `play_count` INTEGER, `disc_number` INTEGER, `created` INTEGER, `starred` INTEGER, `album_id` TEXT, `artist_id` TEXT, `type` TEXT, `bookmark_position` INTEGER, `original_width` INTEGER, `original_height` INTEGER, PRIMARY KEY(`id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "timestamp",
|
||||||
|
"columnName": "timestamp",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "server",
|
||||||
|
"columnName": "server",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "parentId",
|
||||||
|
"columnName": "parent_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isDir",
|
||||||
|
"columnName": "is_dir",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "title",
|
||||||
|
"columnName": "title",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "album",
|
||||||
|
"columnName": "album",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "artist",
|
||||||
|
"columnName": "artist",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "track",
|
||||||
|
"columnName": "track",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "year",
|
||||||
|
"columnName": "year",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "genre",
|
||||||
|
"columnName": "genre",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "coverArtId",
|
||||||
|
"columnName": "cover_art_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "size",
|
||||||
|
"columnName": "size",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "contentType",
|
||||||
|
"columnName": "content_type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "suffix",
|
||||||
|
"columnName": "suffix",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "transcodedContentType",
|
||||||
|
"columnName": "transcoding_content_type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "transcodedSuffix",
|
||||||
|
"columnName": "transcoded_suffix",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "duration",
|
||||||
|
"columnName": "duration",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "bitrate",
|
||||||
|
"columnName": "bitrate",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "path",
|
||||||
|
"columnName": "path",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isVideo",
|
||||||
|
"columnName": "is_video",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "userRating",
|
||||||
|
"columnName": "user_rating",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "averageRating",
|
||||||
|
"columnName": "average_rating",
|
||||||
|
"affinity": "REAL",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "playCount",
|
||||||
|
"columnName": "play_count",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "discNumber",
|
||||||
|
"columnName": "disc_number",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "created",
|
||||||
|
"columnName": "created",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "starred",
|
||||||
|
"columnName": "starred",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "albumId",
|
||||||
|
"columnName": "album_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "artistId",
|
||||||
|
"columnName": "artist_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "type",
|
||||||
|
"columnName": "type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "bookmarkPosition",
|
||||||
|
"columnName": "bookmark_position",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "originalWidth",
|
||||||
|
"columnName": "original_width",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "originalHeight",
|
||||||
|
"columnName": "original_height",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "favorite",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`timestamp` INTEGER NOT NULL, `songId` TEXT, `albumId` TEXT, `artistId` TEXT, `toStar` INTEGER NOT NULL, PRIMARY KEY(`timestamp`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "timestamp",
|
||||||
|
"columnName": "timestamp",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "songId",
|
||||||
|
"columnName": "songId",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "albumId",
|
||||||
|
"columnName": "albumId",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "artistId",
|
||||||
|
"columnName": "artistId",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "toStar",
|
||||||
|
"columnName": "toStar",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"timestamp"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"views": [],
|
||||||
|
"setupQueries": [
|
||||||
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||||
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '6ea111217793c58d54eabb1190dd92ec')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,50 +1,71 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?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">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name="App"
|
android:name="App"
|
||||||
|
android:allowBackup="false"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
|
android:localeConfig="@xml/locale_config"
|
||||||
android:roundIcon="@mipmap/ic_launcher"
|
android:roundIcon="@mipmap/ic_launcher"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/AppTheme.SplashScreen"
|
android:theme="@style/AppTheme.SplashScreen"
|
||||||
android:usesCleartextTraffic="true"
|
android:usesCleartextTraffic="true">
|
||||||
android:allowBackup="false">
|
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
|
android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
|
||||||
android:value="androidx.media3.cast.DefaultCastOptionsProvider"/>
|
android:value="androidx.media3.cast.DefaultCastOptionsProvider" />
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="androidx.car.app.TintableAttributionIcon"
|
||||||
|
android:resource="@drawable/ic_graphic_eq" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.activity.MainActivity"
|
android:name=".ui.activity.MainActivity"
|
||||||
android:windowSoftInputMode="adjustPan|adjustResize"
|
android:exported="true"
|
||||||
android:exported="true">
|
android:windowSoftInputMode="adjustPan|adjustResize">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name=".service.MediaService"
|
android:name=".service.MediaService"
|
||||||
android:foregroundServiceType="mediaPlayback"
|
android:exported="true"
|
||||||
android:exported="true">
|
android:foregroundServiceType="mediaPlayback">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="androidx.media3.session.MediaLibraryService" />
|
<action android:name="androidx.media3.session.MediaLibraryService" />
|
||||||
<action android:name="androidx.media3.session.MediaBrowserService" />
|
<action android:name="androidx.media3.session.MediaBrowserService" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</service>
|
</service>
|
||||||
<service android:name=".service.DownloaderService"
|
|
||||||
android:exported="true">
|
<service
|
||||||
|
android:name=".service.DownloaderService"
|
||||||
|
android:exported="true"
|
||||||
|
android:foregroundServiceType="dataSync">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="androidx.media3.exoplayer.downloadService.action.RESTART"/>
|
<action android:name="androidx.media3.exoplayer.downloadService.action.RESTART" />
|
||||||
<category android:name="android.intent.category.DEFAULT"/>
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
|
||||||
|
android:enabled="false"
|
||||||
|
android:exported="false">
|
||||||
|
<meta-data
|
||||||
|
android:name="autoStoreLocales"
|
||||||
|
android:value="true" />
|
||||||
|
</service>
|
||||||
</application>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
||||||
@@ -10,7 +10,6 @@ import com.cappielloantonio.tempo.helper.ThemeHelper;
|
|||||||
import com.cappielloantonio.tempo.subsonic.Subsonic;
|
import com.cappielloantonio.tempo.subsonic.Subsonic;
|
||||||
import com.cappielloantonio.tempo.subsonic.SubsonicPreferences;
|
import com.cappielloantonio.tempo.subsonic.SubsonicPreferences;
|
||||||
import com.cappielloantonio.tempo.util.Preferences;
|
import com.cappielloantonio.tempo.util.Preferences;
|
||||||
import com.google.android.material.color.DynamicColors;
|
|
||||||
|
|
||||||
public class App extends Application {
|
public class App extends Application {
|
||||||
private static App instance;
|
private static App instance;
|
||||||
@@ -22,7 +21,6 @@ public class App extends Application {
|
|||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
super.onCreate();
|
super.onCreate();
|
||||||
|
|
||||||
DynamicColors.applyToActivitiesIfAvailable(this);
|
|
||||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
|
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
|
||||||
String themePref = sharedPreferences.getString(Preferences.THEME, ThemeHelper.DEFAULT_MODE);
|
String themePref = sharedPreferences.getString(Preferences.THEME, ThemeHelper.DEFAULT_MODE);
|
||||||
ThemeHelper.applyTheme(themePref);
|
ThemeHelper.applyTheme(themePref);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.cappielloantonio.tempo.database;
|
package com.cappielloantonio.tempo.database;
|
||||||
|
|
||||||
|
import androidx.room.AutoMigration;
|
||||||
import androidx.room.Database;
|
import androidx.room.Database;
|
||||||
import androidx.room.Room;
|
import androidx.room.Room;
|
||||||
import androidx.room.RoomDatabase;
|
import androidx.room.RoomDatabase;
|
||||||
@@ -9,19 +10,21 @@ import com.cappielloantonio.tempo.App;
|
|||||||
import com.cappielloantonio.tempo.database.converter.DateConverters;
|
import com.cappielloantonio.tempo.database.converter.DateConverters;
|
||||||
import com.cappielloantonio.tempo.database.dao.ChronologyDao;
|
import com.cappielloantonio.tempo.database.dao.ChronologyDao;
|
||||||
import com.cappielloantonio.tempo.database.dao.DownloadDao;
|
import com.cappielloantonio.tempo.database.dao.DownloadDao;
|
||||||
|
import com.cappielloantonio.tempo.database.dao.FavoriteDao;
|
||||||
import com.cappielloantonio.tempo.database.dao.QueueDao;
|
import com.cappielloantonio.tempo.database.dao.QueueDao;
|
||||||
import com.cappielloantonio.tempo.database.dao.RecentSearchDao;
|
import com.cappielloantonio.tempo.database.dao.RecentSearchDao;
|
||||||
import com.cappielloantonio.tempo.database.dao.ServerDao;
|
import com.cappielloantonio.tempo.database.dao.ServerDao;
|
||||||
import com.cappielloantonio.tempo.model.Chronology;
|
import com.cappielloantonio.tempo.model.Chronology;
|
||||||
import com.cappielloantonio.tempo.model.Download;
|
import com.cappielloantonio.tempo.model.Download;
|
||||||
|
import com.cappielloantonio.tempo.model.Favorite;
|
||||||
import com.cappielloantonio.tempo.model.Queue;
|
import com.cappielloantonio.tempo.model.Queue;
|
||||||
import com.cappielloantonio.tempo.model.RecentSearch;
|
import com.cappielloantonio.tempo.model.RecentSearch;
|
||||||
import com.cappielloantonio.tempo.model.Server;
|
import com.cappielloantonio.tempo.model.Server;
|
||||||
|
|
||||||
@Database(
|
@Database(
|
||||||
version = 1,
|
version = 3,
|
||||||
entities = {Queue.class, Server.class, RecentSearch.class, Download.class, Chronology.class}
|
entities = {Queue.class, Server.class, RecentSearch.class, Download.class, Chronology.class, Favorite.class},
|
||||||
// autoMigrations = {@AutoMigration(from = 61, to = 62)}
|
autoMigrations = {@AutoMigration(from = 2, to = 3)}
|
||||||
)
|
)
|
||||||
@TypeConverters({DateConverters.class})
|
@TypeConverters({DateConverters.class})
|
||||||
public abstract class AppDatabase extends RoomDatabase {
|
public abstract class AppDatabase extends RoomDatabase {
|
||||||
@@ -47,4 +50,6 @@ public abstract class AppDatabase extends RoomDatabase {
|
|||||||
public abstract DownloadDao downloadDao();
|
public abstract DownloadDao downloadDao();
|
||||||
|
|
||||||
public abstract ChronologyDao chronologyDao();
|
public abstract ChronologyDao chronologyDao();
|
||||||
|
|
||||||
|
public abstract FavoriteDao favoriteDao();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,9 @@ public interface DownloadDao {
|
|||||||
@Query("SELECT * FROM download WHERE download_state = 1 ORDER BY artist, album, track ASC")
|
@Query("SELECT * FROM download WHERE download_state = 1 ORDER BY artist, album, track ASC")
|
||||||
LiveData<List<Download>> getAll();
|
LiveData<List<Download>> getAll();
|
||||||
|
|
||||||
|
@Query("SELECT * FROM download WHERE id = :id")
|
||||||
|
Download getOne(String id);
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
void insert(Download download);
|
void insert(Download download);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package com.cappielloantonio.tempo.database.dao;
|
||||||
|
|
||||||
|
import androidx.room.Dao;
|
||||||
|
import androidx.room.Delete;
|
||||||
|
import androidx.room.Insert;
|
||||||
|
import androidx.room.OnConflictStrategy;
|
||||||
|
import androidx.room.Query;
|
||||||
|
|
||||||
|
import com.cappielloantonio.tempo.model.Favorite;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
public interface FavoriteDao {
|
||||||
|
@Query("SELECT * FROM favorite")
|
||||||
|
List<Favorite> getAll();
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||||
|
void insert(Favorite favorite);
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
void delete(Favorite favorite);
|
||||||
|
|
||||||
|
@Query("DELETE FROM favorite")
|
||||||
|
void deleteAll();
|
||||||
|
}
|
||||||
@@ -12,8 +12,8 @@ import java.util.List;
|
|||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
public interface RecentSearchDao {
|
public interface RecentSearchDao {
|
||||||
@Query("SELECT * FROM recent_search GROUP BY search ORDER BY search DESC LIMIT :limit")
|
@Query("SELECT * FROM recent_search ORDER BY search DESC")
|
||||||
List<String> getRecent(int limit);
|
List<String> getRecent();
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
void insert(RecentSearch search);
|
void insert(RecentSearch search);
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import com.bumptech.glide.request.RequestOptions;
|
|||||||
import com.bumptech.glide.signature.ObjectKey;
|
import com.bumptech.glide.signature.ObjectKey;
|
||||||
import com.cappielloantonio.tempo.App;
|
import com.cappielloantonio.tempo.App;
|
||||||
import com.cappielloantonio.tempo.util.Preferences;
|
import com.cappielloantonio.tempo.util.Preferences;
|
||||||
|
import com.cappielloantonio.tempo.util.Util;
|
||||||
import com.google.android.material.elevation.SurfaceColors;
|
import com.google.android.material.elevation.SurfaceColors;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -46,7 +47,7 @@ public class CustomGlideRequest {
|
|||||||
uri.append("getCoverArt");
|
uri.append("getCoverArt");
|
||||||
|
|
||||||
if (params.containsKey("u") && params.get("u") != null)
|
if (params.containsKey("u") && params.get("u") != null)
|
||||||
uri.append("?u=").append(params.get("u"));
|
uri.append("?u=").append(Util.encode(params.get("u")));
|
||||||
if (params.containsKey("p") && params.get("p") != null)
|
if (params.containsKey("p") && params.get("p") != null)
|
||||||
uri.append("&p=").append(params.get("p"));
|
uri.append("&p=").append(params.get("p"));
|
||||||
if (params.containsKey("s") && params.get("s") != null)
|
if (params.containsKey("s") && params.get("s") != null)
|
||||||
|
|||||||
@@ -0,0 +1,197 @@
|
|||||||
|
package com.cappielloantonio.tempo.helper.recyclerview;
|
||||||
|
|
||||||
|
import android.animation.Animator;
|
||||||
|
import android.animation.AnimatorListenerAdapter;
|
||||||
|
import android.animation.ObjectAnimator;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.IdRes;
|
||||||
|
import androidx.annotation.LayoutRes;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.core.view.ViewCompat;
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
public class FastScrollbar extends LinearLayout {
|
||||||
|
private static final int BUBBLE_ANIMATION_DURATION = 100;
|
||||||
|
private static final int TRACK_SNAP_RANGE = 5;
|
||||||
|
|
||||||
|
private TextView bubble;
|
||||||
|
private View handle;
|
||||||
|
private RecyclerView recyclerView;
|
||||||
|
private int height;
|
||||||
|
private boolean isInitialized = false;
|
||||||
|
private ObjectAnimator currentAnimator = null;
|
||||||
|
|
||||||
|
private final RecyclerView.OnScrollListener onScrollListener = new RecyclerView.OnScrollListener() {
|
||||||
|
@Override
|
||||||
|
public void onScrolled(@NonNull final RecyclerView recyclerView, final int dx, final int dy) {
|
||||||
|
updateBubbleAndHandlePosition();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public interface BubbleTextGetter {
|
||||||
|
String getTextToShowInBubble(int pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FastScrollbar(final Context context, final AttributeSet attrs, final int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
init(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FastScrollbar(final Context context) {
|
||||||
|
super(context);
|
||||||
|
init(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FastScrollbar(final Context context, final AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
init(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void init(Context context) {
|
||||||
|
if (isInitialized) return;
|
||||||
|
isInitialized = true;
|
||||||
|
setOrientation(HORIZONTAL);
|
||||||
|
setClipChildren(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setViewsToUse(@LayoutRes int layoutResId, @IdRes int bubbleResId, @IdRes int handleResId) {
|
||||||
|
final LayoutInflater inflater = LayoutInflater.from(getContext());
|
||||||
|
inflater.inflate(layoutResId, this, true);
|
||||||
|
bubble = findViewById(bubbleResId);
|
||||||
|
if (bubble != null) bubble.setVisibility(INVISIBLE);
|
||||||
|
handle = findViewById(handleResId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||||
|
super.onSizeChanged(w, h, oldw, oldh);
|
||||||
|
height = h;
|
||||||
|
updateBubbleAndHandlePosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onTouchEvent(@NonNull MotionEvent event) {
|
||||||
|
final int action = event.getAction();
|
||||||
|
switch (action) {
|
||||||
|
case MotionEvent.ACTION_DOWN:
|
||||||
|
if (event.getX() < handle.getX() - ViewCompat.getPaddingStart(handle)) return false;
|
||||||
|
if (currentAnimator != null) currentAnimator.cancel();
|
||||||
|
if (bubble != null && bubble.getVisibility() == INVISIBLE) showBubble();
|
||||||
|
handle.setSelected(true);
|
||||||
|
case MotionEvent.ACTION_MOVE:
|
||||||
|
final float y = event.getY();
|
||||||
|
setBubbleAndHandlePosition(y);
|
||||||
|
setRecyclerViewPosition(y);
|
||||||
|
return true;
|
||||||
|
case MotionEvent.ACTION_UP:
|
||||||
|
case MotionEvent.ACTION_CANCEL:
|
||||||
|
handle.setSelected(false);
|
||||||
|
hideBubble();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return super.onTouchEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRecyclerView(final RecyclerView recyclerView) {
|
||||||
|
if (this.recyclerView != recyclerView) {
|
||||||
|
if (this.recyclerView != null)
|
||||||
|
this.recyclerView.removeOnScrollListener(onScrollListener);
|
||||||
|
this.recyclerView = recyclerView;
|
||||||
|
if (this.recyclerView == null) return;
|
||||||
|
recyclerView.addOnScrollListener(onScrollListener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDetachedFromWindow() {
|
||||||
|
super.onDetachedFromWindow();
|
||||||
|
if (recyclerView != null) {
|
||||||
|
recyclerView.removeOnScrollListener(onScrollListener);
|
||||||
|
recyclerView = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setRecyclerViewPosition(float y) {
|
||||||
|
if (recyclerView != null) {
|
||||||
|
final int itemCount = recyclerView.getAdapter().getItemCount();
|
||||||
|
float proportion;
|
||||||
|
if (handle.getY() == 0) proportion = 0f;
|
||||||
|
else if (handle.getY() + handle.getHeight() >= height - TRACK_SNAP_RANGE)
|
||||||
|
proportion = 1f;
|
||||||
|
else proportion = y / (float) height;
|
||||||
|
final int targetPos = getValueInRange(0, itemCount - 1, (int) (proportion * (float) itemCount));
|
||||||
|
((LinearLayoutManager) recyclerView.getLayoutManager()).scrollToPositionWithOffset(targetPos, 0);
|
||||||
|
final String bubbleText = ((BubbleTextGetter) recyclerView.getAdapter()).getTextToShowInBubble(targetPos);
|
||||||
|
if (bubble != null) {
|
||||||
|
bubble.setText(bubbleText);
|
||||||
|
if (TextUtils.isEmpty(bubbleText)) {
|
||||||
|
hideBubble();
|
||||||
|
} else if (bubble.getVisibility() == View.INVISIBLE) {
|
||||||
|
showBubble();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getValueInRange(int min, int max, int value) {
|
||||||
|
int minimum = Math.max(min, value);
|
||||||
|
return Math.min(minimum, max);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateBubbleAndHandlePosition() {
|
||||||
|
if (bubble == null || handle.isSelected()) return;
|
||||||
|
|
||||||
|
final int verticalScrollOffset = recyclerView.computeVerticalScrollOffset();
|
||||||
|
final int verticalScrollRange = recyclerView.computeVerticalScrollRange();
|
||||||
|
float proportion = (float) verticalScrollOffset / ((float) verticalScrollRange - height);
|
||||||
|
setBubbleAndHandlePosition(height * proportion);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setBubbleAndHandlePosition(float y) {
|
||||||
|
final int handleHeight = handle.getHeight();
|
||||||
|
handle.setY(getValueInRange(0, height - handleHeight, (int) (y - handleHeight / 2)));
|
||||||
|
if (bubble != null) {
|
||||||
|
int bubbleHeight = bubble.getHeight();
|
||||||
|
bubble.setY(getValueInRange(0, height - bubbleHeight - handleHeight / 2, (int) (y - bubbleHeight)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showBubble() {
|
||||||
|
if (bubble == null) return;
|
||||||
|
bubble.setVisibility(VISIBLE);
|
||||||
|
if (currentAnimator != null) currentAnimator.cancel();
|
||||||
|
currentAnimator = ObjectAnimator.ofFloat(bubble, "alpha", 0f, 1f).setDuration(BUBBLE_ANIMATION_DURATION);
|
||||||
|
currentAnimator.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void hideBubble() {
|
||||||
|
if (bubble == null) return;
|
||||||
|
if (currentAnimator != null) currentAnimator.cancel();
|
||||||
|
currentAnimator = ObjectAnimator.ofFloat(bubble, "alpha", 1f, 0f).setDuration(BUBBLE_ANIMATION_DURATION);
|
||||||
|
currentAnimator.addListener(new AnimatorListenerAdapter() {
|
||||||
|
@Override
|
||||||
|
public void onAnimationEnd(Animator animation) {
|
||||||
|
super.onAnimationEnd(animation);
|
||||||
|
bubble.setVisibility(INVISIBLE);
|
||||||
|
currentAnimator = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnimationCancel(Animator animation) {
|
||||||
|
super.onAnimationCancel(animation);
|
||||||
|
bubble.setVisibility(INVISIBLE);
|
||||||
|
currentAnimator = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
currentAnimator.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,6 +20,7 @@ public interface ClickCallback {
|
|||||||
default void onServerClick(Bundle bundle) {}
|
default void onServerClick(Bundle bundle) {}
|
||||||
default void onServerLongClick(Bundle bundle) {}
|
default void onServerLongClick(Bundle bundle) {}
|
||||||
default void onPodcastEpisodeClick(Bundle bundle) {}
|
default void onPodcastEpisodeClick(Bundle bundle) {}
|
||||||
|
default void onPodcastEpisodeAltClick(Bundle bundle) {}
|
||||||
default void onPodcastEpisodeLongClick(Bundle bundle) {}
|
default void onPodcastEpisodeLongClick(Bundle bundle) {}
|
||||||
default void onPodcastChannelClick(Bundle bundle) {}
|
default void onPodcastChannelClick(Bundle bundle) {}
|
||||||
default void onPodcastChannelLongClick(Bundle bundle) {}
|
default void onPodcastChannelLongClick(Bundle bundle) {}
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.cappielloantonio.tempo.interfaces;
|
||||||
|
|
||||||
|
|
||||||
|
import androidx.annotation.Keep;
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
public interface DialogClickCallback {
|
||||||
|
default void onPositiveClick() {}
|
||||||
|
|
||||||
|
default void onNegativeClick() {}
|
||||||
|
|
||||||
|
default void onNeutralClick() {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.cappielloantonio.tempo.interfaces;
|
||||||
|
|
||||||
|
import androidx.annotation.Keep;
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
public interface PlaylistCallback {
|
||||||
|
default void onDismiss() {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.cappielloantonio.tempo.interfaces;
|
||||||
|
|
||||||
|
import androidx.annotation.Keep;
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
public interface StarCallback {
|
||||||
|
default void onError() {}
|
||||||
|
default void onSuccess() {}
|
||||||
|
}
|
||||||
@@ -20,6 +20,9 @@ class Download(@PrimaryKey override val id: String) : Child(id) {
|
|||||||
@ColumnInfo(name = "download_state", defaultValue = "1")
|
@ColumnInfo(name = "download_state", defaultValue = "1")
|
||||||
var downloadState: Int = 0
|
var downloadState: Int = 0
|
||||||
|
|
||||||
|
@ColumnInfo(name = "download_uri", defaultValue = "")
|
||||||
|
var downloadUri: String? = null
|
||||||
|
|
||||||
constructor(child: Child) : this(child.id) {
|
constructor(child: Child) : this(child.id) {
|
||||||
parentId = child.parentId
|
parentId = child.parentId
|
||||||
isDir = child.isDir
|
isDir = child.isDir
|
||||||
@@ -52,4 +55,10 @@ class Download(@PrimaryKey override val id: String) : Child(id) {
|
|||||||
originalWidth = child.originalWidth
|
originalWidth = child.originalWidth
|
||||||
originalHeight = child.originalHeight
|
originalHeight = child.originalHeight
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
data class DownloadStack(
|
||||||
|
var id: String,
|
||||||
|
var view: String?
|
||||||
|
)
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package com.cappielloantonio.tempo.model
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import androidx.annotation.Keep
|
||||||
|
import androidx.annotation.Nullable
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
@Parcelize
|
||||||
|
@Entity(tableName = "favorite")
|
||||||
|
data class Favorite(
|
||||||
|
@PrimaryKey
|
||||||
|
@ColumnInfo(name = "timestamp")
|
||||||
|
var timestamp: Long,
|
||||||
|
|
||||||
|
@ColumnInfo(name = "songId")
|
||||||
|
val songId: String?,
|
||||||
|
|
||||||
|
@ColumnInfo(name = "albumId")
|
||||||
|
val albumId: String?,
|
||||||
|
|
||||||
|
@ColumnInfo(name = "artistId")
|
||||||
|
val artistId: String?,
|
||||||
|
|
||||||
|
@ColumnInfo(name = "toStar")
|
||||||
|
val toStar: Boolean,
|
||||||
|
) : Parcelable {
|
||||||
|
override fun toString(): String = (songId ?: "null") + (albumId ?: "null") + (artistId ?: "null")
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.cappielloantonio.tempo.model
|
||||||
|
|
||||||
|
import androidx.annotation.Keep
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
data class ReplayGain(
|
||||||
|
var trackGain: Float = 0f,
|
||||||
|
var albumGain: Float = 0f,
|
||||||
|
)
|
||||||
@@ -21,8 +21,6 @@ import retrofit2.Callback;
|
|||||||
import retrofit2.Response;
|
import retrofit2.Response;
|
||||||
|
|
||||||
public class AlbumRepository {
|
public class AlbumRepository {
|
||||||
private static final String TAG = "AlbumRepository";
|
|
||||||
|
|
||||||
public MutableLiveData<List<AlbumID3>> getAlbums(String type, int size, Integer fromYear, Integer toYear) {
|
public MutableLiveData<List<AlbumID3>> getAlbums(String type, int size, Integer fromYear, Integer toYear) {
|
||||||
MutableLiveData<List<AlbumID3>> listLiveAlbums = new MutableLiveData<>(new ArrayList<>());
|
MutableLiveData<List<AlbumID3>> listLiveAlbums = new MutableLiveData<>(new ArrayList<>());
|
||||||
|
|
||||||
@@ -78,40 +76,6 @@ public class AlbumRepository {
|
|||||||
return starredAlbums;
|
return starredAlbums;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void star(String id) {
|
|
||||||
App.getSubsonicClientInstance(false)
|
|
||||||
.getMediaAnnotationClient()
|
|
||||||
.star(null, id, null)
|
|
||||||
.enqueue(new Callback<ApiResponse>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void unstar(String id) {
|
|
||||||
App.getSubsonicClientInstance(false)
|
|
||||||
.getMediaAnnotationClient()
|
|
||||||
.unstar(null, id, null)
|
|
||||||
.enqueue(new Callback<ApiResponse>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRating(String id, int rating) {
|
public void setRating(String id, int rating) {
|
||||||
App.getSubsonicClientInstance(false)
|
App.getSubsonicClientInstance(false)
|
||||||
.getMediaAnnotationClient()
|
.getMediaAnnotationClient()
|
||||||
@@ -141,7 +105,9 @@ public class AlbumRepository {
|
|||||||
List<Child> tracks = new ArrayList<>();
|
List<Child> tracks = new ArrayList<>();
|
||||||
|
|
||||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getAlbum() != null) {
|
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getAlbum() != null) {
|
||||||
tracks.addAll(response.body().getSubsonicResponse().getAlbum().getSongs());
|
if (response.body().getSubsonicResponse().getAlbum().getSongs() != null) {
|
||||||
|
tracks.addAll(response.body().getSubsonicResponse().getAlbum().getSongs());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
albumTracks.setValue(tracks);
|
albumTracks.setValue(tracks);
|
||||||
|
|||||||
@@ -63,8 +63,10 @@ public class ArtistRepository {
|
|||||||
if (response.isSuccessful() && response.body() != null) {
|
if (response.isSuccessful() && response.body() != null) {
|
||||||
List<ArtistID3> artists = new ArrayList<>();
|
List<ArtistID3> artists = new ArrayList<>();
|
||||||
|
|
||||||
for (IndexID3 index : response.body().getSubsonicResponse().getArtists().getIndices()) {
|
if(response.body().getSubsonicResponse().getArtists() != null) {
|
||||||
artists.addAll(index.getArtists());
|
for (IndexID3 index : response.body().getSubsonicResponse().getArtists().getIndices()) {
|
||||||
|
artists.addAll(index.getArtists());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (random) {
|
if (random) {
|
||||||
@@ -135,9 +137,6 @@ public class ArtistRepository {
|
|||||||
return artist;
|
return artist;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Metodo che mi restituisce le informazioni complete dell'artista (bio, immagini prese da last.fm, artisti simili...)
|
|
||||||
*/
|
|
||||||
public MutableLiveData<ArtistInfo2> getArtistFullInfo(String id) {
|
public MutableLiveData<ArtistInfo2> getArtistFullInfo(String id) {
|
||||||
MutableLiveData<ArtistInfo2> artistFullInfo = new MutableLiveData<>(null);
|
MutableLiveData<ArtistInfo2> artistFullInfo = new MutableLiveData<>(null);
|
||||||
|
|
||||||
@@ -161,40 +160,6 @@ public class ArtistRepository {
|
|||||||
return artistFullInfo;
|
return artistFullInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void star(String id) {
|
|
||||||
App.getSubsonicClientInstance(false)
|
|
||||||
.getMediaAnnotationClient()
|
|
||||||
.star(null, null, id)
|
|
||||||
.enqueue(new Callback<ApiResponse>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void unstar(String id) {
|
|
||||||
App.getSubsonicClientInstance(false)
|
|
||||||
.getMediaAnnotationClient()
|
|
||||||
.unstar(null, null, id)
|
|
||||||
.enqueue(new Callback<ApiResponse>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRating(String id, int rating) {
|
public void setRating(String id, int rating) {
|
||||||
App.getSubsonicClientInstance(false)
|
App.getSubsonicClientInstance(false)
|
||||||
.getMediaAnnotationClient()
|
.getMediaAnnotationClient()
|
||||||
|
|||||||
@@ -4,8 +4,11 @@ import androidx.lifecycle.LiveData;
|
|||||||
|
|
||||||
import com.cappielloantonio.tempo.database.AppDatabase;
|
import com.cappielloantonio.tempo.database.AppDatabase;
|
||||||
import com.cappielloantonio.tempo.database.dao.DownloadDao;
|
import com.cappielloantonio.tempo.database.dao.DownloadDao;
|
||||||
|
import com.cappielloantonio.tempo.database.dao.FavoriteDao;
|
||||||
import com.cappielloantonio.tempo.model.Download;
|
import com.cappielloantonio.tempo.model.Download;
|
||||||
|
import com.cappielloantonio.tempo.model.Favorite;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class DownloadRepository {
|
public class DownloadRepository {
|
||||||
@@ -15,6 +18,43 @@ public class DownloadRepository {
|
|||||||
return downloadDao.getAll();
|
return downloadDao.getAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Download getDownload(String id) {
|
||||||
|
Download download = null;
|
||||||
|
|
||||||
|
GetDownloadThreadSafe getDownloadThreadSafe = new GetDownloadThreadSafe(downloadDao, id);
|
||||||
|
Thread thread = new Thread(getDownloadThreadSafe);
|
||||||
|
thread.start();
|
||||||
|
|
||||||
|
try {
|
||||||
|
thread.join();
|
||||||
|
download = getDownloadThreadSafe.getDownload();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return download;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class GetDownloadThreadSafe implements Runnable {
|
||||||
|
private final DownloadDao downloadDao;
|
||||||
|
private final String id;
|
||||||
|
private Download download;
|
||||||
|
|
||||||
|
public GetDownloadThreadSafe(DownloadDao downloadDao, String id) {
|
||||||
|
this.downloadDao = downloadDao;
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
download = downloadDao.getOne(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Download getDownload() {
|
||||||
|
return download;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void insert(Download download) {
|
public void insert(Download download) {
|
||||||
InsertThreadSafe insert = new InsertThreadSafe(downloadDao, download);
|
InsertThreadSafe insert = new InsertThreadSafe(downloadDao, download);
|
||||||
Thread thread = new Thread(insert);
|
Thread thread = new Thread(insert);
|
||||||
|
|||||||
@@ -0,0 +1,140 @@
|
|||||||
|
package com.cappielloantonio.tempo.repository;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import com.cappielloantonio.tempo.App;
|
||||||
|
import com.cappielloantonio.tempo.database.AppDatabase;
|
||||||
|
import com.cappielloantonio.tempo.database.dao.FavoriteDao;
|
||||||
|
import com.cappielloantonio.tempo.interfaces.StarCallback;
|
||||||
|
import com.cappielloantonio.tempo.model.Favorite;
|
||||||
|
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import retrofit2.Call;
|
||||||
|
import retrofit2.Callback;
|
||||||
|
import retrofit2.Response;
|
||||||
|
|
||||||
|
public class FavoriteRepository {
|
||||||
|
private final FavoriteDao favoriteDao = AppDatabase.getInstance().favoriteDao();
|
||||||
|
|
||||||
|
public void star(String id, String albumId, String artistId, StarCallback starCallback) {
|
||||||
|
App.getSubsonicClientInstance(false)
|
||||||
|
.getMediaAnnotationClient()
|
||||||
|
.star(id, albumId, artistId)
|
||||||
|
.enqueue(new Callback<ApiResponse>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||||
|
if (response.isSuccessful()) {
|
||||||
|
starCallback.onSuccess();
|
||||||
|
} else {
|
||||||
|
starCallback.onError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||||
|
starCallback.onError();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unstar(String id, String albumId, String artistId, StarCallback starCallback) {
|
||||||
|
App.getSubsonicClientInstance(false)
|
||||||
|
.getMediaAnnotationClient()
|
||||||
|
.unstar(id, albumId, artistId)
|
||||||
|
.enqueue(new Callback<ApiResponse>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||||
|
if (response.isSuccessful()) {
|
||||||
|
starCallback.onSuccess();
|
||||||
|
} else {
|
||||||
|
starCallback.onError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||||
|
starCallback.onError();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Favorite> getFavorites() {
|
||||||
|
List<Favorite> favorites = new ArrayList<>();
|
||||||
|
|
||||||
|
GetAllThreadSafe getAllThreadSafe = new GetAllThreadSafe(favoriteDao);
|
||||||
|
Thread thread = new Thread(getAllThreadSafe);
|
||||||
|
thread.start();
|
||||||
|
|
||||||
|
try {
|
||||||
|
thread.join();
|
||||||
|
favorites = getAllThreadSafe.getFavorites();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return favorites;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class GetAllThreadSafe implements Runnable {
|
||||||
|
private final FavoriteDao favoriteDao;
|
||||||
|
private List<Favorite> favorites = new ArrayList<>();
|
||||||
|
|
||||||
|
public GetAllThreadSafe(FavoriteDao favoriteDao) {
|
||||||
|
this.favoriteDao = favoriteDao;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
favorites = favoriteDao.getAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Favorite> getFavorites() {
|
||||||
|
return favorites;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void starLater(String id, String albumId, String artistId, boolean toStar) {
|
||||||
|
InsertThreadSafe insert = new InsertThreadSafe(favoriteDao, new Favorite(System.currentTimeMillis(), id, albumId, artistId, toStar));
|
||||||
|
Thread thread = new Thread(insert);
|
||||||
|
thread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class InsertThreadSafe implements Runnable {
|
||||||
|
private final FavoriteDao favoriteDao;
|
||||||
|
private final Favorite favorite;
|
||||||
|
|
||||||
|
public InsertThreadSafe(FavoriteDao favoriteDao, Favorite favorite) {
|
||||||
|
this.favoriteDao = favoriteDao;
|
||||||
|
this.favorite = favorite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
favoriteDao.insert(favorite);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void delete(Favorite favorite) {
|
||||||
|
DeleteThreadSafe delete = new DeleteThreadSafe(favoriteDao, favorite);
|
||||||
|
Thread thread = new Thread(delete);
|
||||||
|
thread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class DeleteThreadSafe implements Runnable {
|
||||||
|
private final FavoriteDao favoriteDao;
|
||||||
|
private final Favorite favorite;
|
||||||
|
|
||||||
|
public DeleteThreadSafe(FavoriteDao favoriteDao, Favorite favorite) {
|
||||||
|
this.favoriteDao = favoriteDao;
|
||||||
|
this.favorite = favorite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
favoriteDao.delete(favorite);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,9 +24,14 @@ public class GenreRepository {
|
|||||||
.enqueue(new Callback<ApiResponse>() {
|
.enqueue(new Callback<ApiResponse>() {
|
||||||
@Override
|
@Override
|
||||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getGenres() != null) {
|
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse() != null && response.body().getSubsonicResponse().getGenres() != null) {
|
||||||
List<Genre> genreList = response.body().getSubsonicResponse().getGenres().getGenres();
|
List<Genre> genreList = response.body().getSubsonicResponse().getGenres().getGenres();
|
||||||
|
|
||||||
|
if (genreList == null || genreList.isEmpty()) {
|
||||||
|
genres.setValue(Collections.emptyList());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (random) {
|
if (random) {
|
||||||
Collections.shuffle(genreList);
|
Collections.shuffle(genreList);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,12 +95,29 @@ public class PlaylistRepository {
|
|||||||
.enqueue(new Callback<ApiResponse>() {
|
.enqueue(new Callback<ApiResponse>() {
|
||||||
@Override
|
@Override
|
||||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||||
Log.d("PLAYLIST", response.toString());
|
Log.d("createPlaylist", "onResponse: ");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||||
Log.d("PLAYLIST", t.toString());
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updatePlaylist(String playlistId, String name, ArrayList<String> songsId) {
|
||||||
|
App.getSubsonicClientInstance(false)
|
||||||
|
.getPlaylistClient()
|
||||||
|
.deletePlaylist(playlistId)
|
||||||
|
.enqueue(new Callback<ApiResponse>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||||
|
createPlaylist(null, name, songsId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -133,4 +133,21 @@ public class PodcastRepository {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void downloadPodcastEpisode(String episodeId) {
|
||||||
|
App.getSubsonicClientInstance(false)
|
||||||
|
.getPodcastClient()
|
||||||
|
.downloadPodcastEpisode(episodeId)
|
||||||
|
.enqueue(new Callback<ApiResponse>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,8 +17,12 @@ public class ScanRepository {
|
|||||||
.enqueue(new Callback<ApiResponse>() {
|
.enqueue(new Callback<ApiResponse>() {
|
||||||
@Override
|
@Override
|
||||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull retrofit2.Response<ApiResponse> response) {
|
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull retrofit2.Response<ApiResponse> response) {
|
||||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getScanStatus() != null) {
|
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse() != null) {
|
||||||
callback.onSuccess(response.body().getSubsonicResponse().getScanStatus().isScanning(), response.body().getSubsonicResponse().getScanStatus().getCount());
|
if (response.body().getSubsonicResponse().getError() != null) {
|
||||||
|
callback.onError(new Exception(response.body().getSubsonicResponse().getError().getMessage()));
|
||||||
|
} else if (response.body().getSubsonicResponse().getScanStatus() != null) {
|
||||||
|
callback.onSuccess(response.body().getSubsonicResponse().getScanStatus().isScanning(), response.body().getSubsonicResponse().getScanStatus().getCount());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,8 +40,12 @@ public class ScanRepository {
|
|||||||
.enqueue(new Callback<ApiResponse>() {
|
.enqueue(new Callback<ApiResponse>() {
|
||||||
@Override
|
@Override
|
||||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull retrofit2.Response<ApiResponse> response) {
|
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull retrofit2.Response<ApiResponse> response) {
|
||||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getScanStatus() != null) {
|
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse() != null) {
|
||||||
callback.onSuccess(response.body().getSubsonicResponse().getScanStatus().isScanning(), response.body().getSubsonicResponse().getScanStatus().getCount());
|
if (response.body().getSubsonicResponse().getError() != null) {
|
||||||
|
callback.onError(new Exception(response.body().getSubsonicResponse().getError().getMessage()));
|
||||||
|
} else if (response.body().getSubsonicResponse().getScanStatus() != null) {
|
||||||
|
callback.onSuccess(response.body().getSubsonicResponse().getScanStatus().isScanning(), response.body().getSubsonicResponse().getScanStatus().getCount());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package com.cappielloantonio.tempo.repository;
|
package com.cappielloantonio.tempo.repository;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.lifecycle.MutableLiveData;
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
|
||||||
@@ -11,6 +13,7 @@ import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
|||||||
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
|
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||||
|
import com.cappielloantonio.tempo.subsonic.models.SearchResult2;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.SearchResult3;
|
import com.cappielloantonio.tempo.subsonic.models.SearchResult3;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -24,7 +27,28 @@ import retrofit2.Response;
|
|||||||
public class SearchingRepository {
|
public class SearchingRepository {
|
||||||
private final RecentSearchDao recentSearchDao = AppDatabase.getInstance().recentSearchDao();
|
private final RecentSearchDao recentSearchDao = AppDatabase.getInstance().recentSearchDao();
|
||||||
|
|
||||||
public MutableLiveData<SearchResult3> search(String query) {
|
public MutableLiveData<SearchResult2> search2(String query) {
|
||||||
|
MutableLiveData<SearchResult2> result = new MutableLiveData<>();
|
||||||
|
|
||||||
|
App.getSubsonicClientInstance(false)
|
||||||
|
.getSearchingClient()
|
||||||
|
.search3(query, 20, 20, 20)
|
||||||
|
.enqueue(new Callback<ApiResponse>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||||
|
result.setValue(response.body().getSubsonicResponse().getSearchResult2());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MutableLiveData<SearchResult3> search3(String query) {
|
||||||
MutableLiveData<SearchResult3> result = new MutableLiveData<>();
|
MutableLiveData<SearchResult3> result = new MutableLiveData<>();
|
||||||
|
|
||||||
App.getSubsonicClientInstance(false)
|
App.getSubsonicClientInstance(false)
|
||||||
@@ -78,6 +102,8 @@ public class SearchingRepository {
|
|||||||
LinkedHashSet<String> hashSet = new LinkedHashSet<>(newSuggestions);
|
LinkedHashSet<String> hashSet = new LinkedHashSet<>(newSuggestions);
|
||||||
ArrayList<String> suggestionsWithoutDuplicates = new ArrayList<>(hashSet);
|
ArrayList<String> suggestionsWithoutDuplicates = new ArrayList<>(hashSet);
|
||||||
|
|
||||||
|
Log.d("suggestionsWithoutDuplicates", suggestionsWithoutDuplicates.toString());
|
||||||
|
|
||||||
suggestions.setValue(suggestionsWithoutDuplicates);
|
suggestions.setValue(suggestionsWithoutDuplicates);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -103,10 +129,10 @@ public class SearchingRepository {
|
|||||||
thread.start();
|
thread.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<String> getRecentSearchSuggestion(int limit) {
|
public List<String> getRecentSearchSuggestion() {
|
||||||
List<String> recent = new ArrayList<>();
|
List<String> recent = new ArrayList<>();
|
||||||
|
|
||||||
RecentThreadSafe suggestionsThread = new RecentThreadSafe(recentSearchDao, limit);
|
RecentThreadSafe suggestionsThread = new RecentThreadSafe(recentSearchDao);
|
||||||
Thread thread = new Thread(suggestionsThread);
|
Thread thread = new Thread(suggestionsThread);
|
||||||
thread.start();
|
thread.start();
|
||||||
|
|
||||||
@@ -152,17 +178,15 @@ public class SearchingRepository {
|
|||||||
|
|
||||||
private static class RecentThreadSafe implements Runnable {
|
private static class RecentThreadSafe implements Runnable {
|
||||||
private final RecentSearchDao recentSearchDao;
|
private final RecentSearchDao recentSearchDao;
|
||||||
private final int limit;
|
|
||||||
private List<String> recent = new ArrayList<>();
|
private List<String> recent = new ArrayList<>();
|
||||||
|
|
||||||
public RecentThreadSafe(RecentSearchDao recentSearchDao, int limit) {
|
public RecentThreadSafe(RecentSearchDao recentSearchDao) {
|
||||||
this.recentSearchDao = recentSearchDao;
|
this.recentSearchDao = recentSearchDao;
|
||||||
this.limit = limit;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
recent = recentSearchDao.getRecent(limit);
|
recent = recentSearchDao.getRecent();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<String> getRecent() {
|
public List<String> getRecent() {
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.lifecycle.MutableLiveData;
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
|
||||||
import com.cappielloantonio.tempo.App;
|
import com.cappielloantonio.tempo.App;
|
||||||
|
import com.cappielloantonio.tempo.database.AppDatabase;
|
||||||
|
import com.cappielloantonio.tempo.database.dao.FavoriteDao;
|
||||||
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||||
|
|
||||||
@@ -86,7 +88,7 @@ public class SongRepository {
|
|||||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||||
List<Child> songs = new ArrayList<>();
|
List<Child> songs = new ArrayList<>();
|
||||||
|
|
||||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getRandomSongs() != null) {
|
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getRandomSongs() != null && response.body().getSubsonicResponse().getRandomSongs().getSongs() != null) {
|
||||||
songs.addAll(response.body().getSubsonicResponse().getRandomSongs().getSongs());
|
songs.addAll(response.body().getSubsonicResponse().getRandomSongs().getSongs());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,40 +121,6 @@ public class SongRepository {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void star(String id) {
|
|
||||||
App.getSubsonicClientInstance(false)
|
|
||||||
.getMediaAnnotationClient()
|
|
||||||
.star(id, null, null)
|
|
||||||
.enqueue(new Callback<ApiResponse>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void unstar(String id) {
|
|
||||||
App.getSubsonicClientInstance(false)
|
|
||||||
.getMediaAnnotationClient()
|
|
||||||
.unstar(id, null, null)
|
|
||||||
.enqueue(new Callback<ApiResponse>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRating(String id, int rating) {
|
public void setRating(String id, int rating) {
|
||||||
App.getSubsonicClientInstance(false)
|
App.getSubsonicClientInstance(false)
|
||||||
.getMediaAnnotationClient()
|
.getMediaAnnotationClient()
|
||||||
@@ -173,8 +141,6 @@ public class SongRepository {
|
|||||||
public MutableLiveData<List<Child>> getSongsByGenre(String id, int page) {
|
public MutableLiveData<List<Child>> getSongsByGenre(String id, int page) {
|
||||||
MutableLiveData<List<Child>> songsByGenre = new MutableLiveData<>();
|
MutableLiveData<List<Child>> songsByGenre = new MutableLiveData<>();
|
||||||
|
|
||||||
Log.d(TAG, "onScrolled PAGE: " + page);
|
|
||||||
|
|
||||||
App.getSubsonicClientInstance(false)
|
App.getSubsonicClientInstance(false)
|
||||||
.getAlbumSongListClient()
|
.getAlbumSongListClient()
|
||||||
.getSongsByGenre(id, 100, 100 * page)
|
.getSongsByGenre(id, 100, 100 * page)
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ public class SystemRepository {
|
|||||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull retrofit2.Response<ApiResponse> response) {
|
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull retrofit2.Response<ApiResponse> response) {
|
||||||
if (response.body() != null) {
|
if (response.body() != null) {
|
||||||
if (response.body().getSubsonicResponse().getStatus().equals(ResponseStatus.FAILED)) {
|
if (response.body().getSubsonicResponse().getStatus().equals(ResponseStatus.FAILED)) {
|
||||||
callback.onError(new Exception(response.body().getSubsonicResponse().getError().getCode().getValue() + " - " + response.body().getSubsonicResponse().getError().getMessage()));
|
callback.onError(new Exception(response.body().getSubsonicResponse().getError().getCode() + " - " + response.body().getSubsonicResponse().getError().getMessage()));
|
||||||
} else if (response.body().getSubsonicResponse().getStatus().equals(ResponseStatus.OK)) {
|
} else if (response.body().getSubsonicResponse().getStatus().equals(ResponseStatus.OK)) {
|
||||||
String password = response.raw().request().url().queryParameter("p");
|
String password = response.raw().request().url().queryParameter("p");
|
||||||
String token = response.raw().request().url().queryParameter("t");
|
String token = response.raw().request().url().queryParameter("t");
|
||||||
|
|||||||
@@ -77,6 +77,8 @@ public class DownloaderManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void download(MediaItem mediaItem, com.cappielloantonio.tempo.model.Download download) {
|
public void download(MediaItem mediaItem, com.cappielloantonio.tempo.model.Download download) {
|
||||||
|
download.setDownloadUri(mediaItem.requestMetadata.mediaUri.toString());
|
||||||
|
|
||||||
DownloadService.sendAddDownload(context, DownloaderService.class, buildDownloadRequest(mediaItem), false);
|
DownloadService.sendAddDownload(context, DownloaderService.class, buildDownloadRequest(mediaItem), false);
|
||||||
insertDatabase(download);
|
insertDatabase(download);
|
||||||
}
|
}
|
||||||
@@ -89,6 +91,7 @@ public class DownloaderManager {
|
|||||||
|
|
||||||
public void remove(MediaItem mediaItem, com.cappielloantonio.tempo.model.Download download) {
|
public void remove(MediaItem mediaItem, com.cappielloantonio.tempo.model.Download download) {
|
||||||
DownloadService.sendRemoveDownload(context, DownloaderService.class, buildDownloadRequest(mediaItem).id, false);
|
DownloadService.sendRemoveDownload(context, DownloaderService.class, buildDownloadRequest(mediaItem).id, false);
|
||||||
|
deleteDatabase(download.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void remove(List<MediaItem> mediaItems, List<com.cappielloantonio.tempo.model.Download> downloads) {
|
public void remove(List<MediaItem> mediaItems, List<com.cappielloantonio.tempo.model.Download> downloads) {
|
||||||
@@ -97,6 +100,12 @@ public class DownloaderManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void removeAll() {
|
||||||
|
DownloadService.sendRemoveAllDownloads(context, DownloaderService.class, false);
|
||||||
|
deleteAllDatabase();
|
||||||
|
DownloadUtil.eraseDownloadFolder(context);
|
||||||
|
}
|
||||||
|
|
||||||
private void loadDownloads() {
|
private void loadDownloads() {
|
||||||
try (DownloadCursor loadedDownloads = downloadIndex.getDownloads()) {
|
try (DownloadCursor loadedDownloads = downloadIndex.getDownloads()) {
|
||||||
while (loadedDownloads.moveToNext()) {
|
while (loadedDownloads.moveToNext()) {
|
||||||
@@ -108,6 +117,11 @@ public class DownloaderManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getDownloadNotificationMessage(String id) {
|
||||||
|
com.cappielloantonio.tempo.model.Download download = getDownloadRepository().getDownload(id);
|
||||||
|
return download != null ? download.getTitle() : null;
|
||||||
|
}
|
||||||
|
|
||||||
private static DownloadRepository getDownloadRepository() {
|
private static DownloadRepository getDownloadRepository() {
|
||||||
return new DownloadRepository();
|
return new DownloadRepository();
|
||||||
}
|
}
|
||||||
@@ -120,6 +134,10 @@ public class DownloaderManager {
|
|||||||
getDownloadRepository().delete(id);
|
getDownloadRepository().delete(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void deleteAllDatabase() {
|
||||||
|
getDownloadRepository().deleteAll();
|
||||||
|
}
|
||||||
|
|
||||||
public static void updateDatabase(String id) {
|
public static void updateDatabase(String id) {
|
||||||
getDownloadRepository().update(id);
|
getDownloadRepository().update(id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media3.common.util.NotificationUtil;
|
import androidx.media3.common.util.NotificationUtil;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
import androidx.media3.common.util.Util;
|
|
||||||
import androidx.media3.exoplayer.offline.Download;
|
import androidx.media3.exoplayer.offline.Download;
|
||||||
import androidx.media3.exoplayer.offline.DownloadManager;
|
import androidx.media3.exoplayer.offline.DownloadManager;
|
||||||
import androidx.media3.exoplayer.offline.DownloadNotificationHelper;
|
import androidx.media3.exoplayer.offline.DownloadNotificationHelper;
|
||||||
@@ -52,6 +51,8 @@ public class DownloaderService extends androidx.media3.exoplayer.offline.Downloa
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static final class TerminalStateNotificationHelper implements DownloadManager.Listener {
|
private static final class TerminalStateNotificationHelper implements DownloadManager.Listener {
|
||||||
|
private static final String TAG = "TerminalStateNotificatinHelper";
|
||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
private final DownloadNotificationHelper notificationHelper;
|
private final DownloadNotificationHelper notificationHelper;
|
||||||
|
|
||||||
@@ -68,10 +69,10 @@ public class DownloaderService extends androidx.media3.exoplayer.offline.Downloa
|
|||||||
Notification notification;
|
Notification notification;
|
||||||
|
|
||||||
if (download.state == Download.STATE_COMPLETED) {
|
if (download.state == Download.STATE_COMPLETED) {
|
||||||
notification = notificationHelper.buildDownloadCompletedNotification(context, R.drawable.ic_check_circle, null, Util.fromUtf8Bytes(download.request.data));
|
notification = notificationHelper.buildDownloadCompletedNotification(context, R.drawable.ic_check_circle, null, DownloaderManager.getDownloadNotificationMessage(download.request.id));
|
||||||
DownloaderManager.updateDatabase(download.request.id);
|
DownloaderManager.updateDatabase(download.request.id);
|
||||||
} else if (download.state == Download.STATE_FAILED) {
|
} else if (download.state == Download.STATE_FAILED) {
|
||||||
notification = notificationHelper.buildDownloadFailedNotification(context, R.drawable.ic_error, null, Util.fromUtf8Bytes(download.request.data));
|
notification = notificationHelper.buildDownloadFailedNotification(context, R.drawable.ic_error, null, DownloaderManager.getDownloadNotificationMessage(download.request.id));
|
||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ class RetrofitClient(subsonic: Subsonic) {
|
|||||||
init {
|
init {
|
||||||
retrofit = Retrofit.Builder()
|
retrofit = Retrofit.Builder()
|
||||||
.baseUrl(subsonic.url)
|
.baseUrl(subsonic.url)
|
||||||
|
.addConverterFactory(GsonConverterFactory.create(GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ss").create()))
|
||||||
.addConverterFactory(GsonConverterFactory.create(GsonBuilder().setLenient().create()))
|
.addConverterFactory(GsonConverterFactory.create(GsonBuilder().setLenient().create()))
|
||||||
.client(getOkHttpClient())
|
.client(getOkHttpClient())
|
||||||
.build()
|
.build()
|
||||||
|
|||||||
@@ -48,4 +48,9 @@ public class PodcastClient {
|
|||||||
Log.d(TAG, "deletePodcastEpisode()");
|
Log.d(TAG, "deletePodcastEpisode()");
|
||||||
return podcastService.deletePodcastEpisode(subsonic.getParams(), episodeId);
|
return podcastService.deletePodcastEpisode(subsonic.getParams(), episodeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Call<ApiResponse> downloadPodcastEpisode(String episodeId) {
|
||||||
|
Log.d(TAG, "downloadPodcastEpisode()");
|
||||||
|
return podcastService.downloadPodcastEpisode(subsonic.getParams(), episodeId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,4 +27,7 @@ public interface PodcastService {
|
|||||||
|
|
||||||
@GET("deletePodcastEpisode")
|
@GET("deletePodcastEpisode")
|
||||||
Call<ApiResponse> deletePodcastEpisode(@QueryMap Map<String, String> params, @Query("id") String id);
|
Call<ApiResponse> deletePodcastEpisode(@QueryMap Map<String, String> params, @Query("id") String id);
|
||||||
|
|
||||||
|
@GET("downloadPodcastEpisode")
|
||||||
|
Call<ApiResponse> downloadPodcastEpisode(@QueryMap Map<String, String> params, @Query("id") String id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,6 @@ import androidx.annotation.Keep
|
|||||||
|
|
||||||
@Keep
|
@Keep
|
||||||
class Error {
|
class Error {
|
||||||
var code: ErrorCode? = null
|
var code: Int? = null
|
||||||
var message: String? = null
|
var message: String? = null
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,6 @@ package com.cappielloantonio.tempo.subsonic.models
|
|||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import androidx.annotation.Keep
|
import androidx.annotation.Keep
|
||||||
import androidx.room.ColumnInfo
|
|
||||||
import com.google.gson.annotations.SerializedName
|
import com.google.gson.annotations.SerializedName
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@@ -29,11 +28,9 @@ class PodcastEpisode : Parcelable {
|
|||||||
var transcodedContentType: String? = null
|
var transcodedContentType: String? = null
|
||||||
var transcodedSuffix: String? = null
|
var transcodedSuffix: String? = null
|
||||||
var duration: Int? = null
|
var duration: Int? = null
|
||||||
@ColumnInfo("bitrate")
|
|
||||||
@SerializedName("bitRate")
|
@SerializedName("bitRate")
|
||||||
var bitrate: Int? = null
|
var bitrate: Int? = null
|
||||||
var path: String? = null
|
var path: String? = null
|
||||||
@ColumnInfo(name = "is_video")
|
|
||||||
@SerializedName("isVideo")
|
@SerializedName("isVideo")
|
||||||
var isVideo: Boolean = false
|
var isVideo: Boolean = false
|
||||||
var userRating: Int? = null
|
var userRating: Int? = null
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.cappielloantonio.tempo.ui.activity;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
import android.net.ConnectivityManager;
|
import android.net.ConnectivityManager;
|
||||||
|
import android.net.NetworkInfo;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -30,6 +31,7 @@ import com.cappielloantonio.tempo.util.Preferences;
|
|||||||
import com.cappielloantonio.tempo.viewmodel.MainViewModel;
|
import com.cappielloantonio.tempo.viewmodel.MainViewModel;
|
||||||
import com.google.android.material.bottomnavigation.BottomNavigationView;
|
import com.google.android.material.bottomnavigation.BottomNavigationView;
|
||||||
import com.google.android.material.bottomsheet.BottomSheetBehavior;
|
import com.google.android.material.bottomsheet.BottomSheetBehavior;
|
||||||
|
import com.google.android.material.color.DynamicColors;
|
||||||
import com.google.common.util.concurrent.MoreExecutors;
|
import com.google.common.util.concurrent.MoreExecutors;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
@@ -50,10 +52,10 @@ public class MainActivity extends BaseActivity {
|
|||||||
|
|
||||||
ConnectivityStatusBroadcastReceiver connectivityStatusBroadcastReceiver;
|
ConnectivityStatusBroadcastReceiver connectivityStatusBroadcastReceiver;
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
SplashScreen.installSplashScreen(this);
|
SplashScreen.installSplashScreen(this);
|
||||||
|
DynamicColors.applyToActivityIfAvailable(this);
|
||||||
|
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
@@ -117,6 +119,8 @@ public class MainActivity extends BaseActivity {
|
|||||||
fragmentManager.beginTransaction().replace(R.id.player_bottom_sheet, new PlayerBottomSheetFragment(), "PlayerBottomSheet").commit();
|
fragmentManager.beginTransaction().replace(R.id.player_bottom_sheet, new PlayerBottomSheetFragment(), "PlayerBottomSheet").commit();
|
||||||
|
|
||||||
setBottomSheetInPeek(mainViewModel.isQueueLoaded());
|
setBottomSheetInPeek(mainViewModel.isQueueLoaded());
|
||||||
|
|
||||||
|
collapseBottomSheet();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setBottomSheetInPeek(Boolean isVisible) {
|
public void setBottomSheetInPeek(Boolean isVisible) {
|
||||||
@@ -141,6 +145,10 @@ public class MainActivity extends BaseActivity {
|
|||||||
handler.postDelayed(runnable, 100);
|
handler.postDelayed(runnable, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void expandBottomSheet() {
|
||||||
|
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
|
||||||
|
}
|
||||||
|
|
||||||
public void setBottomSheetDraggableState(Boolean isDraggable) {
|
public void setBottomSheetDraggableState(Boolean isDraggable) {
|
||||||
bottomSheetBehavior.setDraggable(isDraggable);
|
bottomSheetBehavior.setDraggable(isDraggable);
|
||||||
}
|
}
|
||||||
@@ -247,6 +255,7 @@ public class MainActivity extends BaseActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void goToLogin() {
|
private void goToLogin() {
|
||||||
|
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
|
||||||
setBottomNavigationBarVisibility(false);
|
setBottomNavigationBarVisibility(false);
|
||||||
setBottomSheetVisibility(false);
|
setBottomSheetVisibility(false);
|
||||||
|
|
||||||
@@ -333,7 +342,9 @@ public class MainActivity extends BaseActivity {
|
|||||||
private void checkConnectionType() {
|
private void checkConnectionType() {
|
||||||
if (Preferences.isWifiOnly()) {
|
if (Preferences.isWifiOnly()) {
|
||||||
ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
|
ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||||
if (connectivityManager.getActiveNetworkInfo().getType() != ConnectivityManager.TYPE_WIFI) {
|
NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
|
||||||
|
|
||||||
|
if (networkInfo != null && networkInfo.getType() != ConnectivityManager.TYPE_WIFI) {
|
||||||
ConnectionAlertDialog dialog = new ConnectionAlertDialog();
|
ConnectionAlertDialog dialog = new ConnectionAlertDialog();
|
||||||
dialog.show(getSupportFragmentManager(), null);
|
dialog.show(getSupportFragmentManager(), null);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,15 +2,12 @@ package com.cappielloantonio.tempo.ui.activity.base;
|
|||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.PowerManager;
|
import android.os.PowerManager;
|
||||||
import android.provider.Settings;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.core.app.ActivityCompat;
|
import androidx.core.app.ActivityCompat;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
@@ -19,11 +16,11 @@ import androidx.media3.exoplayer.offline.DownloadService;
|
|||||||
import androidx.media3.session.MediaBrowser;
|
import androidx.media3.session.MediaBrowser;
|
||||||
import androidx.media3.session.SessionToken;
|
import androidx.media3.session.SessionToken;
|
||||||
|
|
||||||
import com.cappielloantonio.tempo.R;
|
|
||||||
import com.cappielloantonio.tempo.service.DownloaderService;
|
import com.cappielloantonio.tempo.service.DownloaderService;
|
||||||
import com.cappielloantonio.tempo.service.MediaService;
|
import com.cappielloantonio.tempo.service.MediaService;
|
||||||
import com.cappielloantonio.tempo.util.UIUtil;
|
import com.cappielloantonio.tempo.ui.dialog.BatteryOptimizationDialog;
|
||||||
import com.google.android.gms.cast.framework.CastContext;
|
import com.cappielloantonio.tempo.util.Flavors;
|
||||||
|
import com.cappielloantonio.tempo.util.Preferences;
|
||||||
import com.google.android.material.elevation.SurfaceColors;
|
import com.google.android.material.elevation.SurfaceColors;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
|
||||||
@@ -36,8 +33,10 @@ public class BaseActivity extends AppCompatActivity {
|
|||||||
@Override
|
@Override
|
||||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
initializeCastContext();
|
Flavors.initializeCastContext(this);
|
||||||
initializeDownloader();
|
initializeDownloader();
|
||||||
|
checkBatteryOptimization();
|
||||||
|
checkPermission();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -47,13 +46,6 @@ public class BaseActivity extends AppCompatActivity {
|
|||||||
initializeBrowser();
|
initializeBrowser();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
checkBatteryOptimization();
|
|
||||||
checkPermission();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onStop() {
|
protected void onStop() {
|
||||||
releaseBrowser();
|
releaseBrowser();
|
||||||
@@ -61,7 +53,7 @@ public class BaseActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void checkBatteryOptimization() {
|
private void checkBatteryOptimization() {
|
||||||
if (detectBatteryOptimization()) {
|
if (detectBatteryOptimization() && Preferences.askForOptimization()) {
|
||||||
showBatteryOptimizationDialog();
|
showBatteryOptimizationDialog();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -81,19 +73,8 @@ public class BaseActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void showBatteryOptimizationDialog() {
|
private void showBatteryOptimizationDialog() {
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
BatteryOptimizationDialog dialog = new BatteryOptimizationDialog();
|
||||||
builder.setMessage(R.string.activity_battery_optimizations_summary)
|
dialog.show(getSupportFragmentManager(), null);
|
||||||
.setTitle(R.string.activity_battery_optimizations_title)
|
|
||||||
.setNegativeButton(R.string.activity_negative_button, null)
|
|
||||||
.setPositiveButton(R.string.activity_neutral_button, (dialog, id) -> openPowerSettings())
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void openPowerSettings() {
|
|
||||||
Intent intent = new Intent();
|
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
||||||
intent.setAction(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS);
|
|
||||||
startActivity(intent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeBrowser() {
|
private void initializeBrowser() {
|
||||||
@@ -116,11 +97,8 @@ public class BaseActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeCastContext() {
|
|
||||||
if (UIUtil.isCastApiAvailable(this)) CastContext.getSharedInstance(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setNavigationBarColor() {
|
private void setNavigationBarColor() {
|
||||||
getWindow().setNavigationBarColor(SurfaceColors.getColorForElevation(this, 10));
|
getWindow().setNavigationBarColor(SurfaceColors.getColorForElevation(this, 8));
|
||||||
|
getWindow().setStatusBarColor(SurfaceColors.getColorForElevation(this, 0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,25 +11,35 @@ import androidx.recyclerview.widget.RecyclerView;
|
|||||||
|
|
||||||
import com.cappielloantonio.tempo.R;
|
import com.cappielloantonio.tempo.R;
|
||||||
import com.cappielloantonio.tempo.databinding.ItemHorizontalDownloadBinding;
|
import com.cappielloantonio.tempo.databinding.ItemHorizontalDownloadBinding;
|
||||||
|
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
||||||
import com.cappielloantonio.tempo.interfaces.ClickCallback;
|
import com.cappielloantonio.tempo.interfaces.ClickCallback;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||||
import com.cappielloantonio.tempo.util.Constants;
|
import com.cappielloantonio.tempo.util.Constants;
|
||||||
import com.cappielloantonio.tempo.util.MusicUtil;
|
import com.cappielloantonio.tempo.util.MusicUtil;
|
||||||
|
import com.cappielloantonio.tempo.util.Util;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
public class DownloadHorizontalAdapter extends RecyclerView.Adapter<DownloadHorizontalAdapter.ViewHolder> {
|
public class DownloadHorizontalAdapter extends RecyclerView.Adapter<DownloadHorizontalAdapter.ViewHolder> {
|
||||||
private final ClickCallback click;
|
private final ClickCallback click;
|
||||||
|
|
||||||
|
private String view;
|
||||||
|
private String filterKey;
|
||||||
|
private String filterValue;
|
||||||
|
|
||||||
private List<Child> songs;
|
private List<Child> songs;
|
||||||
|
private List<Child> grouped;
|
||||||
|
|
||||||
public DownloadHorizontalAdapter(ClickCallback click) {
|
public DownloadHorizontalAdapter(ClickCallback click) {
|
||||||
this.click = click;
|
this.click = click;
|
||||||
|
this.view = Constants.DOWNLOAD_TYPE_TRACK;
|
||||||
this.songs = Collections.emptyList();
|
this.songs = Collections.emptyList();
|
||||||
|
this.grouped = Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@@ -41,31 +51,43 @@ public class DownloadHorizontalAdapter extends RecyclerView.Adapter<DownloadHori
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(ViewHolder holder, int position) {
|
public void onBindViewHolder(ViewHolder holder, int position) {
|
||||||
Child song = songs.get(position);
|
switch (view) {
|
||||||
|
case Constants.DOWNLOAD_TYPE_TRACK:
|
||||||
holder.item.downloadedSongTitleTextView.setText(MusicUtil.getReadableString(song.getTitle()));
|
initTrackLayout(holder, position);
|
||||||
holder.item.downloadedSongArtistTextView.setText(holder.itemView.getContext().getString(R.string.song_subtitle_formatter, MusicUtil.getReadableString(song.getArtist()), MusicUtil.getReadableDurationString(song.getDuration(), false)));
|
break;
|
||||||
holder.item.downloadedSongAlbumTextView.setText(MusicUtil.getReadableString(song.getAlbum()));
|
case Constants.DOWNLOAD_TYPE_ALBUM:
|
||||||
|
initAlbumLayout(holder, position);
|
||||||
if (position > 0 && songs.get(position - 1) != null && !Objects.equals(songs.get(position - 1).getAlbum(), songs.get(position).getAlbum())) {
|
break;
|
||||||
holder.item.divider.setPadding(0, (int) holder.itemView.getContext().getResources().getDimension(R.dimen.downloaded_item_padding), 0, 0);
|
case Constants.DOWNLOAD_TYPE_ARTIST:
|
||||||
} else {
|
initArtistLayout(holder, position);
|
||||||
if (position > 0) holder.item.divider.setVisibility(View.GONE);
|
break;
|
||||||
|
case Constants.DOWNLOAD_TYPE_GENRE:
|
||||||
|
initGenreLayout(holder, position);
|
||||||
|
break;
|
||||||
|
case Constants.DOWNLOAD_TYPE_YEAR:
|
||||||
|
initYearLayout(holder, position);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getItemCount() {
|
public int getItemCount() {
|
||||||
return songs.size();
|
return grouped.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setItems(List<Child> songs) {
|
public void setItems(String view, String filterKey, String filterValue, List<Child> songs) {
|
||||||
|
this.view = filterValue != null ? view : filterKey;
|
||||||
|
this.filterKey = filterKey;
|
||||||
|
this.filterValue = filterValue;
|
||||||
|
|
||||||
this.songs = songs;
|
this.songs = songs;
|
||||||
|
this.grouped = groupSong(songs);
|
||||||
|
|
||||||
notifyDataSetChanged();
|
notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Child getItem(int id) {
|
public Child getItem(int id) {
|
||||||
return songs.get(id);
|
return grouped.get(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -78,6 +100,145 @@ public class DownloadHorizontalAdapter extends RecyclerView.Adapter<DownloadHori
|
|||||||
return position;
|
return position;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<Child> groupSong(List<Child> songs) {
|
||||||
|
switch (view) {
|
||||||
|
case Constants.DOWNLOAD_TYPE_TRACK:
|
||||||
|
return filterSong(filterKey, filterValue, songs.stream().filter(song -> Objects.nonNull(song.getId())).filter(Util.distinctByKey(Child::getId)).collect(Collectors.toList()));
|
||||||
|
case Constants.DOWNLOAD_TYPE_ALBUM:
|
||||||
|
return filterSong(filterKey, filterValue, songs.stream().filter(song -> Objects.nonNull(song.getAlbumId())).filter(Util.distinctByKey(Child::getAlbumId)).collect(Collectors.toList()));
|
||||||
|
case Constants.DOWNLOAD_TYPE_ARTIST:
|
||||||
|
return filterSong(filterKey, filterValue, songs.stream().filter(song -> Objects.nonNull(song.getArtistId())).filter(Util.distinctByKey(Child::getArtistId)).collect(Collectors.toList()));
|
||||||
|
case Constants.DOWNLOAD_TYPE_GENRE:
|
||||||
|
return filterSong(filterKey, filterValue, songs.stream().filter(song -> Objects.nonNull(song.getGenre())).filter(Util.distinctByKey(Child::getGenre)).collect(Collectors.toList()));
|
||||||
|
case Constants.DOWNLOAD_TYPE_YEAR:
|
||||||
|
return filterSong(filterKey, filterValue, songs.stream().filter(song -> Objects.nonNull(song.getYear())).filter(Util.distinctByKey(Child::getYear)).collect(Collectors.toList()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Child> filterSong(String filterKey, String filterValue, List<Child> songs) {
|
||||||
|
if (filterValue != null) {
|
||||||
|
switch (filterKey) {
|
||||||
|
case Constants.DOWNLOAD_TYPE_TRACK:
|
||||||
|
return songs.stream().filter(child -> child.getId().equals(filterValue)).collect(Collectors.toList());
|
||||||
|
case Constants.DOWNLOAD_TYPE_ALBUM:
|
||||||
|
return songs.stream().filter(child -> Objects.equals(child.getAlbumId(), filterValue)).collect(Collectors.toList());
|
||||||
|
case Constants.DOWNLOAD_TYPE_GENRE:
|
||||||
|
return songs.stream().filter(child -> Objects.equals(child.getGenre(), filterValue)).collect(Collectors.toList());
|
||||||
|
case Constants.DOWNLOAD_TYPE_YEAR:
|
||||||
|
return songs.stream().filter(child -> Objects.equals(child.getYear(), Integer.valueOf(filterValue))).collect(Collectors.toList());
|
||||||
|
case Constants.DOWNLOAD_TYPE_ARTIST:
|
||||||
|
return songs.stream().filter(child -> Objects.equals(child.getArtistId(), filterValue)).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return songs;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String countSong(String filterKey, String filterValue, List<Child> songs) {
|
||||||
|
if (filterValue != null) {
|
||||||
|
switch (filterKey) {
|
||||||
|
case Constants.DOWNLOAD_TYPE_TRACK:
|
||||||
|
return String.valueOf(songs.stream().filter(child -> child.getId().equals(filterValue)).count());
|
||||||
|
case Constants.DOWNLOAD_TYPE_ALBUM:
|
||||||
|
return String.valueOf(songs.stream().filter(child -> Objects.equals(child.getAlbumId(), filterValue)).count());
|
||||||
|
case Constants.DOWNLOAD_TYPE_GENRE:
|
||||||
|
return String.valueOf(songs.stream().filter(child -> Objects.equals(child.getGenre(), filterValue)).count());
|
||||||
|
case Constants.DOWNLOAD_TYPE_YEAR:
|
||||||
|
return String.valueOf(songs.stream().filter(child -> Objects.equals(child.getYear(), Integer.valueOf(filterValue))).count());
|
||||||
|
case Constants.DOWNLOAD_TYPE_ARTIST:
|
||||||
|
return String.valueOf(songs.stream().filter(child -> Objects.equals(child.getArtistId(), filterValue)).count());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "0";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initTrackLayout(ViewHolder holder, int position) {
|
||||||
|
Child song = grouped.get(position);
|
||||||
|
|
||||||
|
holder.item.downloadedItemTitleTextView.setText(MusicUtil.getReadableString(song.getTitle()));
|
||||||
|
holder.item.downloadedItemSubtitleTextView.setText(holder.itemView.getContext().getString(R.string.song_subtitle_formatter, MusicUtil.getReadableString(song.getArtist()), MusicUtil.getReadableDurationString(song.getDuration(), false)));
|
||||||
|
holder.item.downloadedItemPreTextView.setText(MusicUtil.getReadableString(song.getAlbum()));
|
||||||
|
|
||||||
|
CustomGlideRequest.Builder
|
||||||
|
.from(holder.itemView.getContext(), song.getCoverArtId())
|
||||||
|
.build()
|
||||||
|
.into(holder.item.itemCoverImageView);
|
||||||
|
|
||||||
|
holder.item.itemCoverImageView.setVisibility(View.VISIBLE);
|
||||||
|
holder.item.downloadedItemMoreButton.setVisibility(View.VISIBLE);
|
||||||
|
holder.item.divider.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
if (position > 0 && grouped.get(position - 1) != null && !Objects.equals(grouped.get(position - 1).getAlbum(), grouped.get(position).getAlbum())) {
|
||||||
|
holder.item.divider.setPadding(0, (int) holder.itemView.getContext().getResources().getDimension(R.dimen.downloaded_item_padding), 0, 0);
|
||||||
|
} else {
|
||||||
|
if (position > 0) holder.item.divider.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initAlbumLayout(ViewHolder holder, int position) {
|
||||||
|
Child song = grouped.get(position);
|
||||||
|
|
||||||
|
holder.item.downloadedItemTitleTextView.setText(MusicUtil.getReadableString(song.getAlbum()));
|
||||||
|
holder.item.downloadedItemSubtitleTextView.setText(holder.itemView.getContext().getString(R.string.download_item_single_subtitle_formatter, countSong(Constants.DOWNLOAD_TYPE_ALBUM, song.getAlbumId(), songs)));
|
||||||
|
holder.item.downloadedItemPreTextView.setText(MusicUtil.getReadableString(song.getArtist()));
|
||||||
|
|
||||||
|
CustomGlideRequest.Builder
|
||||||
|
.from(holder.itemView.getContext(), song.getCoverArtId())
|
||||||
|
.build()
|
||||||
|
.into(holder.item.itemCoverImageView);
|
||||||
|
|
||||||
|
holder.item.itemCoverImageView.setVisibility(View.VISIBLE);
|
||||||
|
holder.item.downloadedItemMoreButton.setVisibility(View.GONE);
|
||||||
|
holder.item.divider.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
if (position > 0 && grouped.get(position - 1) != null && !Objects.equals(grouped.get(position - 1).getArtist(), grouped.get(position).getArtist())) {
|
||||||
|
holder.item.divider.setPadding(0, (int) holder.itemView.getContext().getResources().getDimension(R.dimen.downloaded_item_padding), 0, 0);
|
||||||
|
} else {
|
||||||
|
if (position > 0) holder.item.divider.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initArtistLayout(ViewHolder holder, int position) {
|
||||||
|
Child song = grouped.get(position);
|
||||||
|
|
||||||
|
holder.item.downloadedItemTitleTextView.setText(MusicUtil.getReadableString(song.getArtist()));
|
||||||
|
holder.item.downloadedItemSubtitleTextView.setText(holder.itemView.getContext().getString(R.string.download_item_single_subtitle_formatter, countSong(Constants.DOWNLOAD_TYPE_ARTIST, song.getArtistId(), songs)));
|
||||||
|
|
||||||
|
CustomGlideRequest.Builder
|
||||||
|
.from(holder.itemView.getContext(), song.getCoverArtId())
|
||||||
|
.build()
|
||||||
|
.into(holder.item.itemCoverImageView);
|
||||||
|
|
||||||
|
holder.item.itemCoverImageView.setVisibility(View.VISIBLE);
|
||||||
|
holder.item.downloadedItemMoreButton.setVisibility(View.GONE);
|
||||||
|
holder.item.divider.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initGenreLayout(ViewHolder holder, int position) {
|
||||||
|
Child song = grouped.get(position);
|
||||||
|
|
||||||
|
holder.item.downloadedItemTitleTextView.setText(MusicUtil.getReadableString(song.getGenre()));
|
||||||
|
holder.item.downloadedItemSubtitleTextView.setText(holder.itemView.getContext().getString(R.string.download_item_single_subtitle_formatter, countSong(Constants.DOWNLOAD_TYPE_GENRE, song.getGenre(), songs)));
|
||||||
|
|
||||||
|
holder.item.itemCoverImageView.setVisibility(View.GONE);
|
||||||
|
holder.item.downloadedItemMoreButton.setVisibility(View.GONE);
|
||||||
|
holder.item.divider.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initYearLayout(ViewHolder holder, int position) {
|
||||||
|
Child song = grouped.get(position);
|
||||||
|
|
||||||
|
holder.item.downloadedItemTitleTextView.setText(String.valueOf(song.getYear()));
|
||||||
|
holder.item.downloadedItemSubtitleTextView.setText(holder.itemView.getContext().getString(R.string.download_item_single_subtitle_formatter, countSong(Constants.DOWNLOAD_TYPE_YEAR, song.getYear().toString(), songs)));
|
||||||
|
|
||||||
|
holder.item.itemCoverImageView.setVisibility(View.GONE);
|
||||||
|
holder.item.downloadedItemMoreButton.setVisibility(View.GONE);
|
||||||
|
holder.item.divider.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
public class ViewHolder extends RecyclerView.ViewHolder {
|
public class ViewHolder extends RecyclerView.ViewHolder {
|
||||||
ItemHorizontalDownloadBinding item;
|
ItemHorizontalDownloadBinding item;
|
||||||
|
|
||||||
@@ -86,30 +247,62 @@ public class DownloadHorizontalAdapter extends RecyclerView.Adapter<DownloadHori
|
|||||||
|
|
||||||
this.item = item;
|
this.item = item;
|
||||||
|
|
||||||
item.downloadedSongTitleTextView.setSelected(true);
|
item.downloadedItemTitleTextView.setSelected(true);
|
||||||
item.downloadedSongArtistTextView.setSelected(true);
|
item.downloadedItemSubtitleTextView.setSelected(true);
|
||||||
|
|
||||||
itemView.setOnClickListener(v -> onClick());
|
itemView.setOnClickListener(v -> onClick());
|
||||||
itemView.setOnLongClickListener(v -> onLongClick());
|
itemView.setOnLongClickListener(v -> onLongClick());
|
||||||
|
|
||||||
item.downloadedSongMoreButton.setOnClickListener(v -> onLongClick());
|
item.downloadedItemMoreButton.setOnClickListener(v -> onLongClick());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onClick() {
|
public void onClick() {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putParcelableArrayList(Constants.TRACKS_OBJECT, new ArrayList<>(songs));
|
|
||||||
bundle.putInt(Constants.ITEM_POSITION, getBindingAdapterPosition());
|
|
||||||
|
|
||||||
click.onMediaClick(bundle);
|
switch (view) {
|
||||||
|
case Constants.DOWNLOAD_TYPE_TRACK:
|
||||||
|
bundle.putParcelableArrayList(Constants.TRACKS_OBJECT, new ArrayList<>(grouped));
|
||||||
|
bundle.putInt(Constants.ITEM_POSITION, getBindingAdapterPosition());
|
||||||
|
click.onMediaClick(bundle);
|
||||||
|
break;
|
||||||
|
case Constants.DOWNLOAD_TYPE_ALBUM:
|
||||||
|
bundle.putString(Constants.DOWNLOAD_TYPE_ALBUM, grouped.get(getBindingAdapterPosition()).getAlbumId());
|
||||||
|
click.onAlbumClick(bundle);
|
||||||
|
break;
|
||||||
|
case Constants.DOWNLOAD_TYPE_ARTIST:
|
||||||
|
bundle.putString(Constants.DOWNLOAD_TYPE_ARTIST, grouped.get(getBindingAdapterPosition()).getArtistId());
|
||||||
|
click.onArtistClick(bundle);
|
||||||
|
break;
|
||||||
|
case Constants.DOWNLOAD_TYPE_GENRE:
|
||||||
|
bundle.putString(Constants.DOWNLOAD_TYPE_GENRE, grouped.get(getBindingAdapterPosition()).getGenre());
|
||||||
|
click.onGenreClick(bundle);
|
||||||
|
break;
|
||||||
|
case Constants.DOWNLOAD_TYPE_YEAR:
|
||||||
|
bundle.putString(Constants.DOWNLOAD_TYPE_YEAR, grouped.get(getBindingAdapterPosition()).getYear().toString());
|
||||||
|
click.onYearClick(bundle);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean onLongClick() {
|
private boolean onLongClick() {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putParcelable(Constants.TRACK_OBJECT, songs.get(getBindingAdapterPosition()));
|
|
||||||
|
|
||||||
click.onMediaLongClick(bundle);
|
switch (view) {
|
||||||
|
case Constants.DOWNLOAD_TYPE_TRACK:
|
||||||
|
bundle.putParcelable(Constants.TRACK_OBJECT, grouped.get(getBindingAdapterPosition()));
|
||||||
|
click.onMediaLongClick(bundle);
|
||||||
|
return true;
|
||||||
|
case Constants.DOWNLOAD_TYPE_ALBUM:
|
||||||
|
bundle.putString(Constants.DOWNLOAD_TYPE_ALBUM, grouped.get(getBindingAdapterPosition()).getAlbumId());
|
||||||
|
click.onAlbumLongClick(bundle);
|
||||||
|
return true;
|
||||||
|
case Constants.DOWNLOAD_TYPE_ARTIST:
|
||||||
|
bundle.putString(Constants.DOWNLOAD_TYPE_ARTIST, grouped.get(getBindingAdapterPosition()).getArtistId());
|
||||||
|
click.onArtistLongClick(bundle);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.cappielloantonio.tempo.ui.adapter;
|
|||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
@@ -46,6 +47,9 @@ public class MusicDirectoryAdapter extends RecyclerView.Adapter<MusicDirectoryAd
|
|||||||
.from(holder.itemView.getContext(), child.getCoverArtId())
|
.from(holder.itemView.getContext(), child.getCoverArtId())
|
||||||
.build()
|
.build()
|
||||||
.into(holder.item.musicDirectoryCoverImageView);
|
.into(holder.item.musicDirectoryCoverImageView);
|
||||||
|
|
||||||
|
holder.item.musicDirectoryMoreButton.setVisibility(child.isDir() ? View.VISIBLE : View.GONE);
|
||||||
|
holder.item.musicDirectoryPlayButton.setVisibility(child.isDir() ? View.GONE : View.VISIBLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -76,7 +80,7 @@ public class MusicDirectoryAdapter extends RecyclerView.Adapter<MusicDirectoryAd
|
|||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
|
|
||||||
if (children.get(getBindingAdapterPosition()).isDir()) {
|
if (children.get(getBindingAdapterPosition()).isDir()) {
|
||||||
bundle.putParcelable(Constants.MUSIC_DIRECTORY_OBJECT, children.get(getBindingAdapterPosition()));
|
bundle.putString(Constants.MUSIC_DIRECTORY_ID, children.get(getBindingAdapterPosition()).getId());
|
||||||
click.onMusicDirectoryClick(bundle);
|
click.onMusicDirectoryClick(bundle);
|
||||||
} else {
|
} else {
|
||||||
bundle.putParcelableArrayList(Constants.TRACKS_OBJECT, new ArrayList<>(children));
|
bundle.putParcelableArrayList(Constants.TRACKS_OBJECT, new ArrayList<>(children));
|
||||||
|
|||||||
@@ -9,16 +9,17 @@ import androidx.media3.common.util.UnstableApi;
|
|||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import com.cappielloantonio.tempo.databinding.ItemLibraryMusicIndexBinding;
|
import com.cappielloantonio.tempo.databinding.ItemLibraryMusicIndexBinding;
|
||||||
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
import com.cappielloantonio.tempo.helper.recyclerview.FastScrollbar;
|
||||||
import com.cappielloantonio.tempo.interfaces.ClickCallback;
|
import com.cappielloantonio.tempo.interfaces.ClickCallback;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.Artist;
|
import com.cappielloantonio.tempo.subsonic.models.Artist;
|
||||||
import com.cappielloantonio.tempo.util.Constants;
|
import com.cappielloantonio.tempo.util.Constants;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
public class MusicIndexAdapter extends RecyclerView.Adapter<MusicIndexAdapter.ViewHolder> {
|
public class MusicIndexAdapter extends RecyclerView.Adapter<MusicIndexAdapter.ViewHolder> implements FastScrollbar.BubbleTextGetter {
|
||||||
private final ClickCallback click;
|
private final ClickCallback click;
|
||||||
|
|
||||||
private List<Artist> artists;
|
private List<Artist> artists;
|
||||||
@@ -41,10 +42,10 @@ public class MusicIndexAdapter extends RecyclerView.Adapter<MusicIndexAdapter.Vi
|
|||||||
|
|
||||||
holder.item.musicIndexTitleTextView.setText(artist.getName());
|
holder.item.musicIndexTitleTextView.setText(artist.getName());
|
||||||
|
|
||||||
CustomGlideRequest.Builder
|
/* CustomGlideRequest.Builder
|
||||||
.from(holder.itemView.getContext(), artist.getName())
|
.from(holder.itemView.getContext(), artist.getName())
|
||||||
.build()
|
.build()
|
||||||
.into(holder.item.musicIndexCoverImageView);
|
.into(holder.item.musicIndexCoverImageView); */
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -57,6 +58,11 @@ public class MusicIndexAdapter extends RecyclerView.Adapter<MusicIndexAdapter.Vi
|
|||||||
notifyDataSetChanged();
|
notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getTextToShowInBubble(int pos) {
|
||||||
|
return Character.toString(Objects.requireNonNull(artists.get(pos).getName().toUpperCase()).charAt(0));
|
||||||
|
}
|
||||||
|
|
||||||
public class ViewHolder extends RecyclerView.ViewHolder {
|
public class ViewHolder extends RecyclerView.ViewHolder {
|
||||||
ItemLibraryMusicIndexBinding item;
|
ItemLibraryMusicIndexBinding item;
|
||||||
|
|
||||||
@@ -73,7 +79,7 @@ public class MusicIndexAdapter extends RecyclerView.Adapter<MusicIndexAdapter.Vi
|
|||||||
|
|
||||||
public void onClick() {
|
public void onClick() {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putParcelable(Constants.MUSIC_INDEX_OBJECT, artists.get(getBindingAdapterPosition()));
|
bundle.putString(Constants.MUSIC_DIRECTORY_ID, artists.get(getBindingAdapterPosition()).getId());
|
||||||
click.onMusicIndexClick(bundle);
|
click.onMusicIndexClick(bundle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -128,14 +128,14 @@ public class PodcastChannelCatalogueAdapter extends RecyclerView.Adapter<Podcast
|
|||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putParcelable(Constants.PODCAST_CHANNEL_OBJECT, podcastChannels.get(getBindingAdapterPosition()));
|
bundle.putParcelable(Constants.PODCAST_CHANNEL_OBJECT, podcastChannels.get(getBindingAdapterPosition()));
|
||||||
|
|
||||||
click.onAlbumClick(bundle);
|
click.onPodcastChannelClick(bundle);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean onLongClick() {
|
private boolean onLongClick() {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putParcelable(Constants.PODCAST_CHANNEL_OBJECT, podcastChannels.get(getBindingAdapterPosition()));
|
bundle.putParcelable(Constants.PODCAST_CHANNEL_OBJECT, podcastChannels.get(getBindingAdapterPosition()));
|
||||||
|
|
||||||
click.onAlbumLongClick(bundle);
|
click.onPodcastChannelLongClick(bundle);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.cappielloantonio.tempo.ui.adapter;
|
|||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
@@ -18,11 +19,14 @@ import com.cappielloantonio.tempo.util.MusicUtil;
|
|||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class PodcastEpisodeAdapter extends RecyclerView.Adapter<PodcastEpisodeAdapter.ViewHolder> {
|
public class PodcastEpisodeAdapter extends RecyclerView.Adapter<PodcastEpisodeAdapter.ViewHolder> {
|
||||||
private final ClickCallback click;
|
private final ClickCallback click;
|
||||||
|
|
||||||
private List<PodcastEpisode> podcastEpisodes;
|
private List<PodcastEpisode> podcastEpisodes;
|
||||||
|
private List<PodcastEpisode> podcastEpisodesFull;
|
||||||
|
|
||||||
public PodcastEpisodeAdapter(ClickCallback click) {
|
public PodcastEpisodeAdapter(ClickCallback click) {
|
||||||
this.click = click;
|
this.click = click;
|
||||||
@@ -50,6 +54,10 @@ public class PodcastEpisodeAdapter extends RecyclerView.Adapter<PodcastEpisodeAd
|
|||||||
.from(holder.itemView.getContext(), podcastEpisode.getCoverArtId())
|
.from(holder.itemView.getContext(), podcastEpisode.getCoverArtId())
|
||||||
.build()
|
.build()
|
||||||
.into(holder.item.podcastCoverImageView);
|
.into(holder.item.podcastCoverImageView);
|
||||||
|
|
||||||
|
holder.item.podcastPlayButton.setEnabled(podcastEpisode.getStatus().equals("completed"));
|
||||||
|
holder.item.podcastMoreButton.setVisibility(podcastEpisode.getStatus().equals("completed") ? View.VISIBLE : View.GONE);
|
||||||
|
holder.item.podcastDownloadRequestButton.setVisibility(podcastEpisode.getStatus().equals("completed") ? View.GONE : View.VISIBLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -58,7 +66,8 @@ public class PodcastEpisodeAdapter extends RecyclerView.Adapter<PodcastEpisodeAd
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setItems(List<PodcastEpisode> podcastEpisodes) {
|
public void setItems(List<PodcastEpisode> podcastEpisodes) {
|
||||||
this.podcastEpisodes = podcastEpisodes;
|
this.podcastEpisodesFull = podcastEpisodes;
|
||||||
|
this.podcastEpisodes = podcastEpisodesFull.stream().filter(podcastEpisode -> Objects.equals(podcastEpisode.getStatus(), "completed")).collect(Collectors.toList());
|
||||||
notifyDataSetChanged();
|
notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,22 +94,57 @@ public class PodcastEpisodeAdapter extends RecyclerView.Adapter<PodcastEpisodeAd
|
|||||||
|
|
||||||
item.podcastPlayButton.setOnClickListener(v -> onClick());
|
item.podcastPlayButton.setOnClickListener(v -> onClick());
|
||||||
item.podcastMoreButton.setOnClickListener(v -> openMore());
|
item.podcastMoreButton.setOnClickListener(v -> openMore());
|
||||||
|
item.podcastDownloadRequestButton.setOnClickListener(v -> requestDownload());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onClick() {
|
public void onClick() {
|
||||||
Bundle bundle = new Bundle();
|
PodcastEpisode podcastEpisode = podcastEpisodes.get(getBindingAdapterPosition());
|
||||||
bundle.putParcelable(Constants.PODCAST_OBJECT, podcastEpisodes.get(getBindingAdapterPosition()));
|
|
||||||
|
|
||||||
click.onPodcastEpisodeClick(bundle);
|
if (podcastEpisode.getStatus().equals("completed")) {
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
bundle.putParcelable(Constants.PODCAST_OBJECT, podcastEpisodes.get(getBindingAdapterPosition()));
|
||||||
|
|
||||||
|
click.onPodcastEpisodeClick(bundle);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean openMore() {
|
private boolean openMore() {
|
||||||
Bundle bundle = new Bundle();
|
PodcastEpisode podcastEpisode = podcastEpisodes.get(getBindingAdapterPosition());
|
||||||
bundle.putParcelable(Constants.PODCAST_OBJECT, podcastEpisodes.get(getBindingAdapterPosition()));
|
|
||||||
|
|
||||||
click.onPodcastEpisodeLongClick(bundle);
|
if (podcastEpisode.getStatus().equals("completed")) {
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
bundle.putParcelable(Constants.PODCAST_OBJECT, podcastEpisodes.get(getBindingAdapterPosition()));
|
||||||
|
|
||||||
return true;
|
click.onPodcastEpisodeLongClick(bundle);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void requestDownload() {
|
||||||
|
PodcastEpisode podcastEpisode = podcastEpisodes.get(getBindingAdapterPosition());
|
||||||
|
|
||||||
|
if (!podcastEpisode.getStatus().equals("completed")) {
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
bundle.putParcelable(Constants.PODCAST_OBJECT, podcastEpisodes.get(getBindingAdapterPosition()));
|
||||||
|
|
||||||
|
click.onPodcastEpisodeAltClick(bundle);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void sort(String order) {
|
||||||
|
switch (order) {
|
||||||
|
case Constants.PODCAST_FILTER_BY_DOWNLOAD:
|
||||||
|
podcastEpisodes = podcastEpisodesFull.stream().filter(podcastEpisode -> Objects.equals(podcastEpisode.getStatus(), "completed")).collect(Collectors.toList());
|
||||||
|
break;
|
||||||
|
case Constants.PODCAST_FILTER_BY_ALL:
|
||||||
|
podcastEpisodes = podcastEpisodesFull;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -25,13 +25,15 @@ import java.util.List;
|
|||||||
@UnstableApi
|
@UnstableApi
|
||||||
public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAdapter.ViewHolder> {
|
public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAdapter.ViewHolder> {
|
||||||
private final ClickCallback click;
|
private final ClickCallback click;
|
||||||
private final boolean isCoverVisible;
|
private final boolean showCoverArt;
|
||||||
|
private final boolean showAlbum;
|
||||||
|
|
||||||
private List<Child> songs;
|
private List<Child> songs;
|
||||||
|
|
||||||
public SongHorizontalAdapter(ClickCallback click, boolean isCoverVisible) {
|
public SongHorizontalAdapter(ClickCallback click, boolean showCoverArt, boolean showAlbum) {
|
||||||
this.click = click;
|
this.click = click;
|
||||||
this.isCoverVisible = isCoverVisible;
|
this.showCoverArt = showCoverArt;
|
||||||
|
this.showAlbum = showAlbum;
|
||||||
this.songs = Collections.emptyList();
|
this.songs = Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,8 +49,8 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAd
|
|||||||
Child song = songs.get(position);
|
Child song = songs.get(position);
|
||||||
|
|
||||||
holder.item.searchResultSongTitleTextView.setText(MusicUtil.getReadableString(song.getTitle()));
|
holder.item.searchResultSongTitleTextView.setText(MusicUtil.getReadableString(song.getTitle()));
|
||||||
holder.item.searchResultSongSubtitleTextView.setText(holder.itemView.getContext().getString(R.string.song_subtitle_formatter, MusicUtil.getReadableString(song.getArtist()), MusicUtil.getReadableDurationString(song.getDuration() != null ? song.getDuration() : 0, false)));
|
holder.item.searchResultSongSubtitleTextView.setText(holder.itemView.getContext().getString(R.string.song_subtitle_formatter, MusicUtil.getReadableString(this.showAlbum ? song.getAlbum() : song.getArtist()), MusicUtil.getReadableDurationString(song.getDuration() != null ? song.getDuration() : 0, false)));
|
||||||
holder.item.trackNumberTextView.setText(String.valueOf(song.getTrack()));
|
holder.item.trackNumberTextView.setText(MusicUtil.getReadableTrackNumber(holder.itemView.getContext(), song.getTrack()));
|
||||||
|
|
||||||
if (DownloadUtil.getDownloadTracker(holder.itemView.getContext()).isDownloaded(song.getId())) {
|
if (DownloadUtil.getDownloadTracker(holder.itemView.getContext()).isDownloaded(song.getId())) {
|
||||||
holder.item.searchResultDowanloadIndicatorImageView.setVisibility(View.VISIBLE);
|
holder.item.searchResultDowanloadIndicatorImageView.setVisibility(View.VISIBLE);
|
||||||
@@ -56,16 +58,16 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAd
|
|||||||
holder.item.searchResultDowanloadIndicatorImageView.setVisibility(View.GONE);
|
holder.item.searchResultDowanloadIndicatorImageView.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isCoverVisible) CustomGlideRequest.Builder
|
if (showCoverArt) CustomGlideRequest.Builder
|
||||||
.from(holder.itemView.getContext(), song.getCoverArtId())
|
.from(holder.itemView.getContext(), song.getCoverArtId())
|
||||||
.build()
|
.build()
|
||||||
.into(holder.item.songCoverImageView);
|
.into(holder.item.songCoverImageView);
|
||||||
|
|
||||||
if (isCoverVisible) holder.item.trackNumberTextView.setVisibility(View.INVISIBLE);
|
if (showCoverArt) holder.item.trackNumberTextView.setVisibility(View.INVISIBLE);
|
||||||
|
|
||||||
if (!isCoverVisible) holder.item.songCoverImageView.setVisibility(View.INVISIBLE);
|
if (!showCoverArt) holder.item.songCoverImageView.setVisibility(View.INVISIBLE);
|
||||||
|
|
||||||
if (!isCoverVisible && (position > 0 && songs.get(position - 1) != null && songs.get(position - 1).getDiscNumber() != null && songs.get(position).getDiscNumber() != null && songs.get(position - 1).getDiscNumber() < songs.get(position).getDiscNumber())) {
|
if (!showCoverArt && (position > 0 && songs.get(position - 1) != null && songs.get(position - 1).getDiscNumber() != null && songs.get(position).getDiscNumber() != null && songs.get(position - 1).getDiscNumber() < songs.get(position).getDiscNumber())) {
|
||||||
holder.item.differentDiskDivider.setVisibility(View.VISIBLE);
|
holder.item.differentDiskDivider.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -76,10 +78,20 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAd
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setItems(List<Child> songs) {
|
public void setItems(List<Child> songs) {
|
||||||
this.songs = songs;
|
this.songs = songs != null ? songs : Collections.emptyList();
|
||||||
notifyDataSetChanged();
|
notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemViewType(int position) {
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getItemId(int position) {
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
public Child getItem(int id) {
|
public Child getItem(int id) {
|
||||||
return songs.get(id);
|
return songs.get(id);
|
||||||
}
|
}
|
||||||
@@ -104,7 +116,7 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAd
|
|||||||
public void onClick() {
|
public void onClick() {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putParcelableArrayList(Constants.TRACKS_OBJECT, new ArrayList<>(MusicUtil.limitPlayableMedia(songs, getBindingAdapterPosition())));
|
bundle.putParcelableArrayList(Constants.TRACKS_OBJECT, new ArrayList<>(MusicUtil.limitPlayableMedia(songs, getBindingAdapterPosition())));
|
||||||
bundle.putInt(Constants.ITEM_POSITION, MusicUtil.getPlayableMediaPosition(getBindingAdapterPosition()));
|
bundle.putInt(Constants.ITEM_POSITION, MusicUtil.getPlayableMediaPosition(songs, getBindingAdapterPosition()));
|
||||||
|
|
||||||
click.onMediaClick(bundle);
|
click.onMediaClick(bundle);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package com.cappielloantonio.tempo.ui.dialog;
|
||||||
|
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.app.Dialog;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.provider.Settings;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.OptIn;
|
||||||
|
import androidx.fragment.app.DialogFragment;
|
||||||
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
|
||||||
|
import com.cappielloantonio.tempo.R;
|
||||||
|
import com.cappielloantonio.tempo.databinding.DialogBatteryOptimizationBinding;
|
||||||
|
import com.cappielloantonio.tempo.util.Preferences;
|
||||||
|
|
||||||
|
@OptIn(markerClass = UnstableApi.class)
|
||||||
|
public class BatteryOptimizationDialog extends DialogFragment {
|
||||||
|
private static final String TAG = "BatteryOptimizationDialog";
|
||||||
|
|
||||||
|
private DialogBatteryOptimizationBinding bind;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||||
|
bind = DialogBatteryOptimizationBinding.inflate(getLayoutInflater());
|
||||||
|
|
||||||
|
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||||
|
|
||||||
|
builder.setView(bind.getRoot())
|
||||||
|
.setTitle(R.string.activity_battery_optimizations_title)
|
||||||
|
.setNeutralButton(R.string.battery_optimization_neutral_button, (dialog, id) -> Preferences.dontAskForOptimization())
|
||||||
|
.setNegativeButton(R.string.battery_optimization_negative_button, null)
|
||||||
|
.setPositiveButton(R.string.battery_optimization_positive_button, (dialog, id) -> openPowerSettings());
|
||||||
|
|
||||||
|
AlertDialog popup = builder.create();
|
||||||
|
|
||||||
|
popup.setCancelable(false);
|
||||||
|
popup.setCanceledOnTouchOutside(false);
|
||||||
|
|
||||||
|
return popup;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroyView() {
|
||||||
|
super.onDestroyView();
|
||||||
|
bind = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void openPowerSettings() {
|
||||||
|
Intent intent = new Intent();
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
intent.setAction(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS);
|
||||||
|
startActivity(intent);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package com.cappielloantonio.tempo.ui.dialog;
|
||||||
|
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.app.Dialog;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.widget.Button;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.OptIn;
|
||||||
|
import androidx.fragment.app.DialogFragment;
|
||||||
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
|
||||||
|
import com.cappielloantonio.tempo.R;
|
||||||
|
import com.cappielloantonio.tempo.databinding.DialogDeleteDownloadStorageBinding;
|
||||||
|
import com.cappielloantonio.tempo.interfaces.DialogClickCallback;
|
||||||
|
import com.cappielloantonio.tempo.util.DownloadUtil;
|
||||||
|
|
||||||
|
@OptIn(markerClass = UnstableApi.class)
|
||||||
|
public class DeleteDownloadStorageDialog extends DialogFragment {
|
||||||
|
private DialogDeleteDownloadStorageBinding bind;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||||
|
bind = DialogDeleteDownloadStorageBinding.inflate(getLayoutInflater());
|
||||||
|
|
||||||
|
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||||
|
|
||||||
|
builder.setView(bind.getRoot())
|
||||||
|
.setTitle(R.string.delete_download_storage_dialog_title)
|
||||||
|
.setPositiveButton(R.string.delete_download_storage_dialog_positive_button, null)
|
||||||
|
.setNegativeButton(R.string.delete_download_storage_dialog_negative_button, null);
|
||||||
|
|
||||||
|
return builder.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
setButtonAction();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroyView() {
|
||||||
|
super.onDestroyView();
|
||||||
|
bind = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setButtonAction() {
|
||||||
|
AlertDialog dialog = ((AlertDialog) getDialog());
|
||||||
|
|
||||||
|
if (dialog != null) {
|
||||||
|
Button positiveButton = dialog.getButton(Dialog.BUTTON_POSITIVE);
|
||||||
|
positiveButton.setOnClickListener(v -> {
|
||||||
|
DownloadUtil.getDownloadTracker(requireContext()).removeAll();
|
||||||
|
dialog.dismiss();
|
||||||
|
});
|
||||||
|
|
||||||
|
Button negativeButton = dialog.getButton(Dialog.BUTTON_NEGATIVE);
|
||||||
|
negativeButton.setOnClickListener(v -> {
|
||||||
|
dialog.dismiss();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
package com.cappielloantonio.tempo.ui.dialog;
|
||||||
|
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.app.Dialog;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.widget.Button;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.OptIn;
|
||||||
|
import androidx.fragment.app.DialogFragment;
|
||||||
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
|
||||||
|
import com.cappielloantonio.tempo.R;
|
||||||
|
import com.cappielloantonio.tempo.databinding.DialogDownloadStorageBinding;
|
||||||
|
import com.cappielloantonio.tempo.interfaces.DialogClickCallback;
|
||||||
|
import com.cappielloantonio.tempo.util.DownloadUtil;
|
||||||
|
import com.cappielloantonio.tempo.util.Preferences;
|
||||||
|
|
||||||
|
@OptIn(markerClass = UnstableApi.class)
|
||||||
|
public class DownloadStorageDialog extends DialogFragment {
|
||||||
|
private DialogDownloadStorageBinding bind;
|
||||||
|
|
||||||
|
private final DialogClickCallback dialogClickCallback;
|
||||||
|
|
||||||
|
public DownloadStorageDialog(DialogClickCallback dialogClickCallback) {
|
||||||
|
this.dialogClickCallback = dialogClickCallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||||
|
bind = DialogDownloadStorageBinding.inflate(getLayoutInflater());
|
||||||
|
|
||||||
|
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||||
|
|
||||||
|
builder.setView(bind.getRoot())
|
||||||
|
.setTitle(R.string.download_storage_dialog_title)
|
||||||
|
.setPositiveButton(R.string.download_storage_external_dialog_positive_button, null)
|
||||||
|
.setNegativeButton(R.string.download_storage_internal_dialog_negative_button, null);
|
||||||
|
|
||||||
|
return builder.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
setButtonAction();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroyView() {
|
||||||
|
super.onDestroyView();
|
||||||
|
bind = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setButtonAction() {
|
||||||
|
AlertDialog dialog = ((AlertDialog) getDialog());
|
||||||
|
|
||||||
|
if (dialog != null) {
|
||||||
|
Button positiveButton = dialog.getButton(Dialog.BUTTON_POSITIVE);
|
||||||
|
positiveButton.setOnClickListener(v -> {
|
||||||
|
int currentPreference = Preferences.getDownloadStoragePreference();
|
||||||
|
int newPreference = 1;
|
||||||
|
|
||||||
|
if (currentPreference != newPreference) {
|
||||||
|
Preferences.setDownloadStoragePreference(newPreference);
|
||||||
|
DownloadUtil.getDownloadTracker(requireContext()).removeAll();
|
||||||
|
dialogClickCallback.onPositiveClick();
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog.dismiss();
|
||||||
|
});
|
||||||
|
|
||||||
|
Button negativeButton = dialog.getButton(Dialog.BUTTON_NEGATIVE);
|
||||||
|
negativeButton.setOnClickListener(v -> {
|
||||||
|
int currentPreference = Preferences.getDownloadStoragePreference();
|
||||||
|
int newPreference = 0;
|
||||||
|
|
||||||
|
if (currentPreference != newPreference) {
|
||||||
|
Preferences.setDownloadStoragePreference(newPreference);
|
||||||
|
DownloadUtil.getDownloadTracker(requireContext()).removeAll();
|
||||||
|
dialogClickCallback.onNegativeClick();
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog.dismiss();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -67,7 +67,7 @@ public class PlaylistChooserDialog extends DialogFragment implements ClickCallba
|
|||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putParcelable(Constants.TRACK_OBJECT, playlistChooserViewModel.getSongToAdd());
|
bundle.putParcelable(Constants.TRACK_OBJECT, playlistChooserViewModel.getSongToAdd());
|
||||||
|
|
||||||
PlaylistEditorDialog dialog = new PlaylistEditorDialog();
|
PlaylistEditorDialog dialog = new PlaylistEditorDialog(null);
|
||||||
dialog.setArguments(bundle);
|
dialog.setArguments(bundle);
|
||||||
dialog.show(requireActivity().getSupportFragmentManager(), null);
|
dialog.show(requireActivity().getSupportFragmentManager(), null);
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import androidx.recyclerview.widget.RecyclerView;
|
|||||||
|
|
||||||
import com.cappielloantonio.tempo.R;
|
import com.cappielloantonio.tempo.R;
|
||||||
import com.cappielloantonio.tempo.databinding.DialogPlaylistEditorBinding;
|
import com.cappielloantonio.tempo.databinding.DialogPlaylistEditorBinding;
|
||||||
|
import com.cappielloantonio.tempo.interfaces.PlaylistCallback;
|
||||||
import com.cappielloantonio.tempo.ui.adapter.PlaylistDialogSongHorizontalAdapter;
|
import com.cappielloantonio.tempo.ui.adapter.PlaylistDialogSongHorizontalAdapter;
|
||||||
import com.cappielloantonio.tempo.util.Constants;
|
import com.cappielloantonio.tempo.util.Constants;
|
||||||
import com.cappielloantonio.tempo.util.MusicUtil;
|
import com.cappielloantonio.tempo.util.MusicUtil;
|
||||||
@@ -25,10 +26,15 @@ import java.util.Objects;
|
|||||||
public class PlaylistEditorDialog extends DialogFragment {
|
public class PlaylistEditorDialog extends DialogFragment {
|
||||||
private DialogPlaylistEditorBinding bind;
|
private DialogPlaylistEditorBinding bind;
|
||||||
private PlaylistEditorViewModel playlistEditorViewModel;
|
private PlaylistEditorViewModel playlistEditorViewModel;
|
||||||
|
private PlaylistCallback playlistCallback;
|
||||||
|
|
||||||
private String playlistName;
|
private String playlistName;
|
||||||
private PlaylistDialogSongHorizontalAdapter playlistDialogSongHorizontalAdapter;
|
private PlaylistDialogSongHorizontalAdapter playlistDialogSongHorizontalAdapter;
|
||||||
|
|
||||||
|
public PlaylistEditorDialog(PlaylistCallback playlistCallback) {
|
||||||
|
this.playlistCallback = playlistCallback;
|
||||||
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||||
@@ -85,13 +91,13 @@ public class PlaylistEditorDialog extends DialogFragment {
|
|||||||
playlistEditorViewModel.updatePlaylist(playlistName);
|
playlistEditorViewModel.updatePlaylist(playlistName);
|
||||||
}
|
}
|
||||||
|
|
||||||
Objects.requireNonNull(getDialog()).dismiss();
|
dialogDismiss();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
((AlertDialog) Objects.requireNonNull(getDialog())).getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener(v -> {
|
((AlertDialog) Objects.requireNonNull(getDialog())).getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener(v -> {
|
||||||
playlistEditorViewModel.deletePlaylist();
|
playlistEditorViewModel.deletePlaylist();
|
||||||
Objects.requireNonNull(getDialog()).dismiss();
|
dialogDismiss();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,7 +108,9 @@ public class PlaylistEditorDialog extends DialogFragment {
|
|||||||
playlistDialogSongHorizontalAdapter = new PlaylistDialogSongHorizontalAdapter();
|
playlistDialogSongHorizontalAdapter = new PlaylistDialogSongHorizontalAdapter();
|
||||||
bind.playlistSongRecyclerView.setAdapter(playlistDialogSongHorizontalAdapter);
|
bind.playlistSongRecyclerView.setAdapter(playlistDialogSongHorizontalAdapter);
|
||||||
|
|
||||||
playlistEditorViewModel.getPlaylistSongLiveList().observe(requireActivity(), songs -> playlistDialogSongHorizontalAdapter.setItems(songs));
|
playlistEditorViewModel.getPlaylistSongLiveList().observe(requireActivity(), songs -> {
|
||||||
|
if (songs != null) playlistDialogSongHorizontalAdapter.setItems(songs);
|
||||||
|
});
|
||||||
|
|
||||||
new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.LEFT) {
|
new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.LEFT) {
|
||||||
int originalPosition = -1;
|
int originalPosition = -1;
|
||||||
@@ -157,4 +165,9 @@ public class PlaylistEditorDialog extends DialogFragment {
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void dialogDismiss() {
|
||||||
|
Objects.requireNonNull(getDialog()).dismiss();
|
||||||
|
playlistCallback.onDismiss();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -116,6 +116,11 @@ public class ServerSignupDialog extends DialogFragment {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!server.matches("^https?://(.*)")) {
|
||||||
|
bind.serverTextView.setError(getString(R.string.error_server_prefix));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -181,7 +181,7 @@ public class AlbumPageFragment extends Fragment implements ClickCallback {
|
|||||||
bind.songRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
|
bind.songRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
|
||||||
bind.songRecyclerView.setHasFixedSize(true);
|
bind.songRecyclerView.setHasFixedSize(true);
|
||||||
|
|
||||||
songHorizontalAdapter = new SongHorizontalAdapter(this, false);
|
songHorizontalAdapter = new SongHorizontalAdapter(this, false, false);
|
||||||
bind.songRecyclerView.setAdapter(songHorizontalAdapter);
|
bind.songRecyclerView.setAdapter(songHorizontalAdapter);
|
||||||
|
|
||||||
albumPageViewModel.getAlbumSongLiveList().observe(getViewLifecycleOwner(), songs -> songHorizontalAdapter.setItems(songs));
|
albumPageViewModel.getAlbumSongLiveList().observe(getViewLifecycleOwner(), songs -> songHorizontalAdapter.setItems(songs));
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
|
|||||||
private void initTopSongsView() {
|
private void initTopSongsView() {
|
||||||
bind.mostStreamedSongRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
|
bind.mostStreamedSongRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
|
||||||
|
|
||||||
songHorizontalAdapter = new SongHorizontalAdapter(this, true);
|
songHorizontalAdapter = new SongHorizontalAdapter(this, true, true);
|
||||||
bind.mostStreamedSongRecyclerView.setAdapter(songHorizontalAdapter);
|
bind.mostStreamedSongRecyclerView.setAdapter(songHorizontalAdapter);
|
||||||
artistPageViewModel.getArtistTopSongList().observe(getViewLifecycleOwner(), songs -> {
|
artistPageViewModel.getArtistTopSongList().observe(getViewLifecycleOwner(), songs -> {
|
||||||
if (songs == null) {
|
if (songs == null) {
|
||||||
|
|||||||
@@ -3,33 +3,40 @@ package com.cappielloantonio.tempo.ui.fragment;
|
|||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuInflater;
|
||||||
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.core.view.ViewCompat;
|
import androidx.core.view.ViewCompat;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
import androidx.media3.session.MediaBrowser;
|
import androidx.media3.session.MediaBrowser;
|
||||||
import androidx.media3.session.SessionToken;
|
import androidx.media3.session.SessionToken;
|
||||||
|
import androidx.navigation.Navigation;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
|
||||||
import com.cappielloantonio.tempo.R;
|
import com.cappielloantonio.tempo.R;
|
||||||
import com.cappielloantonio.tempo.databinding.FragmentDirectoryBinding;
|
import com.cappielloantonio.tempo.databinding.FragmentDirectoryBinding;
|
||||||
import com.cappielloantonio.tempo.interfaces.ClickCallback;
|
import com.cappielloantonio.tempo.interfaces.ClickCallback;
|
||||||
|
import com.cappielloantonio.tempo.model.Download;
|
||||||
import com.cappielloantonio.tempo.service.MediaManager;
|
import com.cappielloantonio.tempo.service.MediaManager;
|
||||||
import com.cappielloantonio.tempo.service.MediaService;
|
import com.cappielloantonio.tempo.service.MediaService;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.Artist;
|
|
||||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||||
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
||||||
import com.cappielloantonio.tempo.ui.adapter.MusicDirectoryAdapter;
|
import com.cappielloantonio.tempo.ui.adapter.MusicDirectoryAdapter;
|
||||||
import com.cappielloantonio.tempo.util.Constants;
|
import com.cappielloantonio.tempo.util.Constants;
|
||||||
|
import com.cappielloantonio.tempo.util.DownloadUtil;
|
||||||
|
import com.cappielloantonio.tempo.util.MappingUtil;
|
||||||
import com.cappielloantonio.tempo.viewmodel.DirectoryViewModel;
|
import com.cappielloantonio.tempo.viewmodel.DirectoryViewModel;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
public class DirectoryFragment extends Fragment implements ClickCallback {
|
public class DirectoryFragment extends Fragment implements ClickCallback {
|
||||||
@@ -43,6 +50,18 @@ public class DirectoryFragment extends Fragment implements ClickCallback {
|
|||||||
|
|
||||||
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setHasOptionsMenu(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
|
||||||
|
super.onCreateOptionsMenu(menu, inflater);
|
||||||
|
inflater.inflate(R.menu.directory_page_menu, menu);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
activity = (MainActivity) getActivity();
|
activity = (MainActivity) getActivity();
|
||||||
@@ -52,9 +71,7 @@ public class DirectoryFragment extends Fragment implements ClickCallback {
|
|||||||
directoryViewModel = new ViewModelProvider(requireActivity()).get(DirectoryViewModel.class);
|
directoryViewModel = new ViewModelProvider(requireActivity()).get(DirectoryViewModel.class);
|
||||||
|
|
||||||
initAppBar();
|
initAppBar();
|
||||||
initButtons();
|
|
||||||
initDirectoryListView();
|
initDirectoryListView();
|
||||||
init();
|
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
@@ -77,15 +94,22 @@ public class DirectoryFragment extends Fragment implements ClickCallback {
|
|||||||
bind = null;
|
bind = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void init() {
|
@Override
|
||||||
Artist artist = getArguments().getParcelable(Constants.MUSIC_INDEX_OBJECT);
|
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||||
|
if (item.getItemId() == R.id.action_download_directory) {
|
||||||
if (artist != null) {
|
directoryViewModel.loadMusicDirectory(getArguments().getString(Constants.MUSIC_DIRECTORY_ID)).observe(getViewLifecycleOwner(), directory -> {
|
||||||
directoryViewModel.setMusicDirectoryId(artist.getId());
|
if (isVisible() && getActivity() != null) {
|
||||||
directoryViewModel.setMusicDirectoryName(artist.getName());
|
List<Child> songs = directory.getChildren().stream().filter(child -> !child.isDir()).collect(Collectors.toList());
|
||||||
|
DownloadUtil.getDownloadTracker(requireContext()).download(
|
||||||
|
MappingUtil.mapDownloads(songs),
|
||||||
|
songs.stream().map(Download::new).collect(Collectors.toList())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
directoryViewModel.loadMusicDirectory(getViewLifecycleOwner());
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initAppBar() {
|
private void initAppBar() {
|
||||||
@@ -96,39 +120,10 @@ public class DirectoryFragment extends Fragment implements ClickCallback {
|
|||||||
activity.getSupportActionBar().setDisplayShowHomeEnabled(true);
|
activity.getSupportActionBar().setDisplayShowHomeEnabled(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bind != null)
|
if (bind != null) {
|
||||||
bind.toolbar.setNavigationOnClickListener(v -> activity.navController.navigateUp());
|
bind.toolbar.setNavigationOnClickListener(v -> activity.navController.navigateUp());
|
||||||
|
bind.directoryBackImageView.setOnClickListener(v -> activity.navController.navigateUp());
|
||||||
if (bind != null)
|
}
|
||||||
bind.appBarLayout.addOnOffsetChangedListener((appBarLayout, verticalOffset) -> {
|
|
||||||
if ((bind.directoryInfoSector.getHeight() + verticalOffset) < (2 * ViewCompat.getMinimumHeight(bind.toolbar))) {
|
|
||||||
directoryViewModel.getDirectory().observe(getViewLifecycleOwner(), directory -> {
|
|
||||||
if (directory != null) {
|
|
||||||
bind.toolbar.setTitle(directory.getName());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
bind.toolbar.setTitle(R.string.empty_string);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
directoryViewModel.getDirectory().observe(getViewLifecycleOwner(), directory -> {
|
|
||||||
if (directory != null) {
|
|
||||||
bind.directoryTitleLabel.setText(directory.getName());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initButtons() {
|
|
||||||
directoryViewModel.getDirectory().observe(getViewLifecycleOwner(), directory -> {
|
|
||||||
if (directory != null && directory.getParentId() != null && !Objects.equals(directory.getParentId(), "-1")) {
|
|
||||||
bind.directoryBackImageView.setVisibility(View.VISIBLE);
|
|
||||||
} else {
|
|
||||||
bind.directoryBackImageView.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
bind.directoryBackImageView.setOnClickListener(v -> directoryViewModel.goBack());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initDirectoryListView() {
|
private void initDirectoryListView() {
|
||||||
@@ -137,13 +132,18 @@ public class DirectoryFragment extends Fragment implements ClickCallback {
|
|||||||
|
|
||||||
musicDirectoryAdapter = new MusicDirectoryAdapter(this);
|
musicDirectoryAdapter = new MusicDirectoryAdapter(this);
|
||||||
bind.directoryRecyclerView.setAdapter(musicDirectoryAdapter);
|
bind.directoryRecyclerView.setAdapter(musicDirectoryAdapter);
|
||||||
|
directoryViewModel.loadMusicDirectory(getArguments().getString(Constants.MUSIC_DIRECTORY_ID)).observe(getViewLifecycleOwner(), directory -> {
|
||||||
|
bind.appBarLayout.addOnOffsetChangedListener((appBarLayout, verticalOffset) -> {
|
||||||
|
if ((bind.directoryInfoSector.getHeight() + verticalOffset) < (2 * ViewCompat.getMinimumHeight(bind.toolbar))) {
|
||||||
|
bind.toolbar.setTitle(directory.getName());
|
||||||
|
} else {
|
||||||
|
bind.toolbar.setTitle(R.string.empty_string);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
directoryViewModel.getDirectory().observe(getViewLifecycleOwner(), directory -> {
|
bind.directoryTitleLabel.setText(directory.getName());
|
||||||
if (directory != null) {
|
|
||||||
musicDirectoryAdapter.setItems(directory.getChildren());
|
musicDirectoryAdapter.setItems(directory.getChildren());
|
||||||
} else {
|
|
||||||
musicDirectoryAdapter.setItems(Collections.emptyList());
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,11 +162,6 @@ public class DirectoryFragment extends Fragment implements ClickCallback {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onMusicDirectoryClick(Bundle bundle) {
|
public void onMusicDirectoryClick(Bundle bundle) {
|
||||||
Child child = bundle.getParcelable(Constants.MUSIC_DIRECTORY_OBJECT);
|
Navigation.findNavController(requireView()).navigate(R.id.directoryFragment, bundle);
|
||||||
|
|
||||||
if (child != null) {
|
|
||||||
directoryViewModel.setMusicDirectoryId(child.getId());
|
|
||||||
directoryViewModel.setMusicDirectoryName(child.getTitle());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,11 +3,9 @@ package com.cappielloantonio.tempo.ui.fragment;
|
|||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuInflater;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.PopupMenu;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
@@ -22,15 +20,19 @@ import androidx.recyclerview.widget.LinearLayoutManager;
|
|||||||
import com.cappielloantonio.tempo.R;
|
import com.cappielloantonio.tempo.R;
|
||||||
import com.cappielloantonio.tempo.databinding.FragmentDownloadBinding;
|
import com.cappielloantonio.tempo.databinding.FragmentDownloadBinding;
|
||||||
import com.cappielloantonio.tempo.interfaces.ClickCallback;
|
import com.cappielloantonio.tempo.interfaces.ClickCallback;
|
||||||
|
import com.cappielloantonio.tempo.model.DownloadStack;
|
||||||
import com.cappielloantonio.tempo.service.MediaManager;
|
import com.cappielloantonio.tempo.service.MediaManager;
|
||||||
import com.cappielloantonio.tempo.service.MediaService;
|
import com.cappielloantonio.tempo.service.MediaService;
|
||||||
|
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||||
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
||||||
import com.cappielloantonio.tempo.ui.adapter.DownloadHorizontalAdapter;
|
import com.cappielloantonio.tempo.ui.adapter.DownloadHorizontalAdapter;
|
||||||
import com.cappielloantonio.tempo.util.Constants;
|
import com.cappielloantonio.tempo.util.Constants;
|
||||||
|
import com.cappielloantonio.tempo.util.Preferences;
|
||||||
import com.cappielloantonio.tempo.viewmodel.DownloadViewModel;
|
import com.cappielloantonio.tempo.viewmodel.DownloadViewModel;
|
||||||
import com.google.android.gms.cast.framework.CastButtonFactory;
|
import com.google.android.material.appbar.MaterialToolbar;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@@ -43,18 +45,7 @@ public class DownloadFragment extends Fragment implements ClickCallback {
|
|||||||
|
|
||||||
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
||||||
|
|
||||||
@Override
|
private MaterialToolbar materialToolbar;
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setHasOptionsMenu(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
|
|
||||||
super.onCreateOptionsMenu(menu, inflater);
|
|
||||||
inflater.inflate(R.menu.main_page_menu, menu);
|
|
||||||
CastButtonFactory.setUpMediaRouteButton(requireContext(), menu, R.id.media_route_menu_item);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
@@ -73,7 +64,7 @@ public class DownloadFragment extends Fragment implements ClickCallback {
|
|||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
|
||||||
initAppBar();
|
initAppBar();
|
||||||
initDownloadedSongView();
|
initDownloadedView();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -97,29 +88,19 @@ public class DownloadFragment extends Fragment implements ClickCallback {
|
|||||||
bind = null;
|
bind = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
|
||||||
if (item.getItemId() == R.id.action_search) {
|
|
||||||
activity.navController.navigate(R.id.action_downloadFragment_to_searchFragment);
|
|
||||||
return true;
|
|
||||||
} else if (item.getItemId() == R.id.action_settings) {
|
|
||||||
activity.navController.navigate(R.id.action_downloadFragment_to_settingsFragment);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initAppBar() {
|
private void initAppBar() {
|
||||||
activity.setSupportActionBar(bind.toolbar);
|
materialToolbar = bind.getRoot().findViewById(R.id.toolbar);
|
||||||
Objects.requireNonNull(bind.toolbar.getOverflowIcon()).setTint(requireContext().getResources().getColor(R.color.titleTextColor, null));
|
|
||||||
|
activity.setSupportActionBar(materialToolbar);
|
||||||
|
Objects.requireNonNull(materialToolbar.getOverflowIcon()).setTint(requireContext().getResources().getColor(R.color.titleTextColor, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initDownloadedSongView() {
|
private void initDownloadedView() {
|
||||||
bind.downloadedTracksRecyclerView.setHasFixedSize(true);
|
bind.downloadedRecyclerView.setHasFixedSize(true);
|
||||||
|
|
||||||
downloadHorizontalAdapter = new DownloadHorizontalAdapter(this);
|
downloadHorizontalAdapter = new DownloadHorizontalAdapter(this);
|
||||||
bind.downloadedTracksRecyclerView.setAdapter(downloadHorizontalAdapter);
|
bind.downloadedRecyclerView.setAdapter(downloadHorizontalAdapter);
|
||||||
|
|
||||||
downloadViewModel.getDownloadedTracks(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), songs -> {
|
downloadViewModel.getDownloadedTracks(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), songs -> {
|
||||||
if (songs != null) {
|
if (songs != null) {
|
||||||
if (songs.isEmpty()) {
|
if (songs.isEmpty()) {
|
||||||
@@ -127,26 +108,92 @@ public class DownloadFragment extends Fragment implements ClickCallback {
|
|||||||
bind.emptyDownloadLayout.setVisibility(View.VISIBLE);
|
bind.emptyDownloadLayout.setVisibility(View.VISIBLE);
|
||||||
bind.fragmentDownloadNestedScrollView.setVisibility(View.GONE);
|
bind.fragmentDownloadNestedScrollView.setVisibility(View.GONE);
|
||||||
|
|
||||||
bind.downloadDownloadedTracksPlaceholder.placeholder.setVisibility(View.VISIBLE);
|
bind.downloadDownloadedPlaceholder.placeholder.setVisibility(View.VISIBLE);
|
||||||
bind.downloadDownloadedTracksSector.setVisibility(View.GONE);
|
bind.downloadDownloadedSector.setVisibility(View.GONE);
|
||||||
|
|
||||||
|
bind.downloadedGroupByImageView.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (bind != null) {
|
if (bind != null) {
|
||||||
bind.emptyDownloadLayout.setVisibility(View.GONE);
|
bind.emptyDownloadLayout.setVisibility(View.GONE);
|
||||||
bind.fragmentDownloadNestedScrollView.setVisibility(View.VISIBLE);
|
bind.fragmentDownloadNestedScrollView.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
bind.downloadDownloadedTracksPlaceholder.placeholder.setVisibility(View.GONE);
|
bind.downloadDownloadedPlaceholder.placeholder.setVisibility(View.GONE);
|
||||||
bind.downloadDownloadedTracksSector.setVisibility(View.VISIBLE);
|
bind.downloadDownloadedSector.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
bind.downloadedTracksRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
|
bind.downloadedGroupByImageView.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
downloadHorizontalAdapter.setItems(songs);
|
finishDownloadView(songs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bind != null) bind.loadingProgressBar.setVisibility(View.GONE);
|
if (bind != null) bind.loadingProgressBar.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
bind.downloadedGroupByImageView.setOnClickListener(view -> showPopupMenu(view, R.menu.download_popup_menu));
|
||||||
|
bind.downloadedGoBackImageView.setOnClickListener(view -> downloadViewModel.popViewStack());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void finishDownloadView(List<Child> songs) {
|
||||||
|
downloadViewModel.getViewStack().observe(getViewLifecycleOwner(), stack -> {
|
||||||
|
bind.downloadedRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
|
||||||
|
|
||||||
|
DownloadStack lastLevel = stack.get(stack.size() - 1);
|
||||||
|
|
||||||
|
switch (lastLevel.getId()) {
|
||||||
|
case Constants.DOWNLOAD_TYPE_TRACK:
|
||||||
|
downloadHorizontalAdapter.setItems(Constants.DOWNLOAD_TYPE_TRACK, lastLevel.getId(), lastLevel.getView(), songs);
|
||||||
|
break;
|
||||||
|
case Constants.DOWNLOAD_TYPE_ALBUM:
|
||||||
|
downloadHorizontalAdapter.setItems(Constants.DOWNLOAD_TYPE_TRACK, lastLevel.getId(), lastLevel.getView(), songs);
|
||||||
|
break;
|
||||||
|
case Constants.DOWNLOAD_TYPE_ARTIST:
|
||||||
|
downloadHorizontalAdapter.setItems(Constants.DOWNLOAD_TYPE_ALBUM, lastLevel.getId(), lastLevel.getView(), songs);
|
||||||
|
break;
|
||||||
|
case Constants.DOWNLOAD_TYPE_GENRE:
|
||||||
|
downloadHorizontalAdapter.setItems(Constants.DOWNLOAD_TYPE_TRACK, lastLevel.getId(), lastLevel.getView(), songs);
|
||||||
|
break;
|
||||||
|
case Constants.DOWNLOAD_TYPE_YEAR:
|
||||||
|
downloadHorizontalAdapter.setItems(Constants.DOWNLOAD_TYPE_TRACK, lastLevel.getId(), lastLevel.getView(), songs);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
bind.downloadedGoBackImageView.setVisibility(stack.size() > 1 ? View.VISIBLE : View.GONE);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showPopupMenu(View view, int menuResource) {
|
||||||
|
PopupMenu popup = new PopupMenu(requireContext(), view);
|
||||||
|
popup.getMenuInflater().inflate(menuResource, popup.getMenu());
|
||||||
|
|
||||||
|
popup.setOnMenuItemClickListener(menuItem -> {
|
||||||
|
if (menuItem.getItemId() == R.id.menu_download_group_by_track) {
|
||||||
|
downloadViewModel.initViewStack(new DownloadStack(Constants.DOWNLOAD_TYPE_TRACK, null));
|
||||||
|
Preferences.setDefaultDownloadViewType(Constants.DOWNLOAD_TYPE_TRACK);
|
||||||
|
return true;
|
||||||
|
} else if (menuItem.getItemId() == R.id.menu_download_group_by_album) {
|
||||||
|
downloadViewModel.initViewStack(new DownloadStack(Constants.DOWNLOAD_TYPE_ALBUM, null));
|
||||||
|
Preferences.setDefaultDownloadViewType(Constants.DOWNLOAD_TYPE_ALBUM);
|
||||||
|
return true;
|
||||||
|
} else if (menuItem.getItemId() == R.id.menu_download_group_by_artist) {
|
||||||
|
downloadViewModel.initViewStack(new DownloadStack(Constants.DOWNLOAD_TYPE_ARTIST, null));
|
||||||
|
Preferences.setDefaultDownloadViewType(Constants.DOWNLOAD_TYPE_ARTIST);
|
||||||
|
return true;
|
||||||
|
} else if (menuItem.getItemId() == R.id.menu_download_group_by_genre) {
|
||||||
|
downloadViewModel.initViewStack(new DownloadStack(Constants.DOWNLOAD_TYPE_GENRE, null));
|
||||||
|
Preferences.setDefaultDownloadViewType(Constants.DOWNLOAD_TYPE_GENRE);
|
||||||
|
return true;
|
||||||
|
} else if (menuItem.getItemId() == R.id.menu_download_group_by_year) {
|
||||||
|
downloadViewModel.initViewStack(new DownloadStack(Constants.DOWNLOAD_TYPE_YEAR, null));
|
||||||
|
Preferences.setDefaultDownloadViewType(Constants.DOWNLOAD_TYPE_YEAR);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
popup.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeMediaBrowser() {
|
private void initializeMediaBrowser() {
|
||||||
@@ -157,6 +204,26 @@ public class DownloadFragment extends Fragment implements ClickCallback {
|
|||||||
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
|
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onYearClick(Bundle bundle) {
|
||||||
|
downloadViewModel.pushViewStack(new DownloadStack(Constants.DOWNLOAD_TYPE_YEAR, bundle.getString(Constants.DOWNLOAD_TYPE_YEAR)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onGenreClick(Bundle bundle) {
|
||||||
|
downloadViewModel.pushViewStack(new DownloadStack(Constants.DOWNLOAD_TYPE_GENRE, bundle.getString(Constants.DOWNLOAD_TYPE_GENRE)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onArtistClick(Bundle bundle) {
|
||||||
|
downloadViewModel.pushViewStack(new DownloadStack(Constants.DOWNLOAD_TYPE_ARTIST, bundle.getString(Constants.DOWNLOAD_TYPE_ARTIST)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAlbumClick(Bundle bundle) {
|
||||||
|
downloadViewModel.pushViewStack(new DownloadStack(Constants.DOWNLOAD_TYPE_ALBUM, bundle.getString(Constants.DOWNLOAD_TYPE_ALBUM)));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onMediaClick(Bundle bundle) {
|
public void onMediaClick(Bundle bundle) {
|
||||||
MediaManager.startQueue(mediaBrowserListenableFuture, bundle.getParcelableArrayList(Constants.TRACKS_OBJECT), bundle.getInt(Constants.ITEM_POSITION));
|
MediaManager.startQueue(mediaBrowserListenableFuture, bundle.getParcelableArrayList(Constants.TRACKS_OBJECT), bundle.getInt(Constants.ITEM_POSITION));
|
||||||
|
|||||||
@@ -2,9 +2,6 @@ package com.cappielloantonio.tempo.ui.fragment;
|
|||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuInflater;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
@@ -18,7 +15,9 @@ import com.cappielloantonio.tempo.databinding.FragmentHomeBinding;
|
|||||||
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
||||||
import com.cappielloantonio.tempo.ui.fragment.pager.HomePager;
|
import com.cappielloantonio.tempo.ui.fragment.pager.HomePager;
|
||||||
import com.cappielloantonio.tempo.util.Preferences;
|
import com.cappielloantonio.tempo.util.Preferences;
|
||||||
import com.google.android.gms.cast.framework.CastButtonFactory;
|
import com.google.android.material.appbar.AppBarLayout;
|
||||||
|
import com.google.android.material.appbar.MaterialToolbar;
|
||||||
|
import com.google.android.material.tabs.TabLayout;
|
||||||
import com.google.android.material.tabs.TabLayoutMediator;
|
import com.google.android.material.tabs.TabLayoutMediator;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
@@ -30,18 +29,9 @@ public class HomeFragment extends Fragment {
|
|||||||
private FragmentHomeBinding bind;
|
private FragmentHomeBinding bind;
|
||||||
private MainActivity activity;
|
private MainActivity activity;
|
||||||
|
|
||||||
@Override
|
private MaterialToolbar materialToolbar;
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
private AppBarLayout appBarLayout;
|
||||||
super.onCreate(savedInstanceState);
|
private TabLayout tabLayout;
|
||||||
setHasOptionsMenu(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
|
|
||||||
super.onCreateOptionsMenu(menu, inflater);
|
|
||||||
inflater.inflate(R.menu.main_page_menu, menu);
|
|
||||||
CastButtonFactory.setUpMediaRouteButton(requireContext(), menu, R.id.media_route_menu_item);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
@@ -73,22 +63,18 @@ public class HomeFragment extends Fragment {
|
|||||||
bind = null;
|
bind = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
|
||||||
if (item.getItemId() == R.id.action_search) {
|
|
||||||
activity.navController.navigate(R.id.action_homeFragment_to_searchFragment);
|
|
||||||
return true;
|
|
||||||
} else if (item.getItemId() == R.id.action_settings) {
|
|
||||||
activity.navController.navigate(R.id.action_homeFragment_to_settingsFragment);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initAppBar() {
|
private void initAppBar() {
|
||||||
activity.setSupportActionBar(bind.toolbar);
|
appBarLayout = bind.getRoot().findViewById(R.id.toolbar_fragment);
|
||||||
Objects.requireNonNull(bind.toolbar.getOverflowIcon()).setTint(requireContext().getResources().getColor(R.color.titleTextColor, null));
|
materialToolbar = bind.getRoot().findViewById(R.id.toolbar);
|
||||||
|
|
||||||
|
activity.setSupportActionBar(materialToolbar);
|
||||||
|
Objects.requireNonNull(materialToolbar.getOverflowIcon()).setTint(requireContext().getResources().getColor(R.color.titleTextColor, null));
|
||||||
|
|
||||||
|
tabLayout = new TabLayout(requireContext());
|
||||||
|
tabLayout.setTabGravity(TabLayout.GRAVITY_FILL);
|
||||||
|
tabLayout.setTabMode(TabLayout.MODE_FIXED);
|
||||||
|
|
||||||
|
appBarLayout.addView(tabLayout);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initHomePager() {
|
private void initHomePager() {
|
||||||
@@ -106,13 +92,13 @@ public class HomeFragment extends Fragment {
|
|||||||
bind.homeViewPager.setOffscreenPageLimit(3);
|
bind.homeViewPager.setOffscreenPageLimit(3);
|
||||||
bind.homeViewPager.setUserInputEnabled(false);
|
bind.homeViewPager.setUserInputEnabled(false);
|
||||||
|
|
||||||
new TabLayoutMediator(bind.homeTabLayout, bind.homeViewPager,
|
new TabLayoutMediator(tabLayout, bind.homeViewPager,
|
||||||
(tab, position) -> {
|
(tab, position) -> {
|
||||||
tab.setText(pager.getPageTitle(position));
|
tab.setText(pager.getPageTitle(position));
|
||||||
// tab.setIcon(pager.getPageIcon(position));
|
// tab.setIcon(pager.getPageIcon(position));
|
||||||
}
|
}
|
||||||
).attach();
|
).attach();
|
||||||
|
|
||||||
bind.homeTabLayout.setVisibility(Preferences.isPodcastSectionVisible() || Preferences.isRadioSectionVisible() ? View.VISIBLE : View.GONE);
|
tabLayout.setVisibility(Preferences.isPodcastSectionVisible() || Preferences.isRadioSectionVisible() ? View.VISIBLE : View.GONE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -138,6 +138,15 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
bind.discoveryTextViewClickable.setOnClickListener(v -> {
|
||||||
|
homeViewModel.getRandomShuffleSample().observe(getViewLifecycleOwner(), songs -> {
|
||||||
|
if (songs.size() > 0) {
|
||||||
|
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
|
||||||
|
activity.setBottomSheetInPeek(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
bind.similarTracksTextViewRefreshable.setOnLongClickListener(v -> {
|
bind.similarTracksTextViewRefreshable.setOnLongClickListener(v -> {
|
||||||
homeViewModel.refreshSimilarSongSample(getViewLifecycleOwner());
|
homeViewModel.refreshSimilarSongSample(getViewLifecycleOwner());
|
||||||
return true;
|
return true;
|
||||||
@@ -383,7 +392,7 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
|||||||
private void initStarredTracksView() {
|
private void initStarredTracksView() {
|
||||||
bind.starredTracksRecyclerView.setHasFixedSize(true);
|
bind.starredTracksRecyclerView.setHasFixedSize(true);
|
||||||
|
|
||||||
starredSongAdapter = new SongHorizontalAdapter(this, true);
|
starredSongAdapter = new SongHorizontalAdapter(this, true, false);
|
||||||
bind.starredTracksRecyclerView.setAdapter(starredSongAdapter);
|
bind.starredTracksRecyclerView.setAdapter(starredSongAdapter);
|
||||||
homeViewModel.getStarredTracks(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), songs -> {
|
homeViewModel.getStarredTracks(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), songs -> {
|
||||||
if (songs == null) {
|
if (songs == null) {
|
||||||
@@ -609,25 +618,6 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void reorder() {
|
|
||||||
if (bind != null) {
|
|
||||||
// bind.homeLinearLayoutContainer.removeAllViews();
|
|
||||||
// bind.homeLinearLayoutContainer.addView(bind.homeDiscoverSector);
|
|
||||||
|
|
||||||
// bind.homeLinearLayoutContainer.addView(bind.homeSimilarTracksSector);
|
|
||||||
// bind.homeLinearLayoutContainer.addView(bind.homeRadioArtistSector);
|
|
||||||
// bind.homeLinearLayoutContainer.addView(bind.homeGridTracksSector);
|
|
||||||
// bind.homeLinearLayoutContainer.addView(bind.starredTracksSector);
|
|
||||||
// bind.homeLinearLayoutContainer.addView(bind.starredAlbumsSector);
|
|
||||||
// bind.homeLinearLayoutContainer.addView(bind.starredArtistsSector);
|
|
||||||
|
|
||||||
// bind.homeLinearLayoutContainer.addView(bind.homeRecentlyAddedAlbumsSector);
|
|
||||||
// bind.homeLinearLayoutContainer.addView(bind.homeFlashbackSector);
|
|
||||||
// bind.homeLinearLayoutContainer.addView(bind.homeMostPlayedAlbumsSector);
|
|
||||||
// bind.homeLinearLayoutContainer.addView(bind.homeRecentlyPlayedAlbumsSector);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initializeMediaBrowser() {
|
private void initializeMediaBrowser() {
|
||||||
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
|
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,6 +96,9 @@ public class IndexFragment extends Fragment implements ClickCallback {
|
|||||||
musicIndexAdapter.setItems(IndexUtil.getArtist(indexes));
|
musicIndexAdapter.setItems(IndexUtil.getArtist(indexes));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
bind.fastScrollbar.setRecyclerView(bind.indexRecyclerView);
|
||||||
|
bind.fastScrollbar.setViewsToUse(R.layout.layout_fast_scrollbar, R.id.fastscroller_bubble, R.id.fastscroller_handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
package com.cappielloantonio.tempo.ui.fragment;
|
package com.cappielloantonio.tempo.ui.fragment;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuInflater;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
@@ -21,6 +19,7 @@ import com.cappielloantonio.tempo.R;
|
|||||||
import com.cappielloantonio.tempo.databinding.FragmentLibraryBinding;
|
import com.cappielloantonio.tempo.databinding.FragmentLibraryBinding;
|
||||||
import com.cappielloantonio.tempo.helper.recyclerview.CustomLinearSnapHelper;
|
import com.cappielloantonio.tempo.helper.recyclerview.CustomLinearSnapHelper;
|
||||||
import com.cappielloantonio.tempo.interfaces.ClickCallback;
|
import com.cappielloantonio.tempo.interfaces.ClickCallback;
|
||||||
|
import com.cappielloantonio.tempo.interfaces.PlaylistCallback;
|
||||||
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
||||||
import com.cappielloantonio.tempo.ui.adapter.AlbumAdapter;
|
import com.cappielloantonio.tempo.ui.adapter.AlbumAdapter;
|
||||||
import com.cappielloantonio.tempo.ui.adapter.ArtistAdapter;
|
import com.cappielloantonio.tempo.ui.adapter.ArtistAdapter;
|
||||||
@@ -29,8 +28,9 @@ import com.cappielloantonio.tempo.ui.adapter.MusicFolderAdapter;
|
|||||||
import com.cappielloantonio.tempo.ui.adapter.PlaylistHorizontalAdapter;
|
import com.cappielloantonio.tempo.ui.adapter.PlaylistHorizontalAdapter;
|
||||||
import com.cappielloantonio.tempo.ui.dialog.PlaylistEditorDialog;
|
import com.cappielloantonio.tempo.ui.dialog.PlaylistEditorDialog;
|
||||||
import com.cappielloantonio.tempo.util.Constants;
|
import com.cappielloantonio.tempo.util.Constants;
|
||||||
|
import com.cappielloantonio.tempo.util.Preferences;
|
||||||
import com.cappielloantonio.tempo.viewmodel.LibraryViewModel;
|
import com.cappielloantonio.tempo.viewmodel.LibraryViewModel;
|
||||||
import com.google.android.gms.cast.framework.CastButtonFactory;
|
import com.google.android.material.appbar.MaterialToolbar;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
@@ -46,21 +46,9 @@ public class LibraryFragment extends Fragment implements ClickCallback {
|
|||||||
private AlbumAdapter albumAdapter;
|
private AlbumAdapter albumAdapter;
|
||||||
private ArtistAdapter artistAdapter;
|
private ArtistAdapter artistAdapter;
|
||||||
private GenreAdapter genreAdapter;
|
private GenreAdapter genreAdapter;
|
||||||
|
|
||||||
private PlaylistHorizontalAdapter playlistHorizontalAdapter;
|
private PlaylistHorizontalAdapter playlistHorizontalAdapter;
|
||||||
|
|
||||||
@Override
|
private MaterialToolbar materialToolbar;
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setHasOptionsMenu(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
|
|
||||||
super.onCreateOptionsMenu(menu, inflater);
|
|
||||||
inflater.inflate(R.menu.main_page_menu, menu);
|
|
||||||
CastButtonFactory.setUpMediaRouteButton(requireContext(), menu, R.id.media_route_menu_item);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
@@ -85,7 +73,7 @@ public class LibraryFragment extends Fragment implements ClickCallback {
|
|||||||
initAlbumView();
|
initAlbumView();
|
||||||
initArtistView();
|
initArtistView();
|
||||||
initGenreView();
|
initGenreView();
|
||||||
initPlaylistSlideView();
|
initPlaylistView();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -95,22 +83,15 @@ public class LibraryFragment extends Fragment implements ClickCallback {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onResume() {
|
||||||
super.onDestroyView();
|
super.onResume();
|
||||||
bind = null;
|
refreshPlaylistView();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
public void onDestroyView() {
|
||||||
if (item.getItemId() == R.id.action_search) {
|
super.onDestroyView();
|
||||||
activity.navController.navigate(R.id.action_libraryFragment_to_searchFragment);
|
bind = null;
|
||||||
return true;
|
|
||||||
} else if (item.getItemId() == R.id.action_settings) {
|
|
||||||
activity.navController.navigate(R.id.action_libraryFragment_to_settingsFragment);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void init() {
|
private void init() {
|
||||||
@@ -142,11 +123,18 @@ public class LibraryFragment extends Fragment implements ClickCallback {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void initAppBar() {
|
private void initAppBar() {
|
||||||
activity.setSupportActionBar(bind.toolbar);
|
materialToolbar = bind.getRoot().findViewById(R.id.toolbar);
|
||||||
Objects.requireNonNull(bind.toolbar.getOverflowIcon()).setTint(requireContext().getResources().getColor(R.color.titleTextColor, null));
|
|
||||||
|
activity.setSupportActionBar(materialToolbar);
|
||||||
|
Objects.requireNonNull(materialToolbar.getOverflowIcon()).setTint(requireContext().getResources().getColor(R.color.titleTextColor, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initMusicFolderView() {
|
private void initMusicFolderView() {
|
||||||
|
if (!Preferences.isMusicDirectorySectionVisible()) {
|
||||||
|
bind.libraryMusicFolderSector.setVisibility(View.GONE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
bind.musicFolderRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
|
bind.musicFolderRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
|
||||||
bind.musicFolderRecyclerView.setHasFixedSize(true);
|
bind.musicFolderRecyclerView.setHasFixedSize(true);
|
||||||
|
|
||||||
@@ -242,7 +230,7 @@ public class LibraryFragment extends Fragment implements ClickCallback {
|
|||||||
genreSnapHelper.attachToRecyclerView(bind.genreRecyclerView);
|
genreSnapHelper.attachToRecyclerView(bind.genreRecyclerView);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initPlaylistSlideView() {
|
private void initPlaylistView() {
|
||||||
bind.playlistRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
|
bind.playlistRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
|
||||||
bind.playlistRecyclerView.setHasFixedSize(true);
|
bind.playlistRecyclerView.setHasFixedSize(true);
|
||||||
|
|
||||||
@@ -264,6 +252,12 @@ public class LibraryFragment extends Fragment implements ClickCallback {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void refreshPlaylistView() {
|
||||||
|
final Handler handler = new Handler();
|
||||||
|
final Runnable runnable = () -> libraryViewModel.refreshPlaylistSample(getViewLifecycleOwner());
|
||||||
|
handler.postDelayed(runnable, 100);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAlbumClick(Bundle bundle) {
|
public void onAlbumClick(Bundle bundle) {
|
||||||
Navigation.findNavController(requireView()).navigate(R.id.albumPageFragment, bundle);
|
Navigation.findNavController(requireView()).navigate(R.id.albumPageFragment, bundle);
|
||||||
@@ -296,7 +290,13 @@ public class LibraryFragment extends Fragment implements ClickCallback {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPlaylistLongClick(Bundle bundle) {
|
public void onPlaylistLongClick(Bundle bundle) {
|
||||||
PlaylistEditorDialog dialog = new PlaylistEditorDialog();
|
PlaylistEditorDialog dialog = new PlaylistEditorDialog(new PlaylistCallback() {
|
||||||
|
@Override
|
||||||
|
public void onDismiss() {
|
||||||
|
refreshPlaylistView();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
dialog.setArguments(bundle);
|
dialog.setArguments(bundle);
|
||||||
dialog.show(activity.getSupportFragmentManager(), null);
|
dialog.show(activity.getSupportFragmentManager(), null);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
|||||||
import com.cappielloantonio.tempo.service.MediaManager;
|
import com.cappielloantonio.tempo.service.MediaManager;
|
||||||
import com.cappielloantonio.tempo.service.MediaService;
|
import com.cappielloantonio.tempo.service.MediaService;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.PlayQueue;
|
import com.cappielloantonio.tempo.subsonic.models.PlayQueue;
|
||||||
|
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
||||||
import com.cappielloantonio.tempo.ui.fragment.pager.PlayerControllerVerticalPager;
|
import com.cappielloantonio.tempo.ui.fragment.pager.PlayerControllerVerticalPager;
|
||||||
import com.cappielloantonio.tempo.util.Constants;
|
import com.cappielloantonio.tempo.util.Constants;
|
||||||
import com.cappielloantonio.tempo.util.MusicUtil;
|
import com.cappielloantonio.tempo.util.MusicUtil;
|
||||||
@@ -57,6 +58,7 @@ public class PlayerBottomSheetFragment extends Fragment {
|
|||||||
playerBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(PlayerBottomSheetViewModel.class);
|
playerBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(PlayerBottomSheetViewModel.class);
|
||||||
|
|
||||||
customizeBottomSheetBackground();
|
customizeBottomSheetBackground();
|
||||||
|
customizeBottomSheetAction();
|
||||||
initViewPager();
|
initViewPager();
|
||||||
setHeaderBookmarksButton();
|
setHeaderBookmarksButton();
|
||||||
|
|
||||||
@@ -84,7 +86,11 @@ public class PlayerBottomSheetFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void customizeBottomSheetBackground() {
|
private void customizeBottomSheetBackground() {
|
||||||
bind.playerHeaderLayout.getRoot().setBackgroundColor(SurfaceColors.getColorForElevation(requireContext(), 2));
|
bind.playerHeaderLayout.getRoot().setBackgroundColor(SurfaceColors.getColorForElevation(requireContext(), 8));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void customizeBottomSheetAction() {
|
||||||
|
bind.playerHeaderLayout.getRoot().setOnClickListener(view -> ((MainActivity) requireActivity()).expandBottomSheet());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initViewPager() {
|
private void initViewPager() {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import android.view.ViewGroup;
|
|||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
import android.widget.ToggleButton;
|
import android.widget.ToggleButton;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
@@ -51,6 +52,7 @@ public class PlayerControllerFragment extends Fragment {
|
|||||||
private Chip playerMediaExtension;
|
private Chip playerMediaExtension;
|
||||||
private TextView playerMediaBitrate;
|
private TextView playerMediaBitrate;
|
||||||
private ImageView playerMediaTranscodingIcon;
|
private ImageView playerMediaTranscodingIcon;
|
||||||
|
private ImageView playerMediaTranscodingPriorityIcon;
|
||||||
private Chip playerMediaTranscodedExtension;
|
private Chip playerMediaTranscodedExtension;
|
||||||
private TextView playerMediaTranscodedBitrate;
|
private TextView playerMediaTranscodedBitrate;
|
||||||
|
|
||||||
@@ -71,6 +73,7 @@ public class PlayerControllerFragment extends Fragment {
|
|||||||
initCoverLyricsSlideView();
|
initCoverLyricsSlideView();
|
||||||
initMediaListenable();
|
initMediaListenable();
|
||||||
initArtistLabelButton();
|
initArtistLabelButton();
|
||||||
|
initTranscodingInfo();
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
@@ -104,6 +107,7 @@ public class PlayerControllerFragment extends Fragment {
|
|||||||
playerMediaExtension = bind.getRoot().findViewById(R.id.player_media_extension);
|
playerMediaExtension = bind.getRoot().findViewById(R.id.player_media_extension);
|
||||||
playerMediaBitrate = bind.getRoot().findViewById(R.id.player_media_bitrate);
|
playerMediaBitrate = bind.getRoot().findViewById(R.id.player_media_bitrate);
|
||||||
playerMediaTranscodingIcon = bind.getRoot().findViewById(R.id.player_media_transcoding_audio);
|
playerMediaTranscodingIcon = bind.getRoot().findViewById(R.id.player_media_transcoding_audio);
|
||||||
|
playerMediaTranscodingPriorityIcon = bind.getRoot().findViewById(R.id.player_media_server_transcode_priority);
|
||||||
playerMediaTranscodedExtension = bind.getRoot().findViewById(R.id.player_media_transcoded_extension);
|
playerMediaTranscodedExtension = bind.getRoot().findViewById(R.id.player_media_transcoded_extension);
|
||||||
playerMediaTranscodedBitrate = bind.getRoot().findViewById(R.id.player_media_transcoded_bitrate);
|
playerMediaTranscodedBitrate = bind.getRoot().findViewById(R.id.player_media_transcoded_bitrate);
|
||||||
}
|
}
|
||||||
@@ -166,7 +170,6 @@ public class PlayerControllerFragment extends Fragment {
|
|||||||
playerMediaBitrate.setVisibility(View.GONE);
|
playerMediaBitrate.setVisibility(View.GONE);
|
||||||
} else {
|
} else {
|
||||||
playerMediaBitrate.setVisibility(View.VISIBLE);
|
playerMediaBitrate.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
playerMediaBitrate.setText(bitrate);
|
playerMediaBitrate.setText(bitrate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -177,19 +180,28 @@ public class PlayerControllerFragment extends Fragment {
|
|||||||
: "Original";
|
: "Original";
|
||||||
|
|
||||||
if (transcodingExtension.equals("raw") && transcodingBitrate.equals("Original")) {
|
if (transcodingExtension.equals("raw") && transcodingBitrate.equals("Original")) {
|
||||||
|
playerMediaTranscodingPriorityIcon.setVisibility(View.GONE);
|
||||||
playerMediaTranscodingIcon.setVisibility(View.GONE);
|
playerMediaTranscodingIcon.setVisibility(View.GONE);
|
||||||
playerMediaTranscodedBitrate.setVisibility(View.GONE);
|
playerMediaTranscodedBitrate.setVisibility(View.GONE);
|
||||||
playerMediaTranscodedExtension.setVisibility(View.GONE);
|
playerMediaTranscodedExtension.setVisibility(View.GONE);
|
||||||
} else {
|
} else {
|
||||||
|
playerMediaTranscodingPriorityIcon.setVisibility(View.GONE);
|
||||||
playerMediaTranscodingIcon.setVisibility(View.VISIBLE);
|
playerMediaTranscodingIcon.setVisibility(View.VISIBLE);
|
||||||
playerMediaTranscodedBitrate.setVisibility(View.VISIBLE);
|
playerMediaTranscodedBitrate.setVisibility(View.VISIBLE);
|
||||||
playerMediaTranscodedExtension.setVisibility(View.VISIBLE);
|
playerMediaTranscodedExtension.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
playerMediaTranscodedExtension.setText(transcodingExtension);
|
playerMediaTranscodedExtension.setText(transcodingExtension);
|
||||||
playerMediaTranscodedBitrate.setText(transcodingBitrate);
|
playerMediaTranscodedBitrate.setText(transcodingBitrate);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mediaMetadata.extras != null && mediaMetadata.extras.getString("uri", "").contains(Constants.DOWNLOAD_URI)) {
|
if (mediaMetadata.extras != null && mediaMetadata.extras.getString("uri", "").contains(Constants.DOWNLOAD_URI)) {
|
||||||
|
playerMediaTranscodingPriorityIcon.setVisibility(View.GONE);
|
||||||
|
playerMediaTranscodingIcon.setVisibility(View.GONE);
|
||||||
|
playerMediaTranscodedBitrate.setVisibility(View.GONE);
|
||||||
|
playerMediaTranscodedExtension.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Preferences.isServerPrioritized() && mediaMetadata.extras != null && !mediaMetadata.extras.getString("uri", "").contains(Constants.DOWNLOAD_URI)) {
|
||||||
|
playerMediaTranscodingPriorityIcon.setVisibility(View.VISIBLE);
|
||||||
playerMediaTranscodingIcon.setVisibility(View.GONE);
|
playerMediaTranscodingIcon.setVisibility(View.GONE);
|
||||||
playerMediaTranscodedBitrate.setVisibility(View.GONE);
|
playerMediaTranscodedBitrate.setVisibility(View.GONE);
|
||||||
playerMediaTranscodedExtension.setVisibility(View.GONE);
|
playerMediaTranscodedExtension.setVisibility(View.GONE);
|
||||||
@@ -293,6 +305,13 @@ public class PlayerControllerFragment extends Fragment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void initTranscodingInfo() {
|
||||||
|
playerMediaTranscodingPriorityIcon.setOnLongClickListener(view -> {
|
||||||
|
Toast.makeText(requireActivity(), R.string.settings_audio_transcode_priority_toast, Toast.LENGTH_SHORT).show();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void initPlaybackSpeedButton(MediaBrowser mediaBrowser) {
|
private void initPlaybackSpeedButton(MediaBrowser mediaBrowser) {
|
||||||
playbackSpeedButton.setOnClickListener(view -> {
|
playbackSpeedButton.setOnClickListener(view -> {
|
||||||
float currentSpeed = Preferences.getPlaybackSpeed();
|
float currentSpeed = Preferences.getPlaybackSpeed();
|
||||||
|
|||||||
@@ -178,7 +178,7 @@ public class PlaylistCatalogueFragment extends Fragment implements ClickCallback
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPlaylistLongClick(Bundle bundle) {
|
public void onPlaylistLongClick(Bundle bundle) {
|
||||||
PlaylistEditorDialog dialog = new PlaylistEditorDialog();
|
PlaylistEditorDialog dialog = new PlaylistEditorDialog(null);
|
||||||
dialog.setArguments(bundle);
|
dialog.setArguments(bundle);
|
||||||
dialog.show(activity.getSupportFragmentManager(), null);
|
dialog.show(activity.getSupportFragmentManager(), null);
|
||||||
hideKeyboard(requireView());
|
hideKeyboard(requireView());
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import androidx.media3.session.SessionToken;
|
|||||||
import androidx.navigation.Navigation;
|
import androidx.navigation.Navigation;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
|
||||||
|
import com.bumptech.glide.load.resource.bitmap.GranularRoundedCorners;
|
||||||
import com.cappielloantonio.tempo.R;
|
import com.cappielloantonio.tempo.R;
|
||||||
import com.cappielloantonio.tempo.databinding.FragmentPlaylistPageBinding;
|
import com.cappielloantonio.tempo.databinding.FragmentPlaylistPageBinding;
|
||||||
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
||||||
@@ -146,13 +147,13 @@ public class PlaylistPageFragment extends Fragment implements ClickCallback {
|
|||||||
playlistPageViewModel.getPlaylistSongLiveList().observe(getViewLifecycleOwner(), songs -> {
|
playlistPageViewModel.getPlaylistSongLiveList().observe(getViewLifecycleOwner(), songs -> {
|
||||||
if (bind != null) {
|
if (bind != null) {
|
||||||
bind.playlistPagePlayButton.setOnClickListener(v -> {
|
bind.playlistPagePlayButton.setOnClickListener(v -> {
|
||||||
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
|
MediaManager.startQueue(mediaBrowserListenableFuture, songs.subList(0, Math.min(100, songs.size())), 0);
|
||||||
activity.setBottomSheetInPeek(true);
|
activity.setBottomSheetInPeek(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
bind.playlistPageShuffleButton.setOnClickListener(v -> {
|
bind.playlistPageShuffleButton.setOnClickListener(v -> {
|
||||||
Collections.shuffle(songs);
|
Collections.shuffle(songs);
|
||||||
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
|
MediaManager.startQueue(mediaBrowserListenableFuture, songs.subList(0, Math.min(100, songs.size())), 0);
|
||||||
activity.setBottomSheetInPeek(true);
|
activity.setBottomSheetInPeek(true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -160,17 +161,46 @@ public class PlaylistPageFragment extends Fragment implements ClickCallback {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void initBackCover() {
|
private void initBackCover() {
|
||||||
CustomGlideRequest.Builder
|
playlistPageViewModel.getPlaylistSongLiveList().observe(requireActivity(), songs -> {
|
||||||
.from(requireContext(), playlistPageViewModel.getPlaylist().getCoverArtId())
|
if (bind != null && songs != null && !songs.isEmpty()) {
|
||||||
.build()
|
Collections.shuffle(songs);
|
||||||
.into(bind.playlistCoverImageView);
|
|
||||||
|
// Pic top-left
|
||||||
|
CustomGlideRequest.Builder
|
||||||
|
.from(requireContext(), songs.size() > 0 ? songs.get(0).getCoverArtId() : playlistPageViewModel.getPlaylist().getCoverArtId())
|
||||||
|
.build()
|
||||||
|
.transform(new GranularRoundedCorners(CustomGlideRequest.CORNER_RADIUS, 0, 0, 0))
|
||||||
|
.into(bind.playlistCoverImageViewTopLeft);
|
||||||
|
|
||||||
|
// Pic top-right
|
||||||
|
CustomGlideRequest.Builder
|
||||||
|
.from(requireContext(), songs.size() > 1 ? songs.get(1).getCoverArtId() : playlistPageViewModel.getPlaylist().getCoverArtId())
|
||||||
|
.build()
|
||||||
|
.transform(new GranularRoundedCorners(0, CustomGlideRequest.CORNER_RADIUS, 0, 0))
|
||||||
|
.into(bind.playlistCoverImageViewTopRight);
|
||||||
|
|
||||||
|
// Pic bottom-left
|
||||||
|
CustomGlideRequest.Builder
|
||||||
|
.from(requireContext(), songs.size() > 2 ? songs.get(2).getCoverArtId() : playlistPageViewModel.getPlaylist().getCoverArtId())
|
||||||
|
.build()
|
||||||
|
.transform(new GranularRoundedCorners(0, 0, 0, CustomGlideRequest.CORNER_RADIUS))
|
||||||
|
.into(bind.playlistCoverImageViewBottomLeft);
|
||||||
|
|
||||||
|
// Pic bottom-right
|
||||||
|
CustomGlideRequest.Builder
|
||||||
|
.from(requireContext(), songs.size() > 3 ? songs.get(3).getCoverArtId() : playlistPageViewModel.getPlaylist().getCoverArtId())
|
||||||
|
.build()
|
||||||
|
.transform(new GranularRoundedCorners(0, 0, CustomGlideRequest.CORNER_RADIUS, 0))
|
||||||
|
.into(bind.playlistCoverImageViewBottomRight);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initSongsView() {
|
private void initSongsView() {
|
||||||
bind.songRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
|
bind.songRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
|
||||||
bind.songRecyclerView.setHasFixedSize(true);
|
bind.songRecyclerView.setHasFixedSize(true);
|
||||||
|
|
||||||
songHorizontalAdapter = new SongHorizontalAdapter(this, true);
|
songHorizontalAdapter = new SongHorizontalAdapter(this, true, false);
|
||||||
bind.songRecyclerView.setAdapter(songHorizontalAdapter);
|
bind.songRecyclerView.setAdapter(songHorizontalAdapter);
|
||||||
|
|
||||||
playlistPageViewModel.getPlaylistSongLiveList().observe(getViewLifecycleOwner(), songs -> songHorizontalAdapter.setItems(songs));
|
playlistPageViewModel.getPlaylistSongLiveList().observe(getViewLifecycleOwner(), songs -> songHorizontalAdapter.setItems(songs));
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import android.os.Bundle;
|
|||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.PopupMenu;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
@@ -17,7 +18,6 @@ import androidx.recyclerview.widget.LinearLayoutManager;
|
|||||||
|
|
||||||
import com.cappielloantonio.tempo.R;
|
import com.cappielloantonio.tempo.R;
|
||||||
import com.cappielloantonio.tempo.databinding.FragmentPodcastChannelPageBinding;
|
import com.cappielloantonio.tempo.databinding.FragmentPodcastChannelPageBinding;
|
||||||
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
|
||||||
import com.cappielloantonio.tempo.interfaces.ClickCallback;
|
import com.cappielloantonio.tempo.interfaces.ClickCallback;
|
||||||
import com.cappielloantonio.tempo.service.MediaManager;
|
import com.cappielloantonio.tempo.service.MediaManager;
|
||||||
import com.cappielloantonio.tempo.service.MediaService;
|
import com.cappielloantonio.tempo.service.MediaService;
|
||||||
@@ -28,11 +28,10 @@ import com.cappielloantonio.tempo.util.Constants;
|
|||||||
import com.cappielloantonio.tempo.util.MusicUtil;
|
import com.cappielloantonio.tempo.util.MusicUtil;
|
||||||
import com.cappielloantonio.tempo.util.UIUtil;
|
import com.cappielloantonio.tempo.util.UIUtil;
|
||||||
import com.cappielloantonio.tempo.viewmodel.PodcastChannelPageViewModel;
|
import com.cappielloantonio.tempo.viewmodel.PodcastChannelPageViewModel;
|
||||||
|
import com.google.android.material.snackbar.Snackbar;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
public class PodcastChannelPageFragment extends Fragment implements ClickCallback {
|
public class PodcastChannelPageFragment extends Fragment implements ClickCallback {
|
||||||
@@ -84,28 +83,26 @@ public class PodcastChannelPageFragment extends Fragment implements ClickCallbac
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void initAppBar() {
|
private void initAppBar() {
|
||||||
activity.setSupportActionBar(bind.animToolbar);
|
activity.setSupportActionBar(bind.toolbar);
|
||||||
if (activity.getSupportActionBar() != null)
|
|
||||||
activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
|
||||||
|
|
||||||
bind.collapsingToolbar.setTitle(MusicUtil.getReadableString(podcastChannelPageViewModel.getPodcastChannel().getTitle()));
|
if (activity.getSupportActionBar() != null) {
|
||||||
bind.animToolbar.setNavigationOnClickListener(v -> activity.navController.navigateUp());
|
activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
bind.collapsingToolbar.setExpandedTitleColor(getResources().getColor(R.color.white, null));
|
activity.getSupportActionBar().setDisplayShowHomeEnabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
bind.toolbar.setTitle(MusicUtil.getReadableString(podcastChannelPageViewModel.getPodcastChannel().getTitle()));
|
||||||
|
bind.toolbar.setNavigationOnClickListener(v -> activity.navController.navigateUp());
|
||||||
|
bind.toolbar.setTitle(MusicUtil.getReadableString(podcastChannelPageViewModel.getPodcastChannel().getTitle()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initPodcastChannelInfo() {
|
private void initPodcastChannelInfo() {
|
||||||
String normalizePodcastChannelDescription = MusicUtil.forceReadableString(podcastChannelPageViewModel.getPodcastChannel().getDescription());
|
String normalizePodcastChannelDescription = MusicUtil.forceReadableString(podcastChannelPageViewModel.getPodcastChannel().getDescription());
|
||||||
|
|
||||||
if (bind != null)
|
if (bind != null) {
|
||||||
bind.podcastChannelDescriptionTextView.setVisibility(!normalizePodcastChannelDescription.trim().isEmpty() ? View.VISIBLE : View.GONE);
|
bind.podcastChannelDescriptionTextView.setVisibility(!normalizePodcastChannelDescription.trim().isEmpty() ? View.VISIBLE : View.GONE);
|
||||||
|
|
||||||
if (getContext() != null && bind != null) CustomGlideRequest.Builder
|
|
||||||
.from(requireContext(), podcastChannelPageViewModel.getPodcastChannel().getCoverArtId())
|
|
||||||
.build()
|
|
||||||
.into(bind.podcastChannelBackdropImageView);
|
|
||||||
|
|
||||||
if (bind != null)
|
|
||||||
bind.podcastChannelDescriptionTextView.setText(normalizePodcastChannelDescription);
|
bind.podcastChannelDescriptionTextView.setText(normalizePodcastChannelDescription);
|
||||||
|
bind.podcastEpisodesFilterImageView.setOnClickListener(view -> showPopupMenu(view, R.menu.filter_podcast_episode_popup_menu));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initPodcastChannelEpisodesView() {
|
private void initPodcastChannelEpisodesView() {
|
||||||
@@ -116,22 +113,21 @@ public class PodcastChannelPageFragment extends Fragment implements ClickCallbac
|
|||||||
bind.podcastEpisodesRecyclerView.setAdapter(podcastEpisodeAdapter);
|
bind.podcastEpisodesRecyclerView.setAdapter(podcastEpisodeAdapter);
|
||||||
podcastChannelPageViewModel.getPodcastChannelEpisodes().observe(getViewLifecycleOwner(), channels -> {
|
podcastChannelPageViewModel.getPodcastChannelEpisodes().observe(getViewLifecycleOwner(), channels -> {
|
||||||
if (channels == null) {
|
if (channels == null) {
|
||||||
if (bind != null)
|
if (bind != null) {
|
||||||
bind.podcastChannelPageEpisodesPlaceholder.placeholder.setVisibility(View.VISIBLE);
|
bind.podcastEpisodesRecyclerView.setVisibility(View.GONE);
|
||||||
if (bind != null) bind.podcastChannelPageEpisodesSector.setVisibility(View.GONE);
|
}
|
||||||
} else {
|
} else {
|
||||||
if (bind != null)
|
if (bind != null) {
|
||||||
bind.podcastChannelPageEpisodesPlaceholder.placeholder.setVisibility(View.GONE);
|
bind.podcastEpisodesRecyclerView.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
if (!channels.isEmpty() && channels.get(0) != null && channels.get(0).getEpisodes() != null) {
|
if (!channels.isEmpty() && channels.get(0) != null && channels.get(0).getEpisodes() != null) {
|
||||||
List<PodcastEpisode> availableEpisode = channels.get(0).getEpisodes().stream().filter(podcastEpisode -> Objects.equals(podcastEpisode.getStatus(), "completed")).collect(Collectors.toList());
|
List<PodcastEpisode> availableEpisode = channels.get(0).getEpisodes();
|
||||||
|
|
||||||
if (bind != null) {
|
if (bind != null && availableEpisode != null) {
|
||||||
bind.podcastEpisodesRecyclerView.setVisibility(availableEpisode.isEmpty() ? View.GONE : View.VISIBLE);
|
bind.podcastEpisodesRecyclerView.setVisibility(availableEpisode.isEmpty() ? View.GONE : View.VISIBLE);
|
||||||
bind.podcastEpisodesAvailabilityTextView.setVisibility(availableEpisode.isEmpty() ? View.VISIBLE : View.GONE);
|
podcastEpisodeAdapter.setItems(availableEpisode);
|
||||||
}
|
}
|
||||||
|
|
||||||
podcastEpisodeAdapter.setItems(availableEpisode);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -145,6 +141,25 @@ public class PodcastChannelPageFragment extends Fragment implements ClickCallbac
|
|||||||
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
|
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void showPopupMenu(View view, int menuResource) {
|
||||||
|
PopupMenu popup = new PopupMenu(requireContext(), view);
|
||||||
|
popup.getMenuInflater().inflate(menuResource, popup.getMenu());
|
||||||
|
|
||||||
|
popup.setOnMenuItemClickListener(menuItem -> {
|
||||||
|
if (menuItem.getItemId() == R.id.menu_podcast_filter_download) {
|
||||||
|
podcastEpisodeAdapter.sort(Constants.PODCAST_FILTER_BY_DOWNLOAD);
|
||||||
|
return true;
|
||||||
|
} else if (menuItem.getItemId() == R.id.menu_podcast_filter_all) {
|
||||||
|
podcastEpisodeAdapter.sort(Constants.PODCAST_FILTER_BY_ALL);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
popup.show();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPodcastEpisodeClick(Bundle bundle) {
|
public void onPodcastEpisodeClick(Bundle bundle) {
|
||||||
MediaManager.startPodcast(mediaBrowserListenableFuture, bundle.getParcelable(Constants.PODCAST_OBJECT));
|
MediaManager.startPodcast(mediaBrowserListenableFuture, bundle.getParcelable(Constants.PODCAST_OBJECT));
|
||||||
@@ -155,4 +170,14 @@ public class PodcastChannelPageFragment extends Fragment implements ClickCallbac
|
|||||||
public void onPodcastEpisodeLongClick(Bundle bundle) {
|
public void onPodcastEpisodeLongClick(Bundle bundle) {
|
||||||
Navigation.findNavController(requireView()).navigate(R.id.podcastEpisodeBottomSheetDialog, bundle);
|
Navigation.findNavController(requireView()).navigate(R.id.podcastEpisodeBottomSheetDialog, bundle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPodcastEpisodeAltClick(Bundle bundle) {
|
||||||
|
PodcastEpisode episode = bundle.getParcelable(Constants.PODCAST_OBJECT);
|
||||||
|
podcastChannelPageViewModel.requestPodcastEpisodeDownload(episode);
|
||||||
|
|
||||||
|
Snackbar.make(requireView(), R.string.podcast_episode_download_request_snackbar, Snackbar.LENGTH_SHORT)
|
||||||
|
.setAnchorView(activity.bind.bottomNavigation)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -2,9 +2,15 @@ package com.cappielloantonio.tempo.ui.fragment;
|
|||||||
|
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.text.Editable;
|
||||||
|
import android.text.TextWatcher;
|
||||||
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
import android.view.inputmethod.EditorInfo;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
@@ -28,17 +34,15 @@ import com.cappielloantonio.tempo.ui.adapter.AlbumAdapter;
|
|||||||
import com.cappielloantonio.tempo.ui.adapter.ArtistAdapter;
|
import com.cappielloantonio.tempo.ui.adapter.ArtistAdapter;
|
||||||
import com.cappielloantonio.tempo.ui.adapter.SongHorizontalAdapter;
|
import com.cappielloantonio.tempo.ui.adapter.SongHorizontalAdapter;
|
||||||
import com.cappielloantonio.tempo.util.Constants;
|
import com.cappielloantonio.tempo.util.Constants;
|
||||||
import com.cappielloantonio.tempo.util.MusicUtil;
|
|
||||||
import com.cappielloantonio.tempo.viewmodel.SearchViewModel;
|
import com.cappielloantonio.tempo.viewmodel.SearchViewModel;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
import com.paulrybitskyi.persistentsearchview.adapters.model.SuggestionItem;
|
|
||||||
import com.paulrybitskyi.persistentsearchview.listeners.OnSuggestionChangeListener;
|
|
||||||
import com.paulrybitskyi.persistentsearchview.utils.SuggestionCreationUtil;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
public class SearchFragment extends Fragment implements ClickCallback {
|
public class SearchFragment extends Fragment implements ClickCallback {
|
||||||
|
private static final String TAG = "SearchFragment";
|
||||||
|
|
||||||
private FragmentSearchBinding bind;
|
private FragmentSearchBinding bind;
|
||||||
private MainActivity activity;
|
private MainActivity activity;
|
||||||
private SearchViewModel searchViewModel;
|
private SearchViewModel searchViewModel;
|
||||||
@@ -60,6 +64,7 @@ public class SearchFragment extends Fragment implements ClickCallback {
|
|||||||
|
|
||||||
initSearchResultView();
|
initSearchResultView();
|
||||||
initSearchView();
|
initSearchView();
|
||||||
|
inputFocus();
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
@@ -70,12 +75,6 @@ public class SearchFragment extends Fragment implements ClickCallback {
|
|||||||
initializeMediaBrowser();
|
initializeMediaBrowser();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
inputFocus();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStop() {
|
public void onStop() {
|
||||||
releaseMediaBrowser();
|
releaseMediaBrowser();
|
||||||
@@ -113,74 +112,105 @@ public class SearchFragment extends Fragment implements ClickCallback {
|
|||||||
bind.searchResultTracksRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
|
bind.searchResultTracksRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
|
||||||
bind.searchResultTracksRecyclerView.setHasFixedSize(true);
|
bind.searchResultTracksRecyclerView.setHasFixedSize(true);
|
||||||
|
|
||||||
songHorizontalAdapter = new SongHorizontalAdapter(this, true);
|
songHorizontalAdapter = new SongHorizontalAdapter(this, true, false);
|
||||||
bind.searchResultTracksRecyclerView.setAdapter(songHorizontalAdapter);
|
bind.searchResultTracksRecyclerView.setAdapter(songHorizontalAdapter);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initSearchView() {
|
private void initSearchView() {
|
||||||
if (isQueryValid(searchViewModel.getQuery())) {
|
setRecentSuggestions();
|
||||||
search(searchViewModel.getQuery());
|
|
||||||
}
|
|
||||||
|
|
||||||
bind.persistentSearchView.setInputQuery(searchViewModel.getQuery());
|
bind.searchView
|
||||||
setSuggestions();
|
.getEditText()
|
||||||
|
.setOnEditorActionListener((textView, actionId, keyEvent) -> {
|
||||||
|
String query = bind.searchView.getText().toString();
|
||||||
|
|
||||||
bind.persistentSearchView.setOnSearchQueryChangeListener((searchView, oldQuery, newQuery) -> {
|
if (isQueryValid(query)) {
|
||||||
if (!newQuery.trim().equals("") && newQuery.trim().length() > 1) {
|
search(query);
|
||||||
searchViewModel.getSearchSuggestion(newQuery).observe(getViewLifecycleOwner(), suggestions -> searchView.setSuggestions(SuggestionCreationUtil.asRegularSearchSuggestions(MusicUtil.getReadableStrings(suggestions)), false));
|
return true;
|
||||||
} else {
|
}
|
||||||
setSuggestions();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
bind.persistentSearchView.setOnSuggestionChangeListener(new OnSuggestionChangeListener() {
|
return false;
|
||||||
@Override
|
});
|
||||||
public void onSuggestionPicked(SuggestionItem suggestion) {
|
|
||||||
search(suggestion.getItemModel().getText());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
bind.searchView
|
||||||
public void onSuggestionRemoved(SuggestionItem suggestion) {
|
.getEditText()
|
||||||
}
|
.addTextChangedListener(new TextWatcher() {
|
||||||
});
|
@Override
|
||||||
|
public void beforeTextChanged(CharSequence charSequence, int start, int count, int after) {
|
||||||
|
|
||||||
bind.persistentSearchView.setOnSearchConfirmedListener((searchView, query) -> {
|
}
|
||||||
if (isQueryValid(query)) {
|
|
||||||
searchView.collapse();
|
|
||||||
search(query);
|
|
||||||
} else {
|
|
||||||
Toast.makeText(requireContext(), getString(R.string.search_info_minimum_characters), Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
bind.persistentSearchView.setOnSuggestionChangeListener(new OnSuggestionChangeListener() {
|
@Override
|
||||||
@Override
|
public void onTextChanged(CharSequence charSequence, int start, int before, int count) {
|
||||||
public void onSuggestionPicked(SuggestionItem suggestion) {
|
if (start + count > 1) {
|
||||||
search(suggestion.getItemModel().getText());
|
setSearchSuggestions(charSequence.toString());
|
||||||
}
|
} else {
|
||||||
|
setRecentSuggestions();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSuggestionRemoved(SuggestionItem suggestion) {
|
public void afterTextChanged(Editable editable) {
|
||||||
searchViewModel.deleteRecentSearch(suggestion.getItemModel().getText());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
bind.persistentSearchView.setOnClearInputBtnClickListener(v -> searchViewModel.setQuery(""));
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setSuggestions() {
|
public void setRecentSuggestions() {
|
||||||
bind.persistentSearchView.setSuggestions(SuggestionCreationUtil.asRecentSearchSuggestions(searchViewModel.getRecentSearchSuggestion()), false);
|
bind.searchViewSuggestionContainer.removeAllViews();
|
||||||
|
|
||||||
|
for (String suggestion : searchViewModel.getRecentSearchSuggestion()) {
|
||||||
|
View view = LayoutInflater.from(bind.searchViewSuggestionContainer.getContext()).inflate(R.layout.item_search_suggestion, bind.searchViewSuggestionContainer, false);
|
||||||
|
|
||||||
|
ImageView leadingImageView = view.findViewById(R.id.search_suggestion_icon);
|
||||||
|
TextView titleView = view.findViewById(R.id.search_suggestion_title);
|
||||||
|
ImageView tailingImageView = view.findViewById(R.id.search_suggestion_delete_icon);
|
||||||
|
|
||||||
|
leadingImageView.setImageDrawable(getResources().getDrawable(R.drawable.ic_history, null));
|
||||||
|
titleView.setText(suggestion);
|
||||||
|
|
||||||
|
view.setOnClickListener(v -> search(suggestion));
|
||||||
|
|
||||||
|
tailingImageView.setOnClickListener(v -> {
|
||||||
|
searchViewModel.deleteRecentSearch(suggestion);
|
||||||
|
setRecentSuggestions();
|
||||||
|
});
|
||||||
|
|
||||||
|
bind.searchViewSuggestionContainer.addView(view);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSearchSuggestions(String query) {
|
||||||
|
searchViewModel.getSearchSuggestion(query).observe(getViewLifecycleOwner(), suggestions -> {
|
||||||
|
bind.searchViewSuggestionContainer.removeAllViews();
|
||||||
|
|
||||||
|
for (String suggestion : suggestions) {
|
||||||
|
View view = LayoutInflater.from(bind.searchViewSuggestionContainer.getContext()).inflate(R.layout.item_search_suggestion, bind.searchViewSuggestionContainer, false);
|
||||||
|
|
||||||
|
ImageView leadingImageView = view.findViewById(R.id.search_suggestion_icon);
|
||||||
|
TextView titleView = view.findViewById(R.id.search_suggestion_title);
|
||||||
|
ImageView tailingImageView = view.findViewById(R.id.search_suggestion_delete_icon);
|
||||||
|
|
||||||
|
leadingImageView.setImageDrawable(getResources().getDrawable(R.drawable.ic_search, null));
|
||||||
|
titleView.setText(suggestion);
|
||||||
|
tailingImageView.setVisibility(View.GONE);
|
||||||
|
|
||||||
|
view.setOnClickListener(v -> search(suggestion));
|
||||||
|
|
||||||
|
bind.searchViewSuggestionContainer.addView(view);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void search(String query) {
|
public void search(String query) {
|
||||||
searchViewModel.setQuery(query);
|
searchViewModel.setQuery(query);
|
||||||
|
bind.searchBar.setText(query);
|
||||||
bind.persistentSearchView.setInputQuery(query);
|
bind.searchView.hide();
|
||||||
performSearch(query);
|
performSearch(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void performSearch(String query) {
|
private void performSearch(String query) {
|
||||||
searchViewModel.search(query).observe(getViewLifecycleOwner(), result -> {
|
searchViewModel.search3(query).observe(getViewLifecycleOwner(), result -> {
|
||||||
if (bind != null) {
|
if (bind != null) {
|
||||||
if (result.getArtists() != null) {
|
if (result.getArtists() != null) {
|
||||||
bind.searchArtistSector.setVisibility(!result.getArtists().isEmpty() ? View.VISIBLE : View.GONE);
|
bind.searchArtistSector.setVisibility(!result.getArtists().isEmpty() ? View.VISIBLE : View.GONE);
|
||||||
@@ -212,11 +242,12 @@ public class SearchFragment extends Fragment implements ClickCallback {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean isQueryValid(String query) {
|
private boolean isQueryValid(String query) {
|
||||||
|
Log.d(TAG, "isQueryValid()");
|
||||||
return !query.equals("") && query.trim().length() > 2;
|
return !query.equals("") && query.trim().length() > 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void inputFocus() {
|
private void inputFocus() {
|
||||||
bind.persistentSearchView.expand();
|
bind.searchView.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeMediaBrowser() {
|
private void initializeMediaBrowser() {
|
||||||
|
|||||||
@@ -12,19 +12,30 @@ import androidx.activity.result.contract.ActivityResultContracts;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.OptIn;
|
import androidx.annotation.OptIn;
|
||||||
|
import androidx.appcompat.app.AppCompatDelegate;
|
||||||
|
import androidx.core.os.LocaleListCompat;
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
import androidx.preference.ListPreference;
|
import androidx.preference.ListPreference;
|
||||||
|
import androidx.preference.Preference;
|
||||||
import androidx.preference.PreferenceFragmentCompat;
|
import androidx.preference.PreferenceFragmentCompat;
|
||||||
|
|
||||||
|
import com.cappielloantonio.tempo.BuildConfig;
|
||||||
import com.cappielloantonio.tempo.R;
|
import com.cappielloantonio.tempo.R;
|
||||||
import com.cappielloantonio.tempo.helper.ThemeHelper;
|
import com.cappielloantonio.tempo.helper.ThemeHelper;
|
||||||
|
import com.cappielloantonio.tempo.interfaces.DialogClickCallback;
|
||||||
import com.cappielloantonio.tempo.interfaces.ScanCallback;
|
import com.cappielloantonio.tempo.interfaces.ScanCallback;
|
||||||
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
||||||
|
import com.cappielloantonio.tempo.ui.dialog.DeleteDownloadStorageDialog;
|
||||||
|
import com.cappielloantonio.tempo.ui.dialog.DownloadStorageDialog;
|
||||||
import com.cappielloantonio.tempo.ui.dialog.StarredSyncDialog;
|
import com.cappielloantonio.tempo.ui.dialog.StarredSyncDialog;
|
||||||
import com.cappielloantonio.tempo.util.Preferences;
|
import com.cappielloantonio.tempo.util.Preferences;
|
||||||
|
import com.cappielloantonio.tempo.util.UIUtil;
|
||||||
import com.cappielloantonio.tempo.viewmodel.SettingViewModel;
|
import com.cappielloantonio.tempo.viewmodel.SettingViewModel;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
@OptIn(markerClass = UnstableApi.class)
|
@OptIn(markerClass = UnstableApi.class)
|
||||||
public class SettingsFragment extends PreferenceFragmentCompat {
|
public class SettingsFragment extends PreferenceFragmentCompat {
|
||||||
private static final String TAG = "SettingsFragment";
|
private static final String TAG = "SettingsFragment";
|
||||||
@@ -69,42 +80,17 @@ public class SettingsFragment extends PreferenceFragmentCompat {
|
|||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
|
|
||||||
findPreference("logout").setOnPreferenceClickListener(preference -> {
|
checkEqualizer();
|
||||||
activity.quit();
|
checkStorage();
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
findPreference("scan_library").setOnPreferenceClickListener(preference -> {
|
setAppLanguage();
|
||||||
settingViewModel.launchScan(new ScanCallback() {
|
setVersion();
|
||||||
@Override
|
|
||||||
public void onError(Exception exception) {
|
|
||||||
findPreference("scan_library").setSummary(exception.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
actionLogout();
|
||||||
public void onSuccess(boolean isScanning, long count) {
|
actionScan();
|
||||||
getScanStatus();
|
actionSyncStarredTracks();
|
||||||
}
|
actionChangeDownloadStorage();
|
||||||
});
|
actionDeleteDownloadStorage();
|
||||||
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
findPreference("equalizer").setOnPreferenceClickListener(preference -> {
|
|
||||||
Intent intent = new Intent(AudioEffect.ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL);
|
|
||||||
someActivityResultLauncher.launch(intent);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
findPreference("sync_starred_tracks_for_offline_use").setOnPreferenceChangeListener((preference, newValue) -> {
|
|
||||||
if (newValue instanceof Boolean) {
|
|
||||||
if ((Boolean) newValue) {
|
|
||||||
StarredSyncDialog dialog = new StarredSyncDialog();
|
|
||||||
dialog.show(activity.getSupportFragmentManager(), null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -127,6 +113,127 @@ public class SettingsFragment extends PreferenceFragmentCompat {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void checkEqualizer() {
|
||||||
|
Preference equalizer = findPreference("equalizer");
|
||||||
|
|
||||||
|
if (equalizer == null) return;
|
||||||
|
|
||||||
|
Intent intent = new Intent(AudioEffect.ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL);
|
||||||
|
|
||||||
|
if ((intent.resolveActivity(requireActivity().getPackageManager()) != null)) {
|
||||||
|
equalizer.setOnPreferenceClickListener(preference -> {
|
||||||
|
someActivityResultLauncher.launch(intent);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
equalizer.setVisible(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkStorage() {
|
||||||
|
Preference storage = findPreference("download_storage");
|
||||||
|
|
||||||
|
if (storage == null) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (requireContext().getExternalFilesDirs(null)[1] == null) {
|
||||||
|
storage.setVisible(false);
|
||||||
|
} else {
|
||||||
|
storage.setSummary(Preferences.getDownloadStoragePreference() == 0 ? R.string.download_storage_internal_dialog_negative_button : R.string.download_storage_external_dialog_positive_button);
|
||||||
|
}
|
||||||
|
} catch (Exception exception) {
|
||||||
|
storage.setVisible(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setAppLanguage() {
|
||||||
|
ListPreference localePref = (ListPreference) findPreference("language");
|
||||||
|
|
||||||
|
Map<String, String> locales = UIUtil.getLangPreferenceDropdownEntries(requireContext());
|
||||||
|
|
||||||
|
CharSequence[] entries = locales.keySet().toArray(new CharSequence[locales.size()]);
|
||||||
|
CharSequence[] entryValues = locales.values().toArray(new CharSequence[locales.size()]);
|
||||||
|
|
||||||
|
localePref.setEntries(entries);
|
||||||
|
localePref.setEntryValues(entryValues);
|
||||||
|
|
||||||
|
localePref.setDefaultValue(entryValues[0]);
|
||||||
|
localePref.setSummary(Locale.forLanguageTag(localePref.getValue()).getDisplayLanguage());
|
||||||
|
|
||||||
|
localePref.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||||
|
LocaleListCompat appLocale = LocaleListCompat.forLanguageTags((String) newValue);
|
||||||
|
AppCompatDelegate.setApplicationLocales(appLocale);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setVersion() {
|
||||||
|
findPreference("version").setSummary(BuildConfig.VERSION_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void actionLogout() {
|
||||||
|
findPreference("logout").setOnPreferenceClickListener(preference -> {
|
||||||
|
activity.quit();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void actionScan() {
|
||||||
|
findPreference("scan_library").setOnPreferenceClickListener(preference -> {
|
||||||
|
settingViewModel.launchScan(new ScanCallback() {
|
||||||
|
@Override
|
||||||
|
public void onError(Exception exception) {
|
||||||
|
findPreference("scan_library").setSummary(exception.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(boolean isScanning, long count) {
|
||||||
|
getScanStatus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void actionSyncStarredTracks() {
|
||||||
|
findPreference("sync_starred_tracks_for_offline_use").setOnPreferenceChangeListener((preference, newValue) -> {
|
||||||
|
if (newValue instanceof Boolean) {
|
||||||
|
if ((Boolean) newValue) {
|
||||||
|
StarredSyncDialog dialog = new StarredSyncDialog();
|
||||||
|
dialog.show(activity.getSupportFragmentManager(), null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void actionChangeDownloadStorage() {
|
||||||
|
findPreference("download_storage").setOnPreferenceClickListener(preference -> {
|
||||||
|
DownloadStorageDialog dialog = new DownloadStorageDialog(new DialogClickCallback() {
|
||||||
|
@Override
|
||||||
|
public void onPositiveClick() {
|
||||||
|
findPreference("download_storage").setSummary(R.string.download_storage_external_dialog_positive_button);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNegativeClick() {
|
||||||
|
findPreference("download_storage").setSummary(R.string.download_storage_internal_dialog_negative_button);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
dialog.show(activity.getSupportFragmentManager(), null);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void actionDeleteDownloadStorage() {
|
||||||
|
findPreference("delete_download_storage").setOnPreferenceClickListener(preference -> {
|
||||||
|
DeleteDownloadStorageDialog dialog = new DeleteDownloadStorageDialog();
|
||||||
|
dialog.show(activity.getSupportFragmentManager(), null);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void getScanStatus() {
|
private void getScanStatus() {
|
||||||
settingViewModel.getScanStatus(new ScanCallback() {
|
settingViewModel.getScanStatus(new ScanCallback() {
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -166,7 +166,7 @@ public class SongListPageFragment extends Fragment implements ClickCallback {
|
|||||||
bind.songListRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
|
bind.songListRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
|
||||||
bind.songListRecyclerView.setHasFixedSize(true);
|
bind.songListRecyclerView.setHasFixedSize(true);
|
||||||
|
|
||||||
songHorizontalAdapter = new SongHorizontalAdapter(this, true);
|
songHorizontalAdapter = new SongHorizontalAdapter(this, true, false);
|
||||||
bind.songListRecyclerView.setAdapter(songHorizontalAdapter);
|
bind.songListRecyclerView.setAdapter(songHorizontalAdapter);
|
||||||
songListPageViewModel.getSongList().observe(getViewLifecycleOwner(), songs -> {
|
songListPageViewModel.getSongList().observe(getViewLifecycleOwner(), songs -> {
|
||||||
isLoading = false;
|
isLoading = false;
|
||||||
|
|||||||
@@ -91,10 +91,9 @@ public class AlbumBottomSheetDialog extends BottomSheetDialogFragment implements
|
|||||||
artistAlbum.setText(MusicUtil.getReadableString(albumBottomSheetViewModel.getAlbum().getArtist()));
|
artistAlbum.setText(MusicUtil.getReadableString(albumBottomSheetViewModel.getAlbum().getArtist()));
|
||||||
|
|
||||||
ToggleButton favoriteToggle = view.findViewById(R.id.button_favorite);
|
ToggleButton favoriteToggle = view.findViewById(R.id.button_favorite);
|
||||||
favoriteToggle.setChecked(Boolean.TRUE.equals(albumBottomSheetViewModel.getAlbum().getStarred()));
|
favoriteToggle.setChecked(albumBottomSheetViewModel.getAlbum().getStarred() != null);
|
||||||
favoriteToggle.setOnClickListener(v -> {
|
favoriteToggle.setOnClickListener(v -> {
|
||||||
albumBottomSheetViewModel.setFavorite();
|
albumBottomSheetViewModel.setFavorite();
|
||||||
dismissBottomSheet();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
TextView playRadio = view.findViewById(R.id.play_radio_text_view);
|
TextView playRadio = view.findViewById(R.id.play_radio_text_view);
|
||||||
|
|||||||
@@ -79,10 +79,9 @@ public class ArtistBottomSheetDialog extends BottomSheetDialogFragment implement
|
|||||||
nameArtist.setSelected(true);
|
nameArtist.setSelected(true);
|
||||||
|
|
||||||
ToggleButton favoriteToggle = view.findViewById(R.id.button_favorite);
|
ToggleButton favoriteToggle = view.findViewById(R.id.button_favorite);
|
||||||
favoriteToggle.setChecked(Boolean.TRUE.equals(artistBottomSheetViewModel.getArtist().getStarred()));
|
favoriteToggle.setChecked(artistBottomSheetViewModel.getArtist().getStarred() != null);
|
||||||
favoriteToggle.setOnClickListener(v -> {
|
favoriteToggle.setOnClickListener(v -> {
|
||||||
artistBottomSheetViewModel.setFavorite();
|
artistBottomSheetViewModel.setFavorite();
|
||||||
dismissBottomSheet();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
TextView playRadio = view.findViewById(R.id.play_radio_text_view);
|
TextView playRadio = view.findViewById(R.id.play_radio_text_view);
|
||||||
|
|||||||
@@ -87,10 +87,9 @@ public class SongBottomSheetDialog extends BottomSheetDialogFragment implements
|
|||||||
artistSong.setText(MusicUtil.getReadableString(songBottomSheetViewModel.getSong().getArtist()));
|
artistSong.setText(MusicUtil.getReadableString(songBottomSheetViewModel.getSong().getArtist()));
|
||||||
|
|
||||||
ToggleButton favoriteToggle = view.findViewById(R.id.button_favorite);
|
ToggleButton favoriteToggle = view.findViewById(R.id.button_favorite);
|
||||||
favoriteToggle.setChecked(Boolean.TRUE.equals(songBottomSheetViewModel.getSong().getStarred()));
|
favoriteToggle.setChecked(songBottomSheetViewModel.getSong().getStarred() != null);
|
||||||
favoriteToggle.setOnClickListener(v -> {
|
favoriteToggle.setOnClickListener(v -> {
|
||||||
songBottomSheetViewModel.setFavorite(requireContext());
|
songBottomSheetViewModel.setFavorite(requireContext());
|
||||||
dismissBottomSheet();
|
|
||||||
});
|
});
|
||||||
favoriteToggle.setOnLongClickListener(v -> {
|
favoriteToggle.setOnLongClickListener(v -> {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
@@ -216,7 +215,6 @@ public class SongBottomSheetDialog extends BottomSheetDialogFragment implements
|
|||||||
|
|
||||||
private void initDownloadUI(TextView download, TextView remove) {
|
private void initDownloadUI(TextView download, TextView remove) {
|
||||||
if (DownloadUtil.getDownloadTracker(requireContext()).isDownloaded(song.getId())) {
|
if (DownloadUtil.getDownloadTracker(requireContext()).isDownloaded(song.getId())) {
|
||||||
download.setVisibility(View.GONE);
|
|
||||||
remove.setVisibility(View.VISIBLE);
|
remove.setVisibility(View.VISIBLE);
|
||||||
} else {
|
} else {
|
||||||
download.setVisibility(View.VISIBLE);
|
download.setVisibility(View.VISIBLE);
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ object Constants {
|
|||||||
const val MUSIC_FOLDER_OBJECT = "MUSIC_FOLDER_OBJECT"
|
const val MUSIC_FOLDER_OBJECT = "MUSIC_FOLDER_OBJECT"
|
||||||
const val MUSIC_DIRECTORY_OBJECT = "MUSIC_DIRECTORY_OBJECT"
|
const val MUSIC_DIRECTORY_OBJECT = "MUSIC_DIRECTORY_OBJECT"
|
||||||
const val MUSIC_INDEX_OBJECT = "MUSIC_DIRECTORY_OBJECT"
|
const val MUSIC_INDEX_OBJECT = "MUSIC_DIRECTORY_OBJECT"
|
||||||
|
const val MUSIC_DIRECTORY_ID = "MUSIC_DIRECTORY_ID"
|
||||||
|
|
||||||
const val ALBUM_RECENTLY_PLAYED = "ALBUM_RECENTLY_PLAYED"
|
const val ALBUM_RECENTLY_PLAYED = "ALBUM_RECENTLY_PLAYED"
|
||||||
const val ALBUM_MOST_PLAYED = "ALBUM_MOST_PLAYED"
|
const val ALBUM_MOST_PLAYED = "ALBUM_MOST_PLAYED"
|
||||||
@@ -43,6 +44,9 @@ object Constants {
|
|||||||
const val PLAYLIST_ORDER_BY_NAME = "ORDER_BY_NAME"
|
const val PLAYLIST_ORDER_BY_NAME = "ORDER_BY_NAME"
|
||||||
const val PLAYLIST_ORDER_BY_RANDOM = "ORDER_BY_RANDOM"
|
const val PLAYLIST_ORDER_BY_RANDOM = "ORDER_BY_RANDOM"
|
||||||
|
|
||||||
|
const val PODCAST_FILTER_BY_DOWNLOAD = "PODCAST_FILTER_BY_DOWNLOAD"
|
||||||
|
const val PODCAST_FILTER_BY_ALL = "PODCAST_FILTER_BY_ALL"
|
||||||
|
|
||||||
const val MEDIA_TYPE_MUSIC = "music"
|
const val MEDIA_TYPE_MUSIC = "music"
|
||||||
const val MEDIA_TYPE_PODCAST = "podcast"
|
const val MEDIA_TYPE_PODCAST = "podcast"
|
||||||
const val MEDIA_TYPE_AUDIOBOOK = "audiobook"
|
const val MEDIA_TYPE_AUDIOBOOK = "audiobook"
|
||||||
@@ -72,6 +76,12 @@ object Constants {
|
|||||||
|
|
||||||
const val DOWNLOAD_URI = "rest/download"
|
const val DOWNLOAD_URI = "rest/download"
|
||||||
|
|
||||||
|
const val DOWNLOAD_TYPE_TRACK = "download_type_track"
|
||||||
|
const val DOWNLOAD_TYPE_ALBUM = "download_type_album"
|
||||||
|
const val DOWNLOAD_TYPE_ARTIST = "download_type_artist"
|
||||||
|
const val DOWNLOAD_TYPE_GENRE = "download_type_genre"
|
||||||
|
const val DOWNLOAD_TYPE_YEAR = "download_type_year"
|
||||||
|
|
||||||
const val PLAYABLE_MEDIA_LIMIT = 100
|
const val PLAYABLE_MEDIA_LIMIT = 100
|
||||||
const val PRE_PLAYABLE_MEDIA = 15
|
const val PRE_PLAYABLE_MEDIA = 15
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,7 @@ package com.cappielloantonio.tempo.util;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
||||||
|
import androidx.media3.common.util.Log;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
import androidx.media3.database.DatabaseProvider;
|
import androidx.media3.database.DatabaseProvider;
|
||||||
import androidx.media3.database.StandaloneDatabaseProvider;
|
import androidx.media3.database.StandaloneDatabaseProvider;
|
||||||
@@ -23,6 +24,8 @@ import java.io.File;
|
|||||||
import java.net.CookieHandler;
|
import java.net.CookieHandler;
|
||||||
import java.net.CookieManager;
|
import java.net.CookieManager;
|
||||||
import java.net.CookiePolicy;
|
import java.net.CookiePolicy;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@@ -127,9 +130,19 @@ public final class DownloadUtil {
|
|||||||
|
|
||||||
private static synchronized File getDownloadDirectory(Context context) {
|
private static synchronized File getDownloadDirectory(Context context) {
|
||||||
if (downloadDirectory == null) {
|
if (downloadDirectory == null) {
|
||||||
downloadDirectory = context.getExternalFilesDir(null);
|
if (Preferences.getDownloadStoragePreference() == 0) {
|
||||||
if (downloadDirectory == null) {
|
downloadDirectory = context.getExternalFilesDirs(null)[0];
|
||||||
downloadDirectory = context.getFilesDir();
|
if (downloadDirectory == null) {
|
||||||
|
downloadDirectory = context.getFilesDir();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
downloadDirectory = context.getExternalFilesDirs(null)[1];
|
||||||
|
} catch (Exception exception) {
|
||||||
|
downloadDirectory = context.getExternalFilesDirs(null)[0];
|
||||||
|
Preferences.setDownloadStoragePreference(0);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,4 +156,32 @@ public final class DownloadUtil {
|
|||||||
.setCacheWriteDataSinkFactory(null)
|
.setCacheWriteDataSinkFactory(null)
|
||||||
.setFlags(CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR);
|
.setFlags(CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static synchronized void eraseDownloadFolder(Context context) {
|
||||||
|
File directory = getDownloadDirectory(context);
|
||||||
|
|
||||||
|
ArrayList<File> files = listFiles(directory, new ArrayList<>());
|
||||||
|
|
||||||
|
for (File file : files) {
|
||||||
|
file.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static synchronized ArrayList<File> listFiles(File directory, ArrayList<File> files) {
|
||||||
|
if (directory.isDirectory()) {
|
||||||
|
File[] list = directory.listFiles();
|
||||||
|
|
||||||
|
if (list != null) {
|
||||||
|
for (File file : list) {
|
||||||
|
if (file.isFile() && file.getName().toLowerCase().endsWith(".exo")) {
|
||||||
|
files.add(file);
|
||||||
|
} else if (file.isDirectory()) {
|
||||||
|
listFiles(file, files);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return files;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ import androidx.media3.common.MimeTypes;
|
|||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
|
||||||
import com.cappielloantonio.tempo.App;
|
import com.cappielloantonio.tempo.App;
|
||||||
|
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
||||||
|
import com.cappielloantonio.tempo.model.Download;
|
||||||
|
import com.cappielloantonio.tempo.repository.DownloadRepository;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.InternetRadioStation;
|
import com.cappielloantonio.tempo.subsonic.models.InternetRadioStation;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.PodcastEpisode;
|
import com.cappielloantonio.tempo.subsonic.models.PodcastEpisode;
|
||||||
@@ -31,6 +34,7 @@ public class MappingUtil {
|
|||||||
|
|
||||||
public static MediaItem mapMediaItem(Child media) {
|
public static MediaItem mapMediaItem(Child media) {
|
||||||
Uri uri = getUri(media);
|
Uri uri = getUri(media);
|
||||||
|
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(media.getCoverArtId(), Preferences.getImageSize()));
|
||||||
|
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putString("id", media.getId());
|
bundle.putString("id", media.getId());
|
||||||
@@ -54,8 +58,8 @@ public class MappingUtil {
|
|||||||
bundle.putBoolean("isVideo", media.isVideo());
|
bundle.putBoolean("isVideo", media.isVideo());
|
||||||
bundle.putInt("userRating", media.getUserRating() != null ? media.getUserRating() : 0);
|
bundle.putInt("userRating", media.getUserRating() != null ? media.getUserRating() : 0);
|
||||||
bundle.putDouble("averageRating", media.getAverageRating() != null ? media.getAverageRating() : 0);
|
bundle.putDouble("averageRating", media.getAverageRating() != null ? media.getAverageRating() : 0);
|
||||||
bundle.putLong("playCount", media.getPlayCount() != null ? media.getTrack() : 0);
|
bundle.putLong("playCount", media.getPlayCount() != null ? media.getPlayCount() : 0);
|
||||||
bundle.putInt("discNumber", media.getDiscNumber() != null ? media.getTrack() : 0);
|
bundle.putInt("discNumber", media.getDiscNumber() != null ? media.getDiscNumber() : 0);
|
||||||
bundle.putLong("created", media.getCreated() != null ? media.getCreated().getTime() : 0);
|
bundle.putLong("created", media.getCreated() != null ? media.getCreated().getTime() : 0);
|
||||||
bundle.putLong("starred", media.getStarred() != null ? media.getStarred().getTime() : 0);
|
bundle.putLong("starred", media.getStarred() != null ? media.getStarred().getTime() : 0);
|
||||||
bundle.putString("albumId", media.getAlbumId());
|
bundle.putString("albumId", media.getAlbumId());
|
||||||
@@ -71,11 +75,12 @@ public class MappingUtil {
|
|||||||
.setMediaMetadata(
|
.setMediaMetadata(
|
||||||
new MediaMetadata.Builder()
|
new MediaMetadata.Builder()
|
||||||
.setTitle(MusicUtil.getReadableString(media.getTitle()))
|
.setTitle(MusicUtil.getReadableString(media.getTitle()))
|
||||||
.setTrackNumber(media.getTrack())
|
.setTrackNumber(media.getTrack() != null ? media.getTrack() : 0)
|
||||||
.setDiscNumber(media.getDiscNumber())
|
.setDiscNumber(media.getDiscNumber() != null ? media.getDiscNumber() : 0)
|
||||||
.setReleaseYear(media.getYear())
|
.setReleaseYear(media.getYear() != null ? media.getYear() : 0)
|
||||||
.setAlbumTitle(MusicUtil.getReadableString(media.getAlbum()))
|
.setAlbumTitle(MusicUtil.getReadableString(media.getAlbum()))
|
||||||
.setArtist(MusicUtil.getReadableString(media.getArtist()))
|
.setArtist(MusicUtil.getReadableString(media.getArtist()))
|
||||||
|
.setArtworkUri(artworkUri)
|
||||||
.setExtras(bundle)
|
.setExtras(bundle)
|
||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
@@ -106,20 +111,20 @@ public class MappingUtil {
|
|||||||
.setMediaMetadata(
|
.setMediaMetadata(
|
||||||
new MediaMetadata.Builder()
|
new MediaMetadata.Builder()
|
||||||
.setTitle(MusicUtil.getReadableString(media.getTitle()))
|
.setTitle(MusicUtil.getReadableString(media.getTitle()))
|
||||||
.setTrackNumber(media.getTrack())
|
.setTrackNumber(media.getTrack() != null ? media.getTrack() : 0)
|
||||||
.setDiscNumber(media.getDiscNumber())
|
.setDiscNumber(media.getDiscNumber() != null ? media.getDiscNumber() : 0)
|
||||||
.setReleaseYear(media.getYear())
|
.setReleaseYear(media.getYear() != null ? media.getYear() : 0)
|
||||||
.setAlbumTitle(MusicUtil.getReadableString(media.getAlbum()))
|
.setAlbumTitle(MusicUtil.getReadableString(media.getAlbum()))
|
||||||
.setArtist(MusicUtil.getReadableString(media.getArtist()))
|
.setArtist(MusicUtil.getReadableString(media.getArtist()))
|
||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
.setRequestMetadata(
|
.setRequestMetadata(
|
||||||
new MediaItem.RequestMetadata.Builder()
|
new MediaItem.RequestMetadata.Builder()
|
||||||
.setMediaUri(MusicUtil.getDownloadUri(media.getId()))
|
.setMediaUri(Preferences.preferTranscodedDownload() ? MusicUtil.getTranscodedDownloadUri(media.getId()) : MusicUtil.getDownloadUri(media.getId()))
|
||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
.setMimeType(MimeTypes.BASE_TYPE_AUDIO)
|
.setMimeType(MimeTypes.BASE_TYPE_AUDIO)
|
||||||
.setUri(MusicUtil.getDownloadUri(media.getId()))
|
.setUri(Preferences.preferTranscodedDownload() ? MusicUtil.getTranscodedDownloadUri(media.getId()) : MusicUtil.getDownloadUri(media.getId()))
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,6 +160,7 @@ public class MappingUtil {
|
|||||||
|
|
||||||
public static MediaItem mapMediaItem(PodcastEpisode podcastEpisode) {
|
public static MediaItem mapMediaItem(PodcastEpisode podcastEpisode) {
|
||||||
Uri uri = getUri(podcastEpisode);
|
Uri uri = getUri(podcastEpisode);
|
||||||
|
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(podcastEpisode.getCoverArtId(), Preferences.getImageSize()));
|
||||||
|
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putString("id", podcastEpisode.getId());
|
bundle.putString("id", podcastEpisode.getId());
|
||||||
@@ -178,8 +184,8 @@ public class MappingUtil {
|
|||||||
bundle.putBoolean("isVideo", podcastEpisode.isVideo());
|
bundle.putBoolean("isVideo", podcastEpisode.isVideo());
|
||||||
bundle.putInt("userRating", podcastEpisode.getUserRating() != null ? podcastEpisode.getUserRating() : 0);
|
bundle.putInt("userRating", podcastEpisode.getUserRating() != null ? podcastEpisode.getUserRating() : 0);
|
||||||
bundle.putDouble("averageRating", podcastEpisode.getAverageRating() != null ? podcastEpisode.getAverageRating() : 0);
|
bundle.putDouble("averageRating", podcastEpisode.getAverageRating() != null ? podcastEpisode.getAverageRating() : 0);
|
||||||
bundle.putLong("playCount", podcastEpisode.getPlayCount() != null ? podcastEpisode.getTrack() : 0);
|
bundle.putLong("playCount", podcastEpisode.getPlayCount() != null ? podcastEpisode.getPlayCount() : 0);
|
||||||
bundle.putInt("discNumber", podcastEpisode.getDiscNumber() != null ? podcastEpisode.getTrack() : 0);
|
bundle.putInt("discNumber", podcastEpisode.getDiscNumber() != null ? podcastEpisode.getDiscNumber() : 0);
|
||||||
bundle.putLong("created", podcastEpisode.getCreated() != null ? podcastEpisode.getCreated().getTime() : 0);
|
bundle.putLong("created", podcastEpisode.getCreated() != null ? podcastEpisode.getCreated().getTime() : 0);
|
||||||
bundle.putLong("starred", podcastEpisode.getStarred() != null ? podcastEpisode.getStarred().getTime() : 0);
|
bundle.putLong("starred", podcastEpisode.getStarred() != null ? podcastEpisode.getStarred().getTime() : 0);
|
||||||
bundle.putString("albumId", podcastEpisode.getAlbumId());
|
bundle.putString("albumId", podcastEpisode.getAlbumId());
|
||||||
@@ -195,11 +201,12 @@ public class MappingUtil {
|
|||||||
.setMediaMetadata(
|
.setMediaMetadata(
|
||||||
new MediaMetadata.Builder()
|
new MediaMetadata.Builder()
|
||||||
.setTitle(MusicUtil.getReadableString(podcastEpisode.getTitle()))
|
.setTitle(MusicUtil.getReadableString(podcastEpisode.getTitle()))
|
||||||
.setTrackNumber(podcastEpisode.getTrack())
|
.setTrackNumber(podcastEpisode.getTrack() != null ? podcastEpisode.getTrack() : 0)
|
||||||
.setDiscNumber(podcastEpisode.getDiscNumber())
|
.setDiscNumber(podcastEpisode.getDiscNumber() != null ? podcastEpisode.getDiscNumber() : 0)
|
||||||
.setReleaseYear(podcastEpisode.getYear())
|
.setReleaseYear(podcastEpisode.getYear() != null ? podcastEpisode.getYear() : 0)
|
||||||
.setAlbumTitle(MusicUtil.getReadableString(podcastEpisode.getAlbum()))
|
.setAlbumTitle(MusicUtil.getReadableString(podcastEpisode.getAlbum()))
|
||||||
.setArtist(MusicUtil.getReadableString(podcastEpisode.getArtist()))
|
.setArtist(MusicUtil.getReadableString(podcastEpisode.getArtist()))
|
||||||
|
.setArtworkUri(artworkUri)
|
||||||
.setExtras(bundle)
|
.setExtras(bundle)
|
||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
@@ -216,13 +223,18 @@ public class MappingUtil {
|
|||||||
|
|
||||||
private static Uri getUri(Child media) {
|
private static Uri getUri(Child media) {
|
||||||
return DownloadUtil.getDownloadTracker(App.getContext()).isDownloaded(media.getId())
|
return DownloadUtil.getDownloadTracker(App.getContext()).isDownloaded(media.getId())
|
||||||
? MusicUtil.getDownloadUri(media.getId())
|
? getDownloadUri(media.getId())
|
||||||
: MusicUtil.getStreamUri(media.getId());
|
: MusicUtil.getStreamUri(media.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Uri getUri(PodcastEpisode podcastEpisode) {
|
private static Uri getUri(PodcastEpisode podcastEpisode) {
|
||||||
return DownloadUtil.getDownloadTracker(App.getContext()).isDownloaded(podcastEpisode.getId())
|
return DownloadUtil.getDownloadTracker(App.getContext()).isDownloaded(podcastEpisode.getStreamId())
|
||||||
? MusicUtil.getDownloadUri(podcastEpisode.getId())
|
? getDownloadUri(podcastEpisode.getStreamId())
|
||||||
: MusicUtil.getStreamUri(podcastEpisode.getId());
|
: MusicUtil.getStreamUri(podcastEpisode.getStreamId());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Uri getDownloadUri(String id) {
|
||||||
|
Download download = new DownloadRepository().getDownload(id);
|
||||||
|
return download != null && !download.getDownloadUri().isEmpty() ? Uri.parse(download.getDownloadUri()) : MusicUtil.getDownloadUri(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ import android.text.Html;
|
|||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.cappielloantonio.tempo.App;
|
import com.cappielloantonio.tempo.App;
|
||||||
|
import com.cappielloantonio.tempo.R;
|
||||||
|
import com.cappielloantonio.tempo.model.Download;
|
||||||
|
import com.cappielloantonio.tempo.repository.DownloadRepository;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -29,7 +32,7 @@ public class MusicUtil {
|
|||||||
uri.append("stream");
|
uri.append("stream");
|
||||||
|
|
||||||
if (params.containsKey("u") && params.get("u") != null)
|
if (params.containsKey("u") && params.get("u") != null)
|
||||||
uri.append("?u=").append(params.get("u"));
|
uri.append("?u=").append(Util.encode(params.get("u")));
|
||||||
if (params.containsKey("p") && params.get("p") != null)
|
if (params.containsKey("p") && params.get("p") != null)
|
||||||
uri.append("&p=").append(params.get("p"));
|
uri.append("&p=").append(params.get("p"));
|
||||||
if (params.containsKey("s") && params.get("s") != null)
|
if (params.containsKey("s") && params.get("s") != null)
|
||||||
@@ -41,8 +44,11 @@ public class MusicUtil {
|
|||||||
if (params.containsKey("c") && params.get("c") != null)
|
if (params.containsKey("c") && params.get("c") != null)
|
||||||
uri.append("&c=").append(params.get("c"));
|
uri.append("&c=").append(params.get("c"));
|
||||||
|
|
||||||
uri.append("&maxBitRate=").append(getBitratePreference());
|
if (!Preferences.isServerPrioritized())
|
||||||
uri.append("&format=").append(getTranscodingFormatPreference());
|
uri.append("&maxBitRate=").append(getBitratePreference());
|
||||||
|
if (!Preferences.isServerPrioritized())
|
||||||
|
uri.append("&format=").append(getTranscodingFormatPreference());
|
||||||
|
|
||||||
uri.append("&id=").append(id);
|
uri.append("&id=").append(id);
|
||||||
|
|
||||||
Log.d(TAG, "getStreamUri: " + uri);
|
Log.d(TAG, "getStreamUri: " + uri);
|
||||||
@@ -51,15 +57,49 @@ public class MusicUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static Uri getDownloadUri(String id) {
|
public static Uri getDownloadUri(String id) {
|
||||||
|
StringBuilder uri = new StringBuilder();
|
||||||
|
|
||||||
|
Download download = new DownloadRepository().getDownload(id);
|
||||||
|
|
||||||
|
if (download == null || download.getDownloadUri().isEmpty()) {
|
||||||
|
Map<String, String> params = App.getSubsonicClientInstance(false).getParams();
|
||||||
|
|
||||||
|
uri.append(App.getSubsonicClientInstance(false).getUrl());
|
||||||
|
uri.append("download");
|
||||||
|
|
||||||
|
if (params.containsKey("u") && params.get("u") != null)
|
||||||
|
uri.append("?u=").append(Util.encode(params.get("u")));
|
||||||
|
if (params.containsKey("p") && params.get("p") != null)
|
||||||
|
uri.append("&p=").append(params.get("p"));
|
||||||
|
if (params.containsKey("s") && params.get("s") != null)
|
||||||
|
uri.append("&s=").append(params.get("s"));
|
||||||
|
if (params.containsKey("t") && params.get("t") != null)
|
||||||
|
uri.append("&t=").append(params.get("t"));
|
||||||
|
if (params.containsKey("v") && params.get("v") != null)
|
||||||
|
uri.append("&v=").append(params.get("v"));
|
||||||
|
if (params.containsKey("c") && params.get("c") != null)
|
||||||
|
uri.append("&c=").append(params.get("c"));
|
||||||
|
|
||||||
|
uri.append("&id=").append(id);
|
||||||
|
} else {
|
||||||
|
uri.append(download.getDownloadUri());
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d(TAG, "getDownloadUri: " + uri);
|
||||||
|
|
||||||
|
return Uri.parse(uri.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Uri getTranscodedDownloadUri(String id) {
|
||||||
Map<String, String> params = App.getSubsonicClientInstance(false).getParams();
|
Map<String, String> params = App.getSubsonicClientInstance(false).getParams();
|
||||||
|
|
||||||
StringBuilder uri = new StringBuilder();
|
StringBuilder uri = new StringBuilder();
|
||||||
|
|
||||||
uri.append(App.getSubsonicClientInstance(false).getUrl());
|
uri.append(App.getSubsonicClientInstance(false).getUrl());
|
||||||
uri.append("download");
|
uri.append("stream");
|
||||||
|
|
||||||
if (params.containsKey("u") && params.get("u") != null)
|
if (params.containsKey("u") && params.get("u") != null)
|
||||||
uri.append("?u=").append(params.get("u"));
|
uri.append("?u=").append(Util.encode(params.get("u")));
|
||||||
if (params.containsKey("p") && params.get("p") != null)
|
if (params.containsKey("p") && params.get("p") != null)
|
||||||
uri.append("&p=").append(params.get("p"));
|
uri.append("&p=").append(params.get("p"));
|
||||||
if (params.containsKey("s") && params.get("s") != null)
|
if (params.containsKey("s") && params.get("s") != null)
|
||||||
@@ -71,13 +111,19 @@ public class MusicUtil {
|
|||||||
if (params.containsKey("c") && params.get("c") != null)
|
if (params.containsKey("c") && params.get("c") != null)
|
||||||
uri.append("&c=").append(params.get("c"));
|
uri.append("&c=").append(params.get("c"));
|
||||||
|
|
||||||
|
if (!Preferences.isServerPrioritizedInTranscodedDownload())
|
||||||
|
uri.append("&maxBitRate=").append(getBitratePreferenceForDownload());
|
||||||
|
if (!Preferences.isServerPrioritizedInTranscodedDownload())
|
||||||
|
uri.append("&format=").append(getTranscodingFormatPreferenceForDownload());
|
||||||
|
|
||||||
uri.append("&id=").append(id);
|
uri.append("&id=").append(id);
|
||||||
|
|
||||||
Log.d(TAG, "getDownloadUri: " + uri);
|
Log.d(TAG, "getTranscodedDownloadUri: " + uri);
|
||||||
|
|
||||||
return Uri.parse(uri.toString());
|
return Uri.parse(uri.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static String getReadableDurationString(long duration, boolean millis) {
|
public static String getReadableDurationString(long duration, boolean millis) {
|
||||||
long minutes;
|
long minutes;
|
||||||
long seconds;
|
long seconds;
|
||||||
@@ -119,6 +165,14 @@ public class MusicUtil {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getReadableTrackNumber(Context context, Integer trackNumber) {
|
||||||
|
if (trackNumber != null) {
|
||||||
|
return String.valueOf(trackNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
return context.getString(R.string.label_placeholder);
|
||||||
|
}
|
||||||
|
|
||||||
public static String forceReadableString(String string) {
|
public static String forceReadableString(String string) {
|
||||||
if (string != null) {
|
if (string != null) {
|
||||||
return getReadableString(string)
|
return getReadableString(string)
|
||||||
@@ -193,6 +247,19 @@ public class MusicUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getBitratePreferenceForDownload() {
|
||||||
|
String audioTranscodeFormat = getTranscodingFormatPreferenceForDownload();
|
||||||
|
|
||||||
|
if (audioTranscodeFormat.equals("raw"))
|
||||||
|
return "0";
|
||||||
|
|
||||||
|
return Preferences.getBitrateTranscodedDownload();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getTranscodingFormatPreferenceForDownload() {
|
||||||
|
return Preferences.getAudioTranscodeFormatTranscodedDownload();
|
||||||
|
}
|
||||||
|
|
||||||
public static List<Child> limitPlayableMedia(List<Child> toLimit, int position) {
|
public static List<Child> limitPlayableMedia(List<Child> toLimit, int position) {
|
||||||
if (!toLimit.isEmpty() && toLimit.size() > Constants.PLAYABLE_MEDIA_LIMIT) {
|
if (!toLimit.isEmpty() && toLimit.size() > Constants.PLAYABLE_MEDIA_LIMIT) {
|
||||||
int from = position < Constants.PRE_PLAYABLE_MEDIA ? 0 : position - Constants.PRE_PLAYABLE_MEDIA;
|
int from = position < Constants.PRE_PLAYABLE_MEDIA ? 0 : position - Constants.PRE_PLAYABLE_MEDIA;
|
||||||
@@ -204,8 +271,12 @@ public class MusicUtil {
|
|||||||
return toLimit;
|
return toLimit;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getPlayableMediaPosition(int initialPosition) {
|
public static int getPlayableMediaPosition(List<Child> toLimit, int position) {
|
||||||
return initialPosition > Constants.PLAYABLE_MEDIA_LIMIT ? Constants.PRE_PLAYABLE_MEDIA : initialPosition;
|
if (!toLimit.isEmpty() && toLimit.size() > Constants.PLAYABLE_MEDIA_LIMIT) {
|
||||||
|
return Math.min(position, Constants.PRE_PLAYABLE_MEDIA);
|
||||||
|
}
|
||||||
|
|
||||||
|
return position;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ConnectivityManager getConnectivityManager() {
|
private static ConnectivityManager getConnectivityManager() {
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.cappielloantonio.tempo.util;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.net.ConnectivityManager;
|
||||||
|
import android.net.NetworkInfo;
|
||||||
|
|
||||||
|
import com.cappielloantonio.tempo.App;
|
||||||
|
|
||||||
|
public class NetworkUtil {
|
||||||
|
public static boolean isOffline() {
|
||||||
|
ConnectivityManager connectivityManager = (ConnectivityManager) App.getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||||
|
|
||||||
|
if (connectivityManager != null) {
|
||||||
|
NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
|
||||||
|
|
||||||
|
return networkInfo == null || !networkInfo.isConnected();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ object Preferences {
|
|||||||
private const val TOKEN = "token"
|
private const val TOKEN = "token"
|
||||||
private const val SALT = "salt"
|
private const val SALT = "salt"
|
||||||
private const val LOW_SECURITY = "low_security"
|
private const val LOW_SECURITY = "low_security"
|
||||||
|
private const val BATTERY_OPTIMIZATION = "battery_optimization"
|
||||||
private const val SERVER_ID = "server_id"
|
private const val SERVER_ID = "server_id"
|
||||||
private const val PLAYBACK_SPEED = "playback_speed"
|
private const val PLAYBACK_SPEED = "playback_speed"
|
||||||
private const val SKIP_SILENCE = "skip_silence"
|
private const val SKIP_SILENCE = "skip_silence"
|
||||||
@@ -29,6 +30,15 @@ object Preferences {
|
|||||||
private const val ROUNDED_CORNER_SIZE = "rounded_corner_size"
|
private const val ROUNDED_CORNER_SIZE = "rounded_corner_size"
|
||||||
private const val PODCAST_SECTION_VISIBILITY = "podcast_section_visibility"
|
private const val PODCAST_SECTION_VISIBILITY = "podcast_section_visibility"
|
||||||
private const val RADIO_SECTION_VISIBILITY = "radio_section_visibility"
|
private const val RADIO_SECTION_VISIBILITY = "radio_section_visibility"
|
||||||
|
private const val MUSIC_DIRECTORY_SECTION_VISIBILITY = "music_directory_section_visibility"
|
||||||
|
private const val REPLAY_GAIN_MODE = "replay_gain_mode"
|
||||||
|
private const val AUDIO_TRANSCODE_PRIORITY = "audio_transcode_priority"
|
||||||
|
private const val DOWNLOAD_STORAGE = "download_storage"
|
||||||
|
private const val DEFAULT_DOWNLOAD_VIEW_TYPE = "default_download_view_type"
|
||||||
|
private const val AUDIO_TRANSCODE_DOWNLOAD = "audio_transcode_download"
|
||||||
|
private const val AUDIO_TRANSCODE_DOWNLOAD_PRIORITY = "audio_transcode_download_priority"
|
||||||
|
private const val MAX_BITRATE_DOWNLOAD = "max_bitrate_download"
|
||||||
|
private const val AUDIO_TRANSCODE_FORMAT_DOWNLOAD = "audio_transcode_format_download"
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun getServer(): String? {
|
fun getServer(): String? {
|
||||||
@@ -100,6 +110,16 @@ object Preferences {
|
|||||||
App.getInstance().preferences.edit().putString(SERVER_ID, serverId).apply()
|
App.getInstance().preferences.edit().putString(SERVER_ID, serverId).apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun askForOptimization(): Boolean {
|
||||||
|
return App.getInstance().preferences.getBoolean(BATTERY_OPTIMIZATION, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun dontAskForOptimization() {
|
||||||
|
App.getInstance().preferences.edit().putBoolean(BATTERY_OPTIMIZATION, false).apply()
|
||||||
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun getPlaybackSpeed(): Float {
|
fun getPlaybackSpeed(): Float {
|
||||||
return App.getInstance().preferences.getFloat(PLAYBACK_SPEED, 1f)
|
return App.getInstance().preferences.getFloat(PLAYBACK_SPEED, 1f)
|
||||||
@@ -229,4 +249,68 @@ object Preferences {
|
|||||||
fun setRadioSectionHidden() {
|
fun setRadioSectionHidden() {
|
||||||
App.getInstance().preferences.edit().putBoolean(RADIO_SECTION_VISIBILITY, false).apply()
|
App.getInstance().preferences.edit().putBoolean(RADIO_SECTION_VISIBILITY, false).apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun isMusicDirectorySectionVisible(): Boolean {
|
||||||
|
return App.getInstance().preferences.getBoolean(MUSIC_DIRECTORY_SECTION_VISIBILITY, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun getReplayGainMode(): String? {
|
||||||
|
return App.getInstance().preferences.getString(REPLAY_GAIN_MODE, "disabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun isServerPrioritized(): Boolean {
|
||||||
|
return App.getInstance().preferences.getBoolean(AUDIO_TRANSCODE_PRIORITY, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun getDownloadStoragePreference(): Int {
|
||||||
|
return App.getInstance().preferences.getString(DOWNLOAD_STORAGE, "0")!!.toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun setDownloadStoragePreference(storagePreference: Int) {
|
||||||
|
return App.getInstance().preferences.edit().putString(
|
||||||
|
DOWNLOAD_STORAGE,
|
||||||
|
storagePreference.toString()
|
||||||
|
).apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun getDefaultDownloadViewType(): String {
|
||||||
|
return App.getInstance().preferences.getString(
|
||||||
|
DEFAULT_DOWNLOAD_VIEW_TYPE,
|
||||||
|
Constants.DOWNLOAD_TYPE_TRACK
|
||||||
|
)!!
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun setDefaultDownloadViewType(viewType: String) {
|
||||||
|
return App.getInstance().preferences.edit().putString(
|
||||||
|
DEFAULT_DOWNLOAD_VIEW_TYPE,
|
||||||
|
viewType
|
||||||
|
).apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun preferTranscodedDownload(): Boolean {
|
||||||
|
return App.getInstance().preferences.getBoolean(AUDIO_TRANSCODE_DOWNLOAD, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun isServerPrioritizedInTranscodedDownload(): Boolean {
|
||||||
|
return App.getInstance().preferences.getBoolean(AUDIO_TRANSCODE_DOWNLOAD_PRIORITY, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun getBitrateTranscodedDownload(): String {
|
||||||
|
return App.getInstance().preferences.getString(MAX_BITRATE_DOWNLOAD, "0")!!
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun getAudioTranscodeFormatTranscodedDownload(): String {
|
||||||
|
return App.getInstance().preferences.getString(AUDIO_TRANSCODE_FORMAT_DOWNLOAD, "raw")!!
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
package com.cappielloantonio.tempo.util;
|
||||||
|
|
||||||
|
import androidx.annotation.OptIn;
|
||||||
|
import androidx.media3.common.Metadata;
|
||||||
|
import androidx.media3.common.Tracks;
|
||||||
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
import androidx.media3.exoplayer.ExoPlayer;
|
||||||
|
|
||||||
|
import com.cappielloantonio.tempo.model.ReplayGain;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@OptIn(markerClass = UnstableApi.class)
|
||||||
|
public class ReplayGainUtil {
|
||||||
|
private static final String[] tags = {"REPLAYGAIN_TRACK_GAIN", "REPLAYGAIN_ALBUM_GAIN", "R128_TRACK_GAIN", "R128_ALBUM_GAIN"};
|
||||||
|
|
||||||
|
public static void setReplayGain(ExoPlayer player, Tracks tracks) {
|
||||||
|
List<Metadata> metadata = getMetadata(tracks);
|
||||||
|
List<ReplayGain> gains = getReplayGains(metadata);
|
||||||
|
|
||||||
|
applyReplayGain(player, gains);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<Metadata> getMetadata(Tracks tracks) {
|
||||||
|
List<Metadata> metadata = new ArrayList<>();
|
||||||
|
|
||||||
|
if (tracks != null && !tracks.getGroups().isEmpty()) {
|
||||||
|
for (int i = 0; i < tracks.getGroups().size(); i++) {
|
||||||
|
Tracks.Group group = tracks.getGroups().get(i);
|
||||||
|
|
||||||
|
if (group != null && group.getMediaTrackGroup() != null) {
|
||||||
|
for (int j = 0; j < group.getMediaTrackGroup().length; j++) {
|
||||||
|
metadata.add(group.getTrackFormat(j).metadata);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<ReplayGain> getReplayGains(List<Metadata> metadata) {
|
||||||
|
List<ReplayGain> gains = new ArrayList<>();
|
||||||
|
|
||||||
|
if (metadata != null) {
|
||||||
|
for (int i = 0; i < metadata.size(); i++) {
|
||||||
|
Metadata singleMetadata = metadata.get(i);
|
||||||
|
|
||||||
|
if (singleMetadata != null) {
|
||||||
|
for (int j = 0; j < singleMetadata.length(); j++) {
|
||||||
|
Metadata.Entry entry = singleMetadata.get(j);
|
||||||
|
|
||||||
|
if (checkReplayGain(entry)) {
|
||||||
|
ReplayGain replayGain = setReplayGains(entry);
|
||||||
|
gains.add(replayGain);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return gains;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean checkReplayGain(Metadata.Entry entry) {
|
||||||
|
for (String tag : tags) {
|
||||||
|
if (entry.toString().contains(tag)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ReplayGain setReplayGains(Metadata.Entry entry) {
|
||||||
|
ReplayGain replayGain = new ReplayGain();
|
||||||
|
|
||||||
|
if (entry.toString().contains(tags[0])) {
|
||||||
|
replayGain.setTrackGain(parseReplayGainTag(entry));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.toString().contains(tags[1])) {
|
||||||
|
replayGain.setAlbumGain(parseReplayGainTag(entry));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.toString().contains(tags[2])) {
|
||||||
|
replayGain.setTrackGain(parseReplayGainTag(entry) / 256f);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.toString().contains(tags[3])) {
|
||||||
|
replayGain.setAlbumGain(parseReplayGainTag(entry) / 256f);
|
||||||
|
}
|
||||||
|
|
||||||
|
return replayGain;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Float parseReplayGainTag(Metadata.Entry entry) {
|
||||||
|
try {
|
||||||
|
return Float.parseFloat(entry.toString().replaceAll("[^\\d.]", ""));
|
||||||
|
} catch (NumberFormatException exception) {
|
||||||
|
return 0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void applyReplayGain(ExoPlayer player, List<ReplayGain> gains) {
|
||||||
|
if (Objects.equals(Preferences.getReplayGainMode(), "disabled") || gains.size() == 0) {
|
||||||
|
setReplayGain(player, 0f);
|
||||||
|
} else if (Objects.equals(Preferences.getReplayGainMode(), "track")) {
|
||||||
|
setReplayGain(player, gains.get(0).getTrackGain() != 0f ? gains.get(0).getTrackGain() : gains.get(0).getAlbumGain());
|
||||||
|
} else if (Objects.equals(Preferences.getReplayGainMode(), "album")) {
|
||||||
|
setReplayGain(player, gains.get(0).getAlbumGain() != 0f ? gains.get(0).getAlbumGain() : gains.get(0).getTrackGain());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void setReplayGain(ExoPlayer player, float gain) {
|
||||||
|
player.setVolume((float) Math.pow(10f, gain / 20f));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,10 +5,20 @@ import android.content.res.TypedArray;
|
|||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.graphics.drawable.InsetDrawable;
|
import android.graphics.drawable.InsetDrawable;
|
||||||
|
|
||||||
|
import androidx.core.os.LocaleListCompat;
|
||||||
import androidx.recyclerview.widget.DividerItemDecoration;
|
import androidx.recyclerview.widget.DividerItemDecoration;
|
||||||
|
|
||||||
import com.google.android.gms.common.ConnectionResult;
|
import com.cappielloantonio.tempo.R;
|
||||||
import com.google.android.gms.common.GoogleApiAvailability;
|
|
||||||
|
import org.xmlpull.v1.XmlPullParser;
|
||||||
|
import org.xmlpull.v1.XmlPullParserException;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public class UIUtil {
|
public class UIUtil {
|
||||||
public static int getSpanCount(int itemCount, int maxSpan) {
|
public static int getSpanCount(int itemCount, int maxSpan) {
|
||||||
@@ -21,10 +31,6 @@ public class UIUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isCastApiAvailable(Context context) {
|
|
||||||
return GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context) == ConnectionResult.SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static DividerItemDecoration getDividerItemDecoration(Context context) {
|
public static DividerItemDecoration getDividerItemDecoration(Context context) {
|
||||||
int[] ATTRS = new int[]{android.R.attr.listDivider};
|
int[] ATTRS = new int[]{android.R.attr.listDivider};
|
||||||
|
|
||||||
@@ -38,4 +44,44 @@ public class UIUtil {
|
|||||||
|
|
||||||
return itemDecoration;
|
return itemDecoration;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static LocaleListCompat getLocalesFromResources(Context context) {
|
||||||
|
final List<String> tagsList = new ArrayList<>();
|
||||||
|
|
||||||
|
XmlPullParser xpp = context.getResources().getXml(R.xml.locale_config);
|
||||||
|
|
||||||
|
try {
|
||||||
|
while (xpp.getEventType() != XmlPullParser.END_DOCUMENT) {
|
||||||
|
String tagName = xpp.getName();
|
||||||
|
|
||||||
|
if (xpp.getEventType() == XmlPullParser.START_TAG) {
|
||||||
|
if ("locale".equals(tagName) && xpp.getAttributeCount() > 0 && xpp.getAttributeName(0).equals("name")) {
|
||||||
|
tagsList.add(xpp.getAttributeValue(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
xpp.next();
|
||||||
|
}
|
||||||
|
} catch (XmlPullParserException | IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return LocaleListCompat.forLanguageTags(String.join(",", tagsList));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Map<String, String> getLangPreferenceDropdownEntries(Context context) {
|
||||||
|
LocaleListCompat localeList = getLocalesFromResources(context);
|
||||||
|
|
||||||
|
Map<String, String> map = new HashMap<>();
|
||||||
|
|
||||||
|
for (int i = 0; i < localeList.size(); i++) {
|
||||||
|
Locale locale = localeList.get(i);
|
||||||
|
|
||||||
|
if (locale != null) {
|
||||||
|
map.put(Util.toPascalCase(locale.getDisplayName()), locale.toLanguageTag());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
64
app/src/main/java/com/cappielloantonio/tempo/util/Util.java
Normal file
64
app/src/main/java/com/cappielloantonio/tempo/util/Util.java
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
package com.cappielloantonio.tempo.util;
|
||||||
|
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
public class Util {
|
||||||
|
public static <T> Predicate<T> distinctByKey(Function<? super T, Object> keyExtractor) {
|
||||||
|
try {
|
||||||
|
Map<Object, Boolean> uniqueMap = new ConcurrentHashMap<>();
|
||||||
|
return t -> uniqueMap.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
|
||||||
|
} catch (NullPointerException exception) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String toPascalCase(String name) {
|
||||||
|
if (name == null || name.isEmpty()) {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder pascalCase = new StringBuilder();
|
||||||
|
|
||||||
|
char newChar;
|
||||||
|
boolean toUpper = false;
|
||||||
|
char[] charArray = name.toCharArray();
|
||||||
|
|
||||||
|
for (int ctr = 0; ctr <= charArray.length - 1; ctr++) {
|
||||||
|
if (ctr == 0) {
|
||||||
|
newChar = Character.toUpperCase(charArray[ctr]);
|
||||||
|
pascalCase = new StringBuilder(Character.toString(newChar));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (charArray[ctr] == '_') {
|
||||||
|
toUpper = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toUpper) {
|
||||||
|
newChar = Character.toUpperCase(charArray[ctr]);
|
||||||
|
pascalCase.append(newChar);
|
||||||
|
toUpper = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
pascalCase.append(charArray[ctr]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return pascalCase.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String encode(String value) {
|
||||||
|
try {
|
||||||
|
return URLEncoder.encode(value, StandardCharsets.UTF_8.toString());
|
||||||
|
} catch (UnsupportedEncodingException ex) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,11 +7,14 @@ import androidx.lifecycle.AndroidViewModel;
|
|||||||
import androidx.lifecycle.LiveData;
|
import androidx.lifecycle.LiveData;
|
||||||
import androidx.lifecycle.MutableLiveData;
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
|
||||||
|
import com.cappielloantonio.tempo.interfaces.StarCallback;
|
||||||
import com.cappielloantonio.tempo.repository.AlbumRepository;
|
import com.cappielloantonio.tempo.repository.AlbumRepository;
|
||||||
import com.cappielloantonio.tempo.repository.ArtistRepository;
|
import com.cappielloantonio.tempo.repository.ArtistRepository;
|
||||||
|
import com.cappielloantonio.tempo.repository.FavoriteRepository;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
|
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||||
|
import com.cappielloantonio.tempo.util.NetworkUtil;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -19,6 +22,7 @@ import java.util.List;
|
|||||||
public class AlbumBottomSheetViewModel extends AndroidViewModel {
|
public class AlbumBottomSheetViewModel extends AndroidViewModel {
|
||||||
private final AlbumRepository albumRepository;
|
private final AlbumRepository albumRepository;
|
||||||
private final ArtistRepository artistRepository;
|
private final ArtistRepository artistRepository;
|
||||||
|
private final FavoriteRepository favoriteRepository;
|
||||||
|
|
||||||
private AlbumID3 album;
|
private AlbumID3 album;
|
||||||
|
|
||||||
@@ -27,6 +31,7 @@ public class AlbumBottomSheetViewModel extends AndroidViewModel {
|
|||||||
|
|
||||||
albumRepository = new AlbumRepository();
|
albumRepository = new AlbumRepository();
|
||||||
artistRepository = new ArtistRepository();
|
artistRepository = new ArtistRepository();
|
||||||
|
favoriteRepository = new FavoriteRepository();
|
||||||
}
|
}
|
||||||
|
|
||||||
public AlbumID3 getAlbum() {
|
public AlbumID3 getAlbum() {
|
||||||
@@ -47,11 +52,51 @@ public class AlbumBottomSheetViewModel extends AndroidViewModel {
|
|||||||
|
|
||||||
public void setFavorite() {
|
public void setFavorite() {
|
||||||
if (album.getStarred() != null) {
|
if (album.getStarred() != null) {
|
||||||
artistRepository.unstar(album.getId());
|
if (NetworkUtil.isOffline()) {
|
||||||
album.setStarred(null);
|
removeFavoriteOffline();
|
||||||
|
} else {
|
||||||
|
removeFavoriteOnline();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
artistRepository.star(album.getId());
|
if (NetworkUtil.isOffline()) {
|
||||||
album.setStarred(new Date());
|
setFavoriteOffline();
|
||||||
|
} else {
|
||||||
|
setFavoriteOnline();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void removeFavoriteOffline() {
|
||||||
|
favoriteRepository.starLater(null, album.getId(), null, false);
|
||||||
|
album.setStarred(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeFavoriteOnline() {
|
||||||
|
favoriteRepository.unstar(null, album.getId(), null, new StarCallback() {
|
||||||
|
@Override
|
||||||
|
public void onError() {
|
||||||
|
// album.setStarred(new Date());
|
||||||
|
favoriteRepository.starLater(null, album.getId(), null, false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
album.setStarred(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setFavoriteOffline() {
|
||||||
|
favoriteRepository.starLater(null, album.getId(), null, true);
|
||||||
|
album.setStarred(new Date());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setFavoriteOnline() {
|
||||||
|
favoriteRepository.star(null, album.getId(), null, new StarCallback() {
|
||||||
|
@Override
|
||||||
|
public void onError() {
|
||||||
|
// album.setStarred(null);
|
||||||
|
favoriteRepository.starLater(null, album.getId(), null, true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
album.setStarred(new Date());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,20 +5,25 @@ import android.app.Application;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.lifecycle.AndroidViewModel;
|
import androidx.lifecycle.AndroidViewModel;
|
||||||
|
|
||||||
import com.cappielloantonio.tempo.repository.AlbumRepository;
|
import com.cappielloantonio.tempo.interfaces.StarCallback;
|
||||||
|
import com.cappielloantonio.tempo.repository.ArtistRepository;
|
||||||
|
import com.cappielloantonio.tempo.repository.FavoriteRepository;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
||||||
|
import com.cappielloantonio.tempo.util.NetworkUtil;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
public class ArtistBottomSheetViewModel extends AndroidViewModel {
|
public class ArtistBottomSheetViewModel extends AndroidViewModel {
|
||||||
private final AlbumRepository albumRepository;
|
private final ArtistRepository artistRepository;
|
||||||
|
private final FavoriteRepository favoriteRepository;
|
||||||
|
|
||||||
private ArtistID3 artist;
|
private ArtistID3 artist;
|
||||||
|
|
||||||
public ArtistBottomSheetViewModel(@NonNull Application application) {
|
public ArtistBottomSheetViewModel(@NonNull Application application) {
|
||||||
super(application);
|
super(application);
|
||||||
|
|
||||||
albumRepository = new AlbumRepository();
|
artistRepository = new ArtistRepository();
|
||||||
|
favoriteRepository = new FavoriteRepository();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ArtistID3 getArtist() {
|
public ArtistID3 getArtist() {
|
||||||
@@ -31,11 +36,51 @@ public class ArtistBottomSheetViewModel extends AndroidViewModel {
|
|||||||
|
|
||||||
public void setFavorite() {
|
public void setFavorite() {
|
||||||
if (artist.getStarred() != null) {
|
if (artist.getStarred() != null) {
|
||||||
albumRepository.unstar(artist.getId());
|
if (NetworkUtil.isOffline()) {
|
||||||
artist.setStarred(null);
|
removeFavoriteOffline();
|
||||||
|
} else {
|
||||||
|
removeFavoriteOnline();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
albumRepository.star(artist.getId());
|
if (NetworkUtil.isOffline()) {
|
||||||
artist.setStarred(new Date());
|
setFavoriteOffline();
|
||||||
|
} else {
|
||||||
|
setFavoriteOnline();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void removeFavoriteOffline() {
|
||||||
|
favoriteRepository.starLater(null, null, artist.getId(), false);
|
||||||
|
artist.setStarred(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeFavoriteOnline() {
|
||||||
|
favoriteRepository.unstar(null, null, artist.getId(), new StarCallback() {
|
||||||
|
@Override
|
||||||
|
public void onError() {
|
||||||
|
// artist.setStarred(new Date());
|
||||||
|
favoriteRepository.starLater(null, null, artist.getId(), false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
artist.setStarred(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setFavoriteOffline() {
|
||||||
|
favoriteRepository.starLater(null, null, artist.getId(), true);
|
||||||
|
artist.setStarred(new Date());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setFavoriteOnline() {
|
||||||
|
favoriteRepository.star(null, null, artist.getId(), new StarCallback() {
|
||||||
|
@Override
|
||||||
|
public void onError() {
|
||||||
|
// artist.setStarred(null);
|
||||||
|
favoriteRepository.starLater(null, null, artist.getId(), true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
artist.setStarred(new Date());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,7 @@ import android.app.Application;
|
|||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.lifecycle.AndroidViewModel;
|
import androidx.lifecycle.AndroidViewModel;
|
||||||
import androidx.lifecycle.LifecycleOwner;
|
|
||||||
import androidx.lifecycle.LiveData;
|
import androidx.lifecycle.LiveData;
|
||||||
import androidx.lifecycle.MutableLiveData;
|
|
||||||
|
|
||||||
import com.cappielloantonio.tempo.repository.DirectoryRepository;
|
import com.cappielloantonio.tempo.repository.DirectoryRepository;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.Directory;
|
import com.cappielloantonio.tempo.subsonic.models.Directory;
|
||||||
@@ -14,34 +12,13 @@ import com.cappielloantonio.tempo.subsonic.models.Directory;
|
|||||||
public class DirectoryViewModel extends AndroidViewModel {
|
public class DirectoryViewModel extends AndroidViewModel {
|
||||||
private final DirectoryRepository directoryRepository;
|
private final DirectoryRepository directoryRepository;
|
||||||
|
|
||||||
private MutableLiveData<String> id = new MutableLiveData<>(null);
|
|
||||||
private MutableLiveData<String> name = new MutableLiveData<>(null);
|
|
||||||
|
|
||||||
private MutableLiveData<Directory> directory = new MutableLiveData<>(null);
|
|
||||||
|
|
||||||
public DirectoryViewModel(@NonNull Application application) {
|
public DirectoryViewModel(@NonNull Application application) {
|
||||||
super(application);
|
super(application);
|
||||||
|
|
||||||
directoryRepository = new DirectoryRepository();
|
directoryRepository = new DirectoryRepository();
|
||||||
}
|
}
|
||||||
|
|
||||||
public LiveData<Directory> getDirectory() {
|
public LiveData<Directory> loadMusicDirectory(String id) {
|
||||||
return directory;
|
return directoryRepository.getMusicDirectory(id);
|
||||||
}
|
|
||||||
|
|
||||||
public void setMusicDirectoryId(String id) {
|
|
||||||
this.id.setValue(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMusicDirectoryName(String name) {
|
|
||||||
this.name.setValue(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void loadMusicDirectory(LifecycleOwner owner) {
|
|
||||||
this.id.observe(owner, id -> directoryRepository.getMusicDirectory(id).observe(owner, directory -> this.directory.setValue(directory)));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void goBack() {
|
|
||||||
this.id.setValue(this.directory.getValue().getParentId());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,27 +8,56 @@ import androidx.lifecycle.LifecycleOwner;
|
|||||||
import androidx.lifecycle.LiveData;
|
import androidx.lifecycle.LiveData;
|
||||||
import androidx.lifecycle.MutableLiveData;
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
|
||||||
|
import com.cappielloantonio.tempo.model.DownloadStack;
|
||||||
import com.cappielloantonio.tempo.repository.DownloadRepository;
|
import com.cappielloantonio.tempo.repository.DownloadRepository;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||||
|
import com.cappielloantonio.tempo.util.Constants;
|
||||||
|
import com.cappielloantonio.tempo.util.Preferences;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class DownloadViewModel extends AndroidViewModel {
|
public class DownloadViewModel extends AndroidViewModel {
|
||||||
private static final String TAG = "HomeViewModel";
|
private static final String TAG = "DownloadViewModel";
|
||||||
|
|
||||||
private final DownloadRepository downloadRepository;
|
private final DownloadRepository downloadRepository;
|
||||||
|
|
||||||
private final MutableLiveData<List<Child>> downloadedTrackSample = new MutableLiveData<>(null);
|
private final MutableLiveData<List<Child>> downloadedTrackSample = new MutableLiveData<>(null);
|
||||||
|
private final MutableLiveData<ArrayList<DownloadStack>> viewStack = new MutableLiveData<>(null);
|
||||||
|
|
||||||
public DownloadViewModel(@NonNull Application application) {
|
public DownloadViewModel(@NonNull Application application) {
|
||||||
super(application);
|
super(application);
|
||||||
|
|
||||||
downloadRepository = new DownloadRepository();
|
downloadRepository = new DownloadRepository();
|
||||||
|
|
||||||
|
initViewStack(new DownloadStack(Preferences.getDefaultDownloadViewType(), null));
|
||||||
}
|
}
|
||||||
|
|
||||||
public LiveData<List<Child>> getDownloadedTracks(LifecycleOwner owner) {
|
public LiveData<List<Child>> getDownloadedTracks(LifecycleOwner owner) {
|
||||||
downloadRepository.getLiveDownload().observe(owner, downloads -> downloadedTrackSample.postValue(downloads.stream().map(download -> (Child) download).collect(Collectors.toList())));
|
downloadRepository.getLiveDownload().observe(owner, downloads -> downloadedTrackSample.postValue(downloads.stream().map(download -> (Child) download).collect(Collectors.toList())));
|
||||||
return downloadedTrackSample;
|
return downloadedTrackSample;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public LiveData<ArrayList<DownloadStack>> getViewStack() {
|
||||||
|
return viewStack;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void initViewStack(DownloadStack level) {
|
||||||
|
ArrayList<DownloadStack> stack = new ArrayList<>();
|
||||||
|
stack.add(level);
|
||||||
|
viewStack.setValue(stack);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void pushViewStack(DownloadStack level) {
|
||||||
|
ArrayList<DownloadStack> stack = viewStack.getValue();
|
||||||
|
stack.add(level);
|
||||||
|
viewStack.setValue(stack);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void popViewStack() {
|
||||||
|
ArrayList<DownloadStack> stack = viewStack.getValue();
|
||||||
|
stack.remove(stack.size() - 1);
|
||||||
|
viewStack.setValue(stack);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,20 +8,24 @@ import androidx.lifecycle.LifecycleOwner;
|
|||||||
import androidx.lifecycle.LiveData;
|
import androidx.lifecycle.LiveData;
|
||||||
import androidx.lifecycle.MutableLiveData;
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
|
||||||
|
import com.cappielloantonio.tempo.interfaces.StarCallback;
|
||||||
import com.cappielloantonio.tempo.model.Chronology;
|
import com.cappielloantonio.tempo.model.Chronology;
|
||||||
|
import com.cappielloantonio.tempo.model.Favorite;
|
||||||
import com.cappielloantonio.tempo.repository.AlbumRepository;
|
import com.cappielloantonio.tempo.repository.AlbumRepository;
|
||||||
import com.cappielloantonio.tempo.repository.ArtistRepository;
|
import com.cappielloantonio.tempo.repository.ArtistRepository;
|
||||||
import com.cappielloantonio.tempo.repository.ChronologyRepository;
|
import com.cappielloantonio.tempo.repository.ChronologyRepository;
|
||||||
import com.cappielloantonio.tempo.repository.PodcastRepository;
|
import com.cappielloantonio.tempo.repository.FavoriteRepository;
|
||||||
import com.cappielloantonio.tempo.repository.SongRepository;
|
import com.cappielloantonio.tempo.repository.SongRepository;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
|
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||||
import com.cappielloantonio.tempo.util.Preferences;
|
import com.cappielloantonio.tempo.util.Preferences;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class HomeViewModel extends AndroidViewModel {
|
public class HomeViewModel extends AndroidViewModel {
|
||||||
@@ -31,6 +35,7 @@ public class HomeViewModel extends AndroidViewModel {
|
|||||||
private final AlbumRepository albumRepository;
|
private final AlbumRepository albumRepository;
|
||||||
private final ArtistRepository artistRepository;
|
private final ArtistRepository artistRepository;
|
||||||
private final ChronologyRepository chronologyRepository;
|
private final ChronologyRepository chronologyRepository;
|
||||||
|
private final FavoriteRepository favoriteRepository;
|
||||||
|
|
||||||
private final MutableLiveData<List<Child>> dicoverSongSample = new MutableLiveData<>(null);
|
private final MutableLiveData<List<Child>> dicoverSongSample = new MutableLiveData<>(null);
|
||||||
private final MutableLiveData<List<AlbumID3>> newReleasedAlbum = new MutableLiveData<>(null);
|
private final MutableLiveData<List<AlbumID3>> newReleasedAlbum = new MutableLiveData<>(null);
|
||||||
@@ -57,6 +62,9 @@ public class HomeViewModel extends AndroidViewModel {
|
|||||||
albumRepository = new AlbumRepository();
|
albumRepository = new AlbumRepository();
|
||||||
artistRepository = new ArtistRepository();
|
artistRepository = new ArtistRepository();
|
||||||
chronologyRepository = new ChronologyRepository();
|
chronologyRepository = new ChronologyRepository();
|
||||||
|
favoriteRepository = new FavoriteRepository();
|
||||||
|
|
||||||
|
setOfflineFavorite();
|
||||||
}
|
}
|
||||||
|
|
||||||
public LiveData<List<Child>> getDiscoverSongSample(LifecycleOwner owner) {
|
public LiveData<List<Child>> getDiscoverSongSample(LifecycleOwner owner) {
|
||||||
@@ -67,6 +75,10 @@ public class HomeViewModel extends AndroidViewModel {
|
|||||||
return dicoverSongSample;
|
return dicoverSongSample;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public LiveData<List<Child>> getRandomShuffleSample() {
|
||||||
|
return songRepository.getRandomSample(100, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
public LiveData<List<Chronology>> getGridSongSample(LifecycleOwner owner) {
|
public LiveData<List<Chronology>> getGridSongSample(LifecycleOwner owner) {
|
||||||
String server = Preferences.getServerId();
|
String server = Preferences.getServerId();
|
||||||
chronologyRepository.getLastWeek(server).observe(owner, thisGridTopSong::postValue);
|
chronologyRepository.getLastWeek(server).observe(owner, thisGridTopSong::postValue);
|
||||||
@@ -235,4 +247,109 @@ public class HomeViewModel extends AndroidViewModel {
|
|||||||
public void refreshRecentlyPlayedAlbumList(LifecycleOwner owner) {
|
public void refreshRecentlyPlayedAlbumList(LifecycleOwner owner) {
|
||||||
albumRepository.getAlbums("recent", 20, null, null).observe(owner, recentlyPlayedAlbumSample::postValue);
|
albumRepository.getAlbums("recent", 20, null, null).observe(owner, recentlyPlayedAlbumSample::postValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setOfflineFavorite() {
|
||||||
|
ArrayList<Favorite> favorites = getFavorites();
|
||||||
|
ArrayList<Favorite> favoritesToSave = getFavoritesToSave(favorites);
|
||||||
|
ArrayList<Favorite> favoritesToDelete = getFavoritesToDelete(favorites, favoritesToSave);
|
||||||
|
|
||||||
|
manageFavoriteToSave(favoritesToSave);
|
||||||
|
manageFavoriteToDelete(favoritesToDelete);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ArrayList<Favorite> getFavorites() {
|
||||||
|
return new ArrayList<>(favoriteRepository.getFavorites());
|
||||||
|
}
|
||||||
|
|
||||||
|
private ArrayList<Favorite> getFavoritesToSave(ArrayList<Favorite> favorites) {
|
||||||
|
HashMap<String, Favorite> filteredMap = new HashMap<>();
|
||||||
|
|
||||||
|
for (Favorite favorite : favorites) {
|
||||||
|
String key = favorite.toString();
|
||||||
|
|
||||||
|
if (!filteredMap.containsKey(key) || favorite.getTimestamp() > filteredMap.get(key).getTimestamp()) {
|
||||||
|
filteredMap.put(key, favorite);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ArrayList<>(filteredMap.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
private ArrayList<Favorite> getFavoritesToDelete(ArrayList<Favorite> favorites, ArrayList<Favorite> favoritesToSave) {
|
||||||
|
ArrayList<Favorite> favoritesToDelete = new ArrayList<>();
|
||||||
|
|
||||||
|
for (Favorite favorite : favorites) {
|
||||||
|
if (!favoritesToSave.contains(favorite)) {
|
||||||
|
favoritesToDelete.add(favorite);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return favoritesToDelete;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void manageFavoriteToSave(ArrayList<Favorite> favoritesToSave) {
|
||||||
|
for (Favorite favorite : favoritesToSave) {
|
||||||
|
if (favorite.getToStar()) {
|
||||||
|
favoriteToStar(favorite);
|
||||||
|
} else {
|
||||||
|
favoriteToUnstar(favorite);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void manageFavoriteToDelete(ArrayList<Favorite> favoritesToDelete) {
|
||||||
|
for (Favorite favorite : favoritesToDelete) {
|
||||||
|
favoriteRepository.delete(favorite);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void favoriteToStar(Favorite favorite) {
|
||||||
|
if (favorite.getSongId() != null) {
|
||||||
|
favoriteRepository.star(favorite.getSongId(), null, null, new StarCallback() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess() {
|
||||||
|
favoriteRepository.delete(favorite);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (favorite.getAlbumId() != null) {
|
||||||
|
favoriteRepository.star(null, favorite.getAlbumId(), null, new StarCallback() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess() {
|
||||||
|
favoriteRepository.delete(favorite);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (favorite.getArtistId() != null) {
|
||||||
|
favoriteRepository.star(null, null, favorite.getArtistId(), new StarCallback() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess() {
|
||||||
|
favoriteRepository.delete(favorite);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void favoriteToUnstar(Favorite favorite) {
|
||||||
|
if (favorite.getSongId() != null) {
|
||||||
|
favoriteRepository.unstar(favorite.getSongId(), null, null, new StarCallback() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess() {
|
||||||
|
favoriteRepository.delete(favorite);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (favorite.getAlbumId() != null) {
|
||||||
|
favoriteRepository.unstar(null, favorite.getAlbumId(), null, new StarCallback() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess() {
|
||||||
|
favoriteRepository.delete(favorite);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (favorite.getArtistId() != null) {
|
||||||
|
favoriteRepository.unstar(null, null, favorite.getArtistId(), new StarCallback() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess() {
|
||||||
|
favoriteRepository.delete(favorite);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,9 +11,11 @@ import androidx.lifecycle.LiveData;
|
|||||||
import androidx.lifecycle.MutableLiveData;
|
import androidx.lifecycle.MutableLiveData;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
|
||||||
|
import com.cappielloantonio.tempo.interfaces.StarCallback;
|
||||||
import com.cappielloantonio.tempo.model.Download;
|
import com.cappielloantonio.tempo.model.Download;
|
||||||
import com.cappielloantonio.tempo.model.Queue;
|
import com.cappielloantonio.tempo.model.Queue;
|
||||||
import com.cappielloantonio.tempo.repository.ArtistRepository;
|
import com.cappielloantonio.tempo.repository.ArtistRepository;
|
||||||
|
import com.cappielloantonio.tempo.repository.FavoriteRepository;
|
||||||
import com.cappielloantonio.tempo.repository.QueueRepository;
|
import com.cappielloantonio.tempo.repository.QueueRepository;
|
||||||
import com.cappielloantonio.tempo.repository.SongRepository;
|
import com.cappielloantonio.tempo.repository.SongRepository;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
||||||
@@ -22,6 +24,7 @@ import com.cappielloantonio.tempo.subsonic.models.PlayQueue;
|
|||||||
import com.cappielloantonio.tempo.util.Constants;
|
import com.cappielloantonio.tempo.util.Constants;
|
||||||
import com.cappielloantonio.tempo.util.DownloadUtil;
|
import com.cappielloantonio.tempo.util.DownloadUtil;
|
||||||
import com.cappielloantonio.tempo.util.MappingUtil;
|
import com.cappielloantonio.tempo.util.MappingUtil;
|
||||||
|
import com.cappielloantonio.tempo.util.NetworkUtil;
|
||||||
import com.cappielloantonio.tempo.util.Preferences;
|
import com.cappielloantonio.tempo.util.Preferences;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -36,6 +39,7 @@ public class PlayerBottomSheetViewModel extends AndroidViewModel {
|
|||||||
private final SongRepository songRepository;
|
private final SongRepository songRepository;
|
||||||
private final ArtistRepository artistRepository;
|
private final ArtistRepository artistRepository;
|
||||||
private final QueueRepository queueRepository;
|
private final QueueRepository queueRepository;
|
||||||
|
private final FavoriteRepository favoriteRepository;
|
||||||
|
|
||||||
private final MutableLiveData<String> lyricsLiveData = new MutableLiveData<>(null);
|
private final MutableLiveData<String> lyricsLiveData = new MutableLiveData<>(null);
|
||||||
|
|
||||||
@@ -50,6 +54,7 @@ public class PlayerBottomSheetViewModel extends AndroidViewModel {
|
|||||||
songRepository = new SongRepository();
|
songRepository = new SongRepository();
|
||||||
artistRepository = new ArtistRepository();
|
artistRepository = new ArtistRepository();
|
||||||
queueRepository = new QueueRepository();
|
queueRepository = new QueueRepository();
|
||||||
|
favoriteRepository = new FavoriteRepository();
|
||||||
}
|
}
|
||||||
|
|
||||||
public LiveData<List<Queue>> getQueueSong() {
|
public LiveData<List<Queue>> getQueueSong() {
|
||||||
@@ -59,22 +64,62 @@ public class PlayerBottomSheetViewModel extends AndroidViewModel {
|
|||||||
public void setFavorite(Context context, Child media) {
|
public void setFavorite(Context context, Child media) {
|
||||||
if (media != null) {
|
if (media != null) {
|
||||||
if (media.getStarred() != null) {
|
if (media.getStarred() != null) {
|
||||||
songRepository.unstar(media.getId());
|
if (NetworkUtil.isOffline()) {
|
||||||
media.setStarred(null);
|
removeFavoriteOffline(media);
|
||||||
|
} else {
|
||||||
|
removeFavoriteOnline(media);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
songRepository.star(media.getId());
|
if (NetworkUtil.isOffline()) {
|
||||||
media.setStarred(new Date());
|
setFavoriteOffline(media);
|
||||||
|
} else {
|
||||||
if (Preferences.isStarredSyncEnabled()) {
|
setFavoriteOnline(context, media);
|
||||||
DownloadUtil.getDownloadTracker(context).download(
|
|
||||||
MappingUtil.mapDownload(media),
|
|
||||||
new Download(media)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void removeFavoriteOffline(Child media) {
|
||||||
|
favoriteRepository.starLater(media.getId(), null, null, false);
|
||||||
|
media.setStarred(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeFavoriteOnline(Child media) {
|
||||||
|
favoriteRepository.unstar(media.getId(), null, null, new StarCallback() {
|
||||||
|
@Override
|
||||||
|
public void onError() {
|
||||||
|
// media.setStarred(new Date());
|
||||||
|
favoriteRepository.starLater(media.getId(), null, null, false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
media.setStarred(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setFavoriteOffline(Child media) {
|
||||||
|
favoriteRepository.starLater(media.getId(), null, null, true);
|
||||||
|
media.setStarred(new Date());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setFavoriteOnline(Context context, Child media) {
|
||||||
|
favoriteRepository.star(media.getId(), null, null, new StarCallback() {
|
||||||
|
@Override
|
||||||
|
public void onError() {
|
||||||
|
// media.setStarred(null);
|
||||||
|
favoriteRepository.starLater(media.getId(), null, null, true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
media.setStarred(new Date());
|
||||||
|
|
||||||
|
if (Preferences.isStarredSyncEnabled()) {
|
||||||
|
DownloadUtil.getDownloadTracker(context).download(
|
||||||
|
MappingUtil.mapDownload(media),
|
||||||
|
new Download(media)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public LiveData<String> getLiveLyrics() {
|
public LiveData<String> getLiveLyrics() {
|
||||||
return lyricsLiveData;
|
return lyricsLiveData;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,8 +37,7 @@ public class PlaylistEditorViewModel extends AndroidViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void updatePlaylist(String name) {
|
public void updatePlaylist(String name) {
|
||||||
playlistRepository.deletePlaylist(toEdit.getId());
|
playlistRepository.updatePlaylist(toEdit.getId(), name, getPlaylistSongIds());
|
||||||
playlistRepository.createPlaylist(toEdit.getId(), name, getPlaylistSongIds());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void deletePlaylist() {
|
public void deletePlaylist() {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import androidx.lifecycle.LiveData;
|
|||||||
|
|
||||||
import com.cappielloantonio.tempo.repository.PodcastRepository;
|
import com.cappielloantonio.tempo.repository.PodcastRepository;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.PodcastChannel;
|
import com.cappielloantonio.tempo.subsonic.models.PodcastChannel;
|
||||||
|
import com.cappielloantonio.tempo.subsonic.models.PodcastEpisode;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -33,4 +34,8 @@ public class PodcastChannelPageViewModel extends AndroidViewModel {
|
|||||||
public void setPodcastChannel(PodcastChannel podcastChannel) {
|
public void setPodcastChannel(PodcastChannel podcastChannel) {
|
||||||
this.podcastChannel = podcastChannel;
|
this.podcastChannel = podcastChannel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void requestPodcastEpisodeDownload(PodcastEpisode podcastEpisode) {
|
||||||
|
podcastRepository.downloadPodcastEpisode(podcastEpisode.getId());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import androidx.lifecycle.LiveData;
|
|||||||
|
|
||||||
import com.cappielloantonio.tempo.model.RecentSearch;
|
import com.cappielloantonio.tempo.model.RecentSearch;
|
||||||
import com.cappielloantonio.tempo.repository.SearchingRepository;
|
import com.cappielloantonio.tempo.repository.SearchingRepository;
|
||||||
|
import com.cappielloantonio.tempo.subsonic.models.SearchResult2;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.SearchResult3;
|
import com.cappielloantonio.tempo.subsonic.models.SearchResult3;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -38,8 +39,12 @@ public class SearchViewModel extends AndroidViewModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public LiveData<SearchResult3> search(String title) {
|
public LiveData<SearchResult2> search2(String title) {
|
||||||
return searchingRepository.search(title);
|
return searchingRepository.search2(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<SearchResult3> search3(String title) {
|
||||||
|
return searchingRepository.search3(title);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void insertNewSearch(String search) {
|
public void insertNewSearch(String search) {
|
||||||
@@ -56,7 +61,7 @@ public class SearchViewModel extends AndroidViewModel {
|
|||||||
|
|
||||||
public List<String> getRecentSearchSuggestion() {
|
public List<String> getRecentSearchSuggestion() {
|
||||||
ArrayList<String> suggestions = new ArrayList<>();
|
ArrayList<String> suggestions = new ArrayList<>();
|
||||||
suggestions.addAll(searchingRepository.getRecentSearchSuggestion(5));
|
suggestions.addAll(searchingRepository.getRecentSearchSuggestion());
|
||||||
|
|
||||||
return suggestions;
|
return suggestions;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,15 +10,18 @@ import androidx.lifecycle.LiveData;
|
|||||||
import androidx.lifecycle.MutableLiveData;
|
import androidx.lifecycle.MutableLiveData;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
|
||||||
|
import com.cappielloantonio.tempo.interfaces.StarCallback;
|
||||||
import com.cappielloantonio.tempo.model.Download;
|
import com.cappielloantonio.tempo.model.Download;
|
||||||
import com.cappielloantonio.tempo.repository.AlbumRepository;
|
import com.cappielloantonio.tempo.repository.AlbumRepository;
|
||||||
import com.cappielloantonio.tempo.repository.ArtistRepository;
|
import com.cappielloantonio.tempo.repository.ArtistRepository;
|
||||||
|
import com.cappielloantonio.tempo.repository.FavoriteRepository;
|
||||||
import com.cappielloantonio.tempo.repository.SongRepository;
|
import com.cappielloantonio.tempo.repository.SongRepository;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
|
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||||
import com.cappielloantonio.tempo.util.DownloadUtil;
|
import com.cappielloantonio.tempo.util.DownloadUtil;
|
||||||
import com.cappielloantonio.tempo.util.MappingUtil;
|
import com.cappielloantonio.tempo.util.MappingUtil;
|
||||||
|
import com.cappielloantonio.tempo.util.NetworkUtil;
|
||||||
import com.cappielloantonio.tempo.util.Preferences;
|
import com.cappielloantonio.tempo.util.Preferences;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -30,6 +33,7 @@ public class SongBottomSheetViewModel extends AndroidViewModel {
|
|||||||
private final SongRepository songRepository;
|
private final SongRepository songRepository;
|
||||||
private final AlbumRepository albumRepository;
|
private final AlbumRepository albumRepository;
|
||||||
private final ArtistRepository artistRepository;
|
private final ArtistRepository artistRepository;
|
||||||
|
private final FavoriteRepository favoriteRepository;
|
||||||
|
|
||||||
private Child song;
|
private Child song;
|
||||||
|
|
||||||
@@ -41,6 +45,7 @@ public class SongBottomSheetViewModel extends AndroidViewModel {
|
|||||||
songRepository = new SongRepository();
|
songRepository = new SongRepository();
|
||||||
albumRepository = new AlbumRepository();
|
albumRepository = new AlbumRepository();
|
||||||
artistRepository = new ArtistRepository();
|
artistRepository = new ArtistRepository();
|
||||||
|
favoriteRepository = new FavoriteRepository();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Child getSong() {
|
public Child getSong() {
|
||||||
@@ -53,18 +58,58 @@ public class SongBottomSheetViewModel extends AndroidViewModel {
|
|||||||
|
|
||||||
public void setFavorite(Context context) {
|
public void setFavorite(Context context) {
|
||||||
if (song.getStarred() != null) {
|
if (song.getStarred() != null) {
|
||||||
songRepository.unstar(song.getId());
|
if (NetworkUtil.isOffline()) {
|
||||||
song.setStarred(null);
|
removeFavoriteOffline(song);
|
||||||
} else {
|
} else {
|
||||||
songRepository.star(song.getId());
|
removeFavoriteOnline(song);
|
||||||
song.setStarred(new Date());
|
|
||||||
|
|
||||||
if (Preferences.isStarredSyncEnabled()) {
|
|
||||||
DownloadUtil.getDownloadTracker(context).download(
|
|
||||||
MappingUtil.mapDownload(song),
|
|
||||||
new Download(song)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if (NetworkUtil.isOffline()) {
|
||||||
|
setFavoriteOffline(song);
|
||||||
|
} else {
|
||||||
|
setFavoriteOnline(context, song);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeFavoriteOffline(Child media) {
|
||||||
|
favoriteRepository.starLater(media.getId(), null, null, false);
|
||||||
|
media.setStarred(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeFavoriteOnline(Child media) {
|
||||||
|
favoriteRepository.unstar(media.getId(), null, null, new StarCallback() {
|
||||||
|
@Override
|
||||||
|
public void onError() {
|
||||||
|
// media.setStarred(new Date());
|
||||||
|
favoriteRepository.starLater(media.getId(), null, null, false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
media.setStarred(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setFavoriteOffline(Child media) {
|
||||||
|
favoriteRepository.starLater(media.getId(), null, null, true);
|
||||||
|
media.setStarred(new Date());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setFavoriteOnline(Context context, Child media) {
|
||||||
|
favoriteRepository.star(media.getId(), null, null, new StarCallback() {
|
||||||
|
@Override
|
||||||
|
public void onError() {
|
||||||
|
// media.setStarred(null);
|
||||||
|
favoriteRepository.starLater(media.getId(), null, null, true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
media.setStarred(new Date());
|
||||||
|
|
||||||
|
if (Preferences.isStarredSyncEnabled()) {
|
||||||
|
DownloadUtil.getDownloadTracker(context).download(
|
||||||
|
MappingUtil.mapDownload(media),
|
||||||
|
new Download(media)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -71,11 +71,17 @@ public class SongListPageViewModel extends AndroidViewModel {
|
|||||||
public void getSongsByPage(LifecycleOwner owner) {
|
public void getSongsByPage(LifecycleOwner owner) {
|
||||||
switch (title) {
|
switch (title) {
|
||||||
case Constants.MEDIA_BY_GENRE:
|
case Constants.MEDIA_BY_GENRE:
|
||||||
int page = (songList.getValue() != null ? songList.getValue().size() : 0) / 100;
|
int songCount = songList.getValue() != null ? songList.getValue().size() : 0;
|
||||||
|
|
||||||
|
if (songCount > 0 && songCount % 100 != 0) return;
|
||||||
|
|
||||||
|
int page = songCount / 100;
|
||||||
songRepository.getSongsByGenre(genre.getGenre(), page).observe(owner, children -> {
|
songRepository.getSongsByGenre(genre.getGenre(), page).observe(owner, children -> {
|
||||||
List<Child> currentMedia = songList.getValue();
|
if (children != null && !children.isEmpty()) {
|
||||||
currentMedia.addAll(children);
|
List<Child> currentMedia = songList.getValue();
|
||||||
songList.setValue(currentMedia);
|
currentMedia.addAll(children);
|
||||||
|
songList.setValue(currentMedia);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case Constants.MEDIA_BY_ARTIST:
|
case Constants.MEDIA_BY_ARTIST:
|
||||||
|
|||||||
15
app/src/main/res/drawable/fast_scrollbar_bubble.xml
Normal file
15
app/src/main/res/drawable/fast_scrollbar_bubble.xml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
|
||||||
|
<corners
|
||||||
|
android:bottomLeftRadius="44dp"
|
||||||
|
android:bottomRightRadius="0px"
|
||||||
|
android:topLeftRadius="44dp"
|
||||||
|
android:topRightRadius="44dp" />
|
||||||
|
|
||||||
|
<solid android:color="?attr/colorPrimary" />
|
||||||
|
<size
|
||||||
|
android:width="88dp"
|
||||||
|
android:height="88dp" />
|
||||||
|
</shape>
|
||||||
17
app/src/main/res/drawable/fast_scrollbar_handle.xml
Normal file
17
app/src/main/res/drawable/fast_scrollbar_handle.xml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:state_selected="true">
|
||||||
|
<shape android:shape="rectangle">
|
||||||
|
<corners android:radius="2dp" />
|
||||||
|
<solid android:color="?attr/colorPrimary" />
|
||||||
|
<size android:width="4dp" android:height="32dp" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<shape android:shape="rectangle">
|
||||||
|
<corners android:radius="2dp" />
|
||||||
|
<solid android:color="?attr/colorPrimaryContainer" />
|
||||||
|
<size android:width="4dp" android:height="32dp" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</selector>
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="24dp"
|
android:width="24dp"
|
||||||
android:height="24dp"
|
android:height="24dp"
|
||||||
android:tint="?attr/colorControlNormal"
|
android:viewportWidth="960"
|
||||||
android:viewportWidth="24"
|
android:viewportHeight="960">
|
||||||
android:viewportHeight="24">
|
|
||||||
<path
|
<path
|
||||||
android:fillColor="@android:color/white"
|
android:fillColor="@color/titleTextColor"
|
||||||
android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z" />
|
android:pathData="M256,760L200,704L424,480L200,256L256,200L480,424L704,200L760,256L536,480L760,704L704,760L480,536L256,760Z"/>
|
||||||
</vector>
|
</vector>
|
||||||
9
app/src/main/res/drawable/ic_history.xml
Normal file
9
app/src/main/res/drawable/ic_history.xml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="960"
|
||||||
|
android:viewportHeight="960">
|
||||||
|
<path
|
||||||
|
android:fillColor="@color/titleTextColor"
|
||||||
|
android:pathData="M480,840Q342,840 239.5,748.5Q137,657 122,520L204,520Q218,624 296.5,692Q375,760 480,760Q597,760 678.5,678.5Q760,597 760,480Q760,363 678.5,281.5Q597,200 480,200Q411,200 351,232Q291,264 250,320L360,320L360,400L120,400L120,160L200,160L200,254Q251,190 324.5,155Q398,120 480,120Q555,120 620.5,148.5Q686,177 734.5,225.5Q783,274 811.5,339.5Q840,405 840,480Q840,555 811.5,620.5Q783,686 734.5,734.5Q686,783 620.5,811.5Q555,840 480,840ZM592,648L440,496L440,280L520,280L520,464L648,592L592,648Z"/>
|
||||||
|
</vector>
|
||||||
9
app/src/main/res/drawable/ic_podcast_download.xml
Normal file
9
app/src/main/res/drawable/ic_podcast_download.xml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="960"
|
||||||
|
android:viewportHeight="960">
|
||||||
|
<path
|
||||||
|
android:fillColor="@color/titleTextColor"
|
||||||
|
android:pathData="M439,878Q363,870 297.5,835.5Q232,801 184,747.5Q136,694 108.5,625Q81,556 81,479Q81,324 183.5,210.5Q286,97 440,80L440,160Q319,177 240,267.5Q161,358 161,479Q161,600 240,690.5Q319,781 439,798L439,878ZM479,680L278,478L335,421L439,525L439,280L519,280L519,525L622,422L679,480L479,680ZM519,878L519,798Q562,792 601.5,775Q641,758 675,732L733,790Q686,827 632,849.5Q578,872 519,878ZM677,226Q642,200 602.5,183Q563,166 520,160L520,80Q579,86 633,108.5Q687,131 733,168L677,226ZM789,732L733,675Q759,641 775,601.5Q791,562 797,519L879,519Q871,578 849,632.5Q827,687 789,732ZM797,439Q791,396 775,356.5Q759,317 733,283L789,226Q827,271 850,325.5Q873,380 879,439L797,439Z"/>
|
||||||
|
</vector>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user