diff --git a/.github/workflows/github_release.yml b/.github/workflows/github_release.yml
index afa74d8b..b116f3b2 100644
--- a/.github/workflows/github_release.yml
+++ b/.github/workflows/github_release.yml
@@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Setup JDK 17
- uses: actions/setup-java@v2
+ uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: '17'
@@ -28,6 +28,13 @@ jobs:
- name: Make gradlew executable
run: chmod +x ./gradlew
+ - name: Setup build tool version variable
+ shell: bash
+ run: |
+ BUILD_TOOL_VERSION=$(ls /usr/local/lib/android/sdk/build-tools/ | tail -n 1)
+ echo "BUILD_TOOL_VERSION=$BUILD_TOOL_VERSION" >> $GITHUB_ENV
+ echo Last build tool version is: $BUILD_TOOL_VERSION
+
- name: Build APK
id: build
run: bash ./gradlew assembleTempoRelease
@@ -41,6 +48,8 @@ jobs:
alias: ${{ secrets.KEY_ALIAS_GITHUB }}
keyStorePassword: ${{ secrets.KEYSTORE_PASSWORD }}
keyPassword: ${{ secrets.KEY_PASSWORD_GITHUB }}
+ env:
+ BUILD_TOOLS_VERSION: ${{ env.BUILD_TOOL_VERSION }}
- name: Make artifact
uses: actions/upload-artifact@v2
@@ -48,34 +57,6 @@ jobs:
name: app-release-signed
path: ${{steps.sign_apk.outputs.signedReleaseFile}}
-# - name: Build AAB
-# run: bash ./gradlew bundleRelease
-
-# - name: Sign AAB
-# id: sign_aab
-# uses: r0adkll/sign-android-release@v1
-# with:
-# releaseDirectory: app/build/outputs/bundle/release
-# signingKeyBase64: ${{ secrets.KEYSTORE_BASE64 }}
-# alias: ${{ secrets.KEY_ALIAS_GITHUB }}
-# keyStorePassword: ${{ secrets.KEYSTORE_PASSWORD }}
-# keyPassword: ${{ secrets.KEY_PASSWORD_GITHUB }}
-
-# - name: Make artifact
-# uses: actions/upload-artifact@v2
-# with:
-# name: app-release-signed
-# path: ${{steps.sign_aab.outputs.signedReleaseFile}}
-
-# - name: Build Changelog
-# id: changelog
-# uses: ardalanamini/auto-changelog@v3
-# with:
-# mention-authors: false
-# mention-new-contributors: false
-# include-compare: false
-# semver: false
-
- name: Create Release
id: create_release
uses: actions/create-release@v1
@@ -83,7 +64,6 @@ jobs:
tag_name: ${{ github.ref }}
release_name: Release v${{ github.ref }}
body: '> Changelog coming soon'
-# body: ${{ steps.changelog.outputs.changelog }}
env:
GITHUB_TOKEN: ${{ github.token }}
@@ -96,13 +76,3 @@ jobs:
asset_path: ${{steps.sign_apk.outputs.signedReleaseFile}}
asset_name: app-tempo-release.apk
asset_content_type: application/zip
-
-# - name: Upload AAB
-# uses: actions/upload-release-asset@v1
-# env:
-# GITHUB_TOKEN: ${{ github.token }}
-# with:
-# upload_url: ${{ steps.create_release.outputs.upload_url }}
-# asset_path: ${{steps.sign_aab.outputs.signedReleaseFile}}
-# asset_name: app-release.aab
-# asset_content_type: application/zip
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index a33cee77..0897082f 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -4,16 +4,15 @@
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 3467fa48..d3c08545 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -191,7 +191,7 @@
-
+
diff --git a/README.md b/README.md
index 1380fb2b..349c9187 100644
--- a/README.md
+++ b/README.md
@@ -6,6 +6,12 @@
Access your music library on all your android devices
+
+
+
+
+
+
**Tempo** is an open-source and lightweight music client for Subsonic, designed and built natively for Android. It provides a seamless and intuitive music streaming experience, allowing you to access and play your Subsonic music library directly from your Android device.
Tempo does not rely on magic algorithms to decide what you should listen to. Instead, the interface is built around your listening history, randomness, and optionally integrates with services like Last.fm to personalize your music experience.
diff --git a/app/build.gradle b/app/build.gradle
index 7267fd4a..2e421ade 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -4,12 +4,15 @@ apply plugin: 'kotlin-parcelize'
android {
compileSdk = 34
- buildToolsVersion = "34.0.0"
+ buildToolsVersion = '34.0.0'
defaultConfig {
minSdkVersion 24
targetSdkVersion 34
+ versionCode 25
+ versionName '3.8.1'
+
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
javaCompileOptions {
@@ -28,15 +31,11 @@ android {
tempo {
dimension = "default"
applicationId 'com.cappielloantonio.tempo'
- versionCode 22
- versionName '3.5.8'
}
notquitemy {
dimension = "default"
applicationId "com.cappielloantonio.notquitemy.tempo"
- versionCode 1
- versionName "1.0.0"
}
}
@@ -66,14 +65,12 @@ android {
}
dependencies {
- implementation fileTree(dir: 'libs', include: ['*.jar'])
-
// AndroidX
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.2.0'
implementation 'androidx.preference:preference-ktx:1.2.1'
- implementation 'androidx.navigation:navigation-fragment-ktx:2.7.5'
- implementation 'androidx.navigation:navigation-ui-ktx:2.7.5'
+ implementation 'androidx.navigation:navigation-fragment-ktx:2.7.7'
+ implementation 'androidx.navigation:navigation-ui-ktx:2.7.7'
implementation 'androidx.recyclerview:recyclerview:1.3.2'
implementation 'androidx.room:room-runtime:2.6.1'
implementation 'androidx.core:core-splashscreen:1.0.1'
@@ -87,17 +84,17 @@ dependencies {
implementation 'com.github.bumptech.glide:annotations:4.16.0'
// Media3
- implementation 'androidx.media3:media3-session:1.2.0'
- implementation 'androidx.media3:media3-common:1.2.0'
- implementation 'androidx.media3:media3-exoplayer:1.2.0'
- implementation 'androidx.media3:media3-ui:1.2.0'
- tempoImplementation 'androidx.media3:media3-cast:1.2.0'
+ implementation 'androidx.media3:media3-session:1.3.1'
+ implementation 'androidx.media3:media3-common:1.3.1'
+ implementation 'androidx.media3:media3-exoplayer:1.3.1'
+ implementation 'androidx.media3:media3-ui:1.3.1'
+ tempoImplementation 'androidx.media3:media3-cast:1.3.1'
annotationProcessor 'com.github.bumptech.glide:compiler:4.16.0'
annotationProcessor 'androidx.room:room-compiler:2.6.1'
// Retrofit
- implementation 'com.squareup.retrofit2:retrofit:2.9.0'
- implementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.11'
- implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
+ implementation 'com.squareup.retrofit2:retrofit:2.11.0'
+ implementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.14'
+ implementation 'com.squareup.retrofit2:converter-gson:2.11.0'
}
\ No newline at end of file
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
index 2ef25c5f..48b1f068 100644
--- a/app/proguard-rules.pro
+++ b/app/proguard-rules.pro
@@ -22,4 +22,7 @@
-keepattributes SourceFile, LineNumberTable
-keep public class * extends java.lang.Exception
--keep class retrofit2.** { *; }
\ No newline at end of file
+-keep class retrofit2.** { *; }
+
+-keep class **.reflect.TypeToken { *; }
+-keep class * extends **.reflect.TypeToken
\ No newline at end of file
diff --git a/app/schemas/com.cappielloantonio.tempo.database.AppDatabase/4.json b/app/schemas/com.cappielloantonio.tempo.database.AppDatabase/4.json
new file mode 100644
index 00000000..5356ab94
--- /dev/null
+++ b/app/schemas/com.cappielloantonio.tempo.database.AppDatabase/4.json
@@ -0,0 +1,997 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 4,
+ "identityHash": "528d037bee0f0575f8e0670ae1b04e00",
+ "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": []
+ },
+ {
+ "tableName": "session_media_item",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `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": "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, '528d037bee0f0575f8e0670ae1b04e00')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/app/schemas/com.cappielloantonio.tempo.database.AppDatabase/5.json b/app/schemas/com.cappielloantonio.tempo.database.AppDatabase/5.json
new file mode 100644
index 00000000..e8e7e86c
--- /dev/null
+++ b/app/schemas/com.cappielloantonio.tempo.database.AppDatabase/5.json
@@ -0,0 +1,1004 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 5,
+ "identityHash": "0e65e1c3fb44d9dc04c9c6cf35b7ea58",
+ "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": []
+ },
+ {
+ "tableName": "session_media_item",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `index` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL DEFAULT 0, `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)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "index",
+ "columnName": "index",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "0"
+ },
+ {
+ "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": true,
+ "columnNames": [
+ "index"
+ ]
+ },
+ "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, '0e65e1c3fb44d9dc04c9c6cf35b7ea58')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/app/schemas/com.cappielloantonio.tempo.database.AppDatabase/6.json b/app/schemas/com.cappielloantonio.tempo.database.AppDatabase/6.json
new file mode 100644
index 00000000..c6ccf4b7
--- /dev/null
+++ b/app/schemas/com.cappielloantonio.tempo.database.AppDatabase/6.json
@@ -0,0 +1,1016 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 6,
+ "identityHash": "dff788fb3b6ff922a1f566a9752c2029",
+ "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": []
+ },
+ {
+ "tableName": "session_media_item",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`index` INTEGER NOT NULL DEFAULT 0, `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, `stream_id` TEXT, `stream_url` TEXT, PRIMARY KEY(`index`))",
+ "fields": [
+ {
+ "fieldPath": "index",
+ "columnName": "index",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "0"
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "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
+ },
+ {
+ "fieldPath": "streamId",
+ "columnName": "stream_id",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "streamUrl",
+ "columnName": "stream_url",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "index"
+ ]
+ },
+ "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, 'dff788fb3b6ff922a1f566a9752c2029')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/app/schemas/com.cappielloantonio.tempo.database.AppDatabase/7.json b/app/schemas/com.cappielloantonio.tempo.database.AppDatabase/7.json
new file mode 100644
index 00000000..a3286def
--- /dev/null
+++ b/app/schemas/com.cappielloantonio.tempo.database.AppDatabase/7.json
@@ -0,0 +1,1021 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 7,
+ "identityHash": "cca7b016c047d8fdc86dd6373f2fb173",
+ "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": []
+ },
+ {
+ "tableName": "session_media_item",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`index` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `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, `stream_id` TEXT, `stream_url` TEXT, `timestamp` INTEGER)",
+ "fields": [
+ {
+ "fieldPath": "index",
+ "columnName": "index",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "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
+ },
+ {
+ "fieldPath": "streamId",
+ "columnName": "stream_id",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "streamUrl",
+ "columnName": "stream_url",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "timestamp",
+ "columnName": "timestamp",
+ "affinity": "INTEGER",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "index"
+ ]
+ },
+ "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, 'cca7b016c047d8fdc86dd6373f2fb173')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/app/schemas/com.cappielloantonio.tempo.database.AppDatabase/8.json b/app/schemas/com.cappielloantonio.tempo.database.AppDatabase/8.json
new file mode 100644
index 00000000..26ab81b1
--- /dev/null
+++ b/app/schemas/com.cappielloantonio.tempo.database.AppDatabase/8.json
@@ -0,0 +1,1021 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 8,
+ "identityHash": "cca7b016c047d8fdc86dd6373f2fb173",
+ "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": []
+ },
+ {
+ "tableName": "session_media_item",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`index` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `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, `stream_id` TEXT, `stream_url` TEXT, `timestamp` INTEGER)",
+ "fields": [
+ {
+ "fieldPath": "index",
+ "columnName": "index",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "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
+ },
+ {
+ "fieldPath": "streamId",
+ "columnName": "stream_id",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "streamUrl",
+ "columnName": "stream_url",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "timestamp",
+ "columnName": "timestamp",
+ "affinity": "INTEGER",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "index"
+ ]
+ },
+ "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, 'cca7b016c047d8fdc86dd6373f2fb173')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index b94766b9..986481bd 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -16,11 +16,16 @@
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:localeConfig="@xml/locale_config"
+ android:networkSecurityConfig="@xml/network_security_config"
android:roundIcon="@mipmap/ic_launcher"
android:supportsRtl="true"
android:theme="@style/AppTheme.SplashScreen"
- android:usesCleartextTraffic="true"
- android:networkSecurityConfig="@xml/network_security_config">
+ android:usesCleartextTraffic="true">
+
+
+
-
+
+
diff --git a/app/src/main/java/com/cappielloantonio/tempo/database/AppDatabase.java b/app/src/main/java/com/cappielloantonio/tempo/database/AppDatabase.java
index 7e516853..f656b3c4 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/database/AppDatabase.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/database/AppDatabase.java
@@ -14,17 +14,19 @@ import com.cappielloantonio.tempo.database.dao.FavoriteDao;
import com.cappielloantonio.tempo.database.dao.QueueDao;
import com.cappielloantonio.tempo.database.dao.RecentSearchDao;
import com.cappielloantonio.tempo.database.dao.ServerDao;
+import com.cappielloantonio.tempo.database.dao.SessionMediaItemDao;
import com.cappielloantonio.tempo.model.Chronology;
import com.cappielloantonio.tempo.model.Download;
import com.cappielloantonio.tempo.model.Favorite;
import com.cappielloantonio.tempo.model.Queue;
import com.cappielloantonio.tempo.model.RecentSearch;
import com.cappielloantonio.tempo.model.Server;
+import com.cappielloantonio.tempo.model.SessionMediaItem;
@Database(
- version = 3,
- entities = {Queue.class, Server.class, RecentSearch.class, Download.class, Chronology.class, Favorite.class},
- autoMigrations = {@AutoMigration(from = 2, to = 3)}
+ version = 8,
+ entities = {Queue.class, Server.class, RecentSearch.class, Download.class, Chronology.class, Favorite.class, SessionMediaItem.class},
+ autoMigrations = {@AutoMigration(from = 7, to = 8)}
)
@TypeConverters({DateConverters.class})
public abstract class AppDatabase extends RoomDatabase {
@@ -52,4 +54,6 @@ public abstract class AppDatabase extends RoomDatabase {
public abstract ChronologyDao chronologyDao();
public abstract FavoriteDao favoriteDao();
+
+ public abstract SessionMediaItemDao sessionMediaItemDao();
}
diff --git a/app/src/main/java/com/cappielloantonio/tempo/database/dao/ChronologyDao.java b/app/src/main/java/com/cappielloantonio/tempo/database/dao/ChronologyDao.java
index 573c8940..906f7efa 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/database/dao/ChronologyDao.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/database/dao/ChronologyDao.java
@@ -12,6 +12,9 @@ import java.util.List;
@Dao
public interface ChronologyDao {
+ @Query("SELECT * FROM chronology WHERE server == :server GROUP BY id ORDER BY timestamp DESC LIMIT :count")
+ LiveData> getLastPlayed(String server, int count);
+
@Query("SELECT * FROM chronology WHERE timestamp >= :startDate AND timestamp < :endDate AND server == :server GROUP BY id ORDER BY COUNT(id) DESC LIMIT 9")
LiveData> getAllFrom(long startDate, long endDate, String server);
diff --git a/app/src/main/java/com/cappielloantonio/tempo/database/dao/SessionMediaItemDao.java b/app/src/main/java/com/cappielloantonio/tempo/database/dao/SessionMediaItemDao.java
new file mode 100644
index 00000000..a3f415ad
--- /dev/null
+++ b/app/src/main/java/com/cappielloantonio/tempo/database/dao/SessionMediaItemDao.java
@@ -0,0 +1,29 @@
+package com.cappielloantonio.tempo.database.dao;
+
+import androidx.room.Dao;
+import androidx.room.Insert;
+import androidx.room.OnConflictStrategy;
+import androidx.room.Query;
+
+import com.cappielloantonio.tempo.model.Queue;
+import com.cappielloantonio.tempo.model.SessionMediaItem;
+
+import java.util.List;
+
+@Dao
+public interface SessionMediaItemDao {
+ @Query("SELECT * FROM session_media_item WHERE id = :id")
+ SessionMediaItem get(String id);
+
+ @Query("SELECT * FROM session_media_item WHERE timestamp = :timestamp")
+ List get(long timestamp);
+
+ @Insert(onConflict = OnConflictStrategy.IGNORE)
+ void insert(SessionMediaItem sessionMediaItem);
+
+ @Insert(onConflict = OnConflictStrategy.IGNORE)
+ void insertAll(List sessionMediaItems);
+
+ @Query("DELETE FROM session_media_item")
+ void deleteAll();
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/cappielloantonio/tempo/model/HomeSector.kt b/app/src/main/java/com/cappielloantonio/tempo/model/HomeSector.kt
new file mode 100644
index 00000000..79ca4356
--- /dev/null
+++ b/app/src/main/java/com/cappielloantonio/tempo/model/HomeSector.kt
@@ -0,0 +1,11 @@
+package com.cappielloantonio.tempo.model
+
+import androidx.annotation.Keep
+
+@Keep
+data class HomeSector(
+ val id: String,
+ val sectorTitle: String,
+ var isVisible: Boolean,
+ val order: Int,
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/cappielloantonio/tempo/model/SessionMediaItem.kt b/app/src/main/java/com/cappielloantonio/tempo/model/SessionMediaItem.kt
new file mode 100644
index 00000000..96e7ac6b
--- /dev/null
+++ b/app/src/main/java/com/cappielloantonio/tempo/model/SessionMediaItem.kt
@@ -0,0 +1,281 @@
+package com.cappielloantonio.tempo.model
+
+import android.net.Uri
+import android.os.Bundle
+import androidx.annotation.Keep
+import androidx.media3.common.MediaItem
+import androidx.media3.common.MediaItem.RequestMetadata
+import androidx.media3.common.MediaMetadata
+import androidx.media3.common.MimeTypes
+import androidx.media3.common.util.UnstableApi
+import androidx.room.ColumnInfo
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+import com.cappielloantonio.tempo.glide.CustomGlideRequest
+import com.cappielloantonio.tempo.subsonic.models.Child
+import com.cappielloantonio.tempo.subsonic.models.InternetRadioStation
+import com.cappielloantonio.tempo.subsonic.models.PodcastEpisode
+import com.cappielloantonio.tempo.util.Constants
+import com.cappielloantonio.tempo.util.MusicUtil
+import com.cappielloantonio.tempo.util.Preferences.getImageSize
+import java.util.Date
+
+@UnstableApi
+@Keep
+@Entity(tableName = "session_media_item")
+class SessionMediaItem() {
+ @PrimaryKey(autoGenerate = true)
+ @ColumnInfo(name = "index")
+ var index: Int = 0
+
+ @ColumnInfo(name = "id")
+ var id: String? = null
+
+ @ColumnInfo(name = "parent_id")
+ var parentId: String? = null
+
+ @ColumnInfo(name = "is_dir")
+ var isDir: Boolean = false
+
+ @ColumnInfo
+ var title: String? = null
+
+ @ColumnInfo
+ var album: String? = null
+
+ @ColumnInfo
+ var artist: String? = null
+
+ @ColumnInfo
+ var track: Int? = null
+
+ @ColumnInfo
+ var year: Int? = null
+
+ @ColumnInfo
+ var genre: String? = null
+
+ @ColumnInfo(name = "cover_art_id")
+ var coverArtId: String? = null
+
+ @ColumnInfo
+ var size: Long? = null
+
+ @ColumnInfo(name = "content_type")
+ var contentType: String? = null
+
+ @ColumnInfo
+ var suffix: String? = null
+
+ @ColumnInfo("transcoding_content_type")
+ var transcodedContentType: String? = null
+
+ @ColumnInfo(name = "transcoded_suffix")
+ var transcodedSuffix: String? = null
+
+ @ColumnInfo
+ var duration: Int? = null
+
+ @ColumnInfo("bitrate")
+ var bitrate: Int? = null
+
+ @ColumnInfo
+ var path: String? = null
+
+ @ColumnInfo(name = "is_video")
+ var isVideo: Boolean = false
+
+ @ColumnInfo(name = "user_rating")
+ var userRating: Int? = null
+
+ @ColumnInfo(name = "average_rating")
+ var averageRating: Double? = null
+
+ @ColumnInfo(name = "play_count")
+ var playCount: Long? = null
+
+ @ColumnInfo(name = "disc_number")
+ var discNumber: Int? = null
+
+ @ColumnInfo
+ var created: Date? = null
+
+ @ColumnInfo
+ var starred: Date? = null
+
+ @ColumnInfo(name = "album_id")
+ var albumId: String? = null
+
+ @ColumnInfo(name = "artist_id")
+ var artistId: String? = null
+
+ @ColumnInfo
+ var type: String? = null
+
+ @ColumnInfo(name = "bookmark_position")
+ var bookmarkPosition: Long? = null
+
+ @ColumnInfo(name = "original_width")
+ var originalWidth: Int? = null
+
+ @ColumnInfo(name = "original_height")
+ var originalHeight: Int? = null
+
+ @ColumnInfo(name = "stream_id")
+ var streamId: String? = null
+
+ @ColumnInfo(name = "stream_url")
+ var streamUrl: String? = null
+
+ @ColumnInfo(name = "timestamp")
+ var timestamp: Long? = null
+
+ constructor(child: Child) : this() {
+ id = child.id
+ parentId = child.parentId
+ isDir = child.isDir
+ title = child.title
+ album = child.album
+ artist = child.artist
+ track = child.track
+ year = child.year
+ genre = child.genre
+ coverArtId = child.coverArtId
+ size = child.size
+ contentType = child.contentType
+ suffix = child.suffix
+ transcodedContentType = child.transcodedContentType
+ transcodedSuffix = child.transcodedSuffix
+ duration = child.duration
+ bitrate = child.bitrate
+ path = child.path
+ isVideo = child.isVideo
+ userRating = child.userRating
+ averageRating = child.averageRating
+ playCount = child.playCount
+ discNumber = child.discNumber
+ created = child.created
+ starred = child.starred
+ albumId = child.albumId
+ artistId = child.artistId
+ type = Constants.MEDIA_TYPE_MUSIC
+ bookmarkPosition = child.bookmarkPosition
+ originalWidth = child.originalWidth
+ originalHeight = child.originalHeight
+ }
+
+ constructor(podcastEpisode: PodcastEpisode) : this() {
+ id = podcastEpisode.id
+ parentId = podcastEpisode.parentId
+ isDir = podcastEpisode.isDir
+ title = podcastEpisode.title
+ album = podcastEpisode.album
+ artist = podcastEpisode.artist
+ year = podcastEpisode.year
+ genre = podcastEpisode.genre
+ coverArtId = podcastEpisode.coverArtId
+ size = podcastEpisode.size
+ contentType = podcastEpisode.contentType
+ suffix = podcastEpisode.suffix
+ duration = podcastEpisode.duration
+ bitrate = podcastEpisode.bitrate
+ path = podcastEpisode.path
+ isVideo = podcastEpisode.isVideo
+ created = podcastEpisode.created
+ artistId = podcastEpisode.artistId
+ streamId = podcastEpisode.streamId
+ type = Constants.MEDIA_TYPE_PODCAST
+ }
+
+ constructor(internetRadioStation: InternetRadioStation) : this() {
+ id = internetRadioStation.id
+ title = internetRadioStation.name
+ streamUrl = internetRadioStation.streamUrl
+ type = Constants.MEDIA_TYPE_RADIO
+ }
+
+ fun getMediaItem(): MediaItem {
+ val uri: Uri = getStreamUri()
+ val artworkUri = Uri.parse(CustomGlideRequest.createUrl(coverArtId, getImageSize()))
+
+ val bundle = Bundle()
+ bundle.putString("id", id)
+ bundle.putString("parentId", parentId)
+ bundle.putBoolean("isDir", isDir)
+ bundle.putString("title", title)
+ bundle.putString("album", album)
+ bundle.putString("artist", artist)
+ bundle.putInt("track", track ?: 0)
+ bundle.putInt("year", year ?: 0)
+ bundle.putString("genre", genre)
+ bundle.putString("coverArtId", coverArtId)
+ bundle.putLong("size", size ?: 0)
+ bundle.putString("contentType", contentType)
+ bundle.putString("suffix", suffix)
+ bundle.putString("transcodedContentType", transcodedContentType)
+ bundle.putString("transcodedSuffix", transcodedSuffix)
+ bundle.putInt("duration", duration ?: 0)
+ bundle.putInt("bitrate", bitrate ?: 0)
+ bundle.putString("path", path)
+ bundle.putBoolean("isVideo", isVideo)
+ bundle.putInt("userRating", userRating ?: 0)
+ bundle.putDouble("averageRating", averageRating ?: .0)
+ bundle.putLong("playCount", playCount ?: 0)
+ bundle.putInt("discNumber", discNumber ?: 0)
+ bundle.putLong("created", created?.time ?: 0)
+ bundle.putLong("starred", starred?.time ?: 0)
+ bundle.putString("albumId", albumId)
+ bundle.putString("artistId", artistId)
+ bundle.putString("type", Constants.MEDIA_TYPE_MUSIC)
+ bundle.putLong("bookmarkPosition", bookmarkPosition ?: 0)
+ bundle.putInt("originalWidth", originalWidth ?: 0)
+ bundle.putInt("originalHeight", originalHeight ?: 0)
+ bundle.putString("uri", uri.toString())
+
+ return MediaItem.Builder()
+ .setMediaId(id!!)
+ .setMediaMetadata(
+ MediaMetadata.Builder()
+ .setTitle(MusicUtil.getReadableString(title))
+ .setTrackNumber(track ?: 0)
+ .setDiscNumber(discNumber ?: 0)
+ .setReleaseYear(year ?: 0)
+ .setAlbumTitle(MusicUtil.getReadableString(album))
+ .setArtist(MusicUtil.getReadableString(artist))
+ .setArtworkUri(artworkUri)
+ .setExtras(bundle)
+ .setIsBrowsable(false)
+ .setIsPlayable(true)
+ .build()
+ )
+ .setRequestMetadata(
+ RequestMetadata.Builder()
+ .setMediaUri(uri)
+ .setExtras(bundle)
+ .build()
+ )
+ .setMimeType(MimeTypes.BASE_TYPE_AUDIO)
+ .setUri(uri)
+ .build()
+ }
+
+ private fun getStreamUri(): Uri {
+ return when (type) {
+ Constants.MEDIA_TYPE_MUSIC -> {
+ MusicUtil.getStreamUri(id)
+ }
+
+ Constants.MEDIA_TYPE_PODCAST -> {
+ MusicUtil.getStreamUri(streamId)
+ }
+
+ Constants.MEDIA_TYPE_RADIO -> {
+ Uri.parse(streamUrl)
+ }
+
+ else -> {
+ MusicUtil.getStreamUri(id)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/cappielloantonio/tempo/repository/AlbumRepository.java b/app/src/main/java/com/cappielloantonio/tempo/repository/AlbumRepository.java
index 76a55220..6dc8d3e3 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/repository/AlbumRepository.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/repository/AlbumRepository.java
@@ -8,6 +8,7 @@ import com.cappielloantonio.tempo.interfaces.DecadesCallback;
import com.cappielloantonio.tempo.interfaces.MediaCallback;
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
+import com.cappielloantonio.tempo.subsonic.models.AlbumInfo;
import com.cappielloantonio.tempo.subsonic.models.Child;
import java.util.ArrayList;
@@ -131,9 +132,10 @@ public class AlbumRepository {
.enqueue(new Callback() {
@Override
public void onResponse(@NonNull Call call, @NonNull Response response) {
- if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getArtist() != null) {
+ if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getArtist() != null && response.body().getSubsonicResponse().getArtist().getAlbums() != null) {
List albums = response.body().getSubsonicResponse().getArtist().getAlbums();
albums.sort(Comparator.comparing(AlbumID3::getYear));
+ Collections.reverse(albums);
artistsAlbum.setValue(albums);
}
}
@@ -170,6 +172,29 @@ public class AlbumRepository {
return album;
}
+ public MutableLiveData getAlbumInfo(String id) {
+ MutableLiveData albumInfo = new MutableLiveData<>();
+
+ App.getSubsonicClientInstance(false)
+ .getBrowsingClient()
+ .getAlbumInfo2(id)
+ .enqueue(new Callback() {
+ @Override
+ public void onResponse(@NonNull Call call, @NonNull Response response) {
+ if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getAlbumInfo() != null) {
+ albumInfo.setValue(response.body().getSubsonicResponse().getAlbumInfo());
+ }
+ }
+
+ @Override
+ public void onFailure(@NonNull Call call, @NonNull Throwable t) {
+
+ }
+ });
+
+ return albumInfo;
+ }
+
public void getInstantMix(AlbumID3 album, int count, MediaCallback callback) {
App.getSubsonicClientInstance(false)
.getBrowsingClient()
@@ -250,7 +275,7 @@ public class AlbumRepository {
@Override
public void onResponse(@NonNull Call call, @NonNull Response response) {
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getAlbumList2() != null && response.body().getSubsonicResponse().getAlbumList2().getAlbums() != null) {
- if (response.body().getSubsonicResponse().getAlbumList2().getAlbums().size() > 0 && !response.body().getSubsonicResponse().getAlbumList2().getAlbums().isEmpty()) {
+ if (!response.body().getSubsonicResponse().getAlbumList2().getAlbums().isEmpty() && !response.body().getSubsonicResponse().getAlbumList2().getAlbums().isEmpty()) {
callback.onLoadYear(response.body().getSubsonicResponse().getAlbumList2().getAlbums().get(0).getYear());
} else {
callback.onLoadYear(-1);
diff --git a/app/src/main/java/com/cappielloantonio/tempo/repository/AutomotiveRepository.java b/app/src/main/java/com/cappielloantonio/tempo/repository/AutomotiveRepository.java
new file mode 100644
index 00000000..fe24d81d
--- /dev/null
+++ b/app/src/main/java/com/cappielloantonio/tempo/repository/AutomotiveRepository.java
@@ -0,0 +1,1027 @@
+package com.cappielloantonio.tempo.repository;
+
+
+import android.net.Uri;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.OptIn;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.MutableLiveData;
+import androidx.lifecycle.Observer;
+import androidx.media3.common.MediaItem;
+import androidx.media3.common.MediaMetadata;
+import androidx.media3.common.util.UnstableApi;
+import androidx.media3.session.LibraryResult;
+
+import com.cappielloantonio.tempo.App;
+import com.cappielloantonio.tempo.database.AppDatabase;
+import com.cappielloantonio.tempo.database.dao.ChronologyDao;
+import com.cappielloantonio.tempo.database.dao.SessionMediaItemDao;
+import com.cappielloantonio.tempo.glide.CustomGlideRequest;
+import com.cappielloantonio.tempo.model.Chronology;
+import com.cappielloantonio.tempo.model.Download;
+import com.cappielloantonio.tempo.model.SessionMediaItem;
+import com.cappielloantonio.tempo.service.DownloaderManager;
+import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
+import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
+import com.cappielloantonio.tempo.subsonic.models.Artist;
+import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
+import com.cappielloantonio.tempo.subsonic.models.Child;
+import com.cappielloantonio.tempo.subsonic.models.Directory;
+import com.cappielloantonio.tempo.subsonic.models.Index;
+import com.cappielloantonio.tempo.subsonic.models.InternetRadioStation;
+import com.cappielloantonio.tempo.subsonic.models.MusicFolder;
+import com.cappielloantonio.tempo.subsonic.models.Playlist;
+import com.cappielloantonio.tempo.subsonic.models.PodcastEpisode;
+import com.cappielloantonio.tempo.util.DownloadUtil;
+import com.cappielloantonio.tempo.util.MappingUtil;
+import com.cappielloantonio.tempo.util.MusicUtil;
+import com.cappielloantonio.tempo.util.Preferences;
+import com.google.common.collect.ImmutableList;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.SettableFuture;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import retrofit2.Call;
+import retrofit2.Callback;
+import retrofit2.Response;
+
+public class AutomotiveRepository {
+ private final SessionMediaItemDao sessionMediaItemDao = AppDatabase.getInstance().sessionMediaItemDao();
+ private final ChronologyDao chronologyDao = AppDatabase.getInstance().chronologyDao();
+
+ public ListenableFuture>> getAlbums(String prefix, String type, int size) {
+ final SettableFuture>> listenableFuture = SettableFuture.create();
+
+ App.getSubsonicClientInstance(false)
+ .getAlbumSongListClient()
+ .getAlbumList2(type, size, 0, null, null)
+ .enqueue(new Callback() {
+ @Override
+ public void onResponse(@NonNull Call call, @NonNull Response response) {
+ if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getAlbumList2() != null && response.body().getSubsonicResponse().getAlbumList2().getAlbums() != null) {
+ List albums = response.body().getSubsonicResponse().getAlbumList2().getAlbums();
+
+ List mediaItems = new ArrayList<>();
+
+ for (AlbumID3 album : albums) {
+ Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(album.getCoverArtId(), Preferences.getImageSize()));
+
+ MediaMetadata mediaMetadata = new MediaMetadata.Builder()
+ .setTitle(album.getName())
+ .setAlbumTitle(album.getName())
+ .setArtist(album.getArtist())
+ .setGenre(album.getGenre())
+ .setIsBrowsable(true)
+ .setIsPlayable(false)
+ .setMediaType(MediaMetadata.MEDIA_TYPE_ALBUM)
+ .setArtworkUri(artworkUri)
+ .build();
+
+ MediaItem mediaItem = new MediaItem.Builder()
+ .setMediaId(prefix + album.getId())
+ .setMediaMetadata(mediaMetadata)
+ .setUri("")
+ .build();
+
+ mediaItems.add(mediaItem);
+ }
+
+ LibraryResult> libraryResult = LibraryResult.ofItemList(ImmutableList.copyOf(mediaItems), null);
+
+ listenableFuture.set(libraryResult);
+ } else {
+ listenableFuture.set(LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE));
+ }
+ }
+
+ @Override
+ public void onFailure(@NonNull Call call, @NonNull Throwable t) {
+ listenableFuture.setException(t);
+ }
+ });
+
+ return listenableFuture;
+ }
+
+ public ListenableFuture>> getStarredSongs() {
+ final SettableFuture>> listenableFuture = SettableFuture.create();
+
+ App.getSubsonicClientInstance(false)
+ .getAlbumSongListClient()
+ .getStarred2()
+ .enqueue(new Callback() {
+ @Override
+ public void onResponse(@NonNull Call call, @NonNull Response response) {
+ if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getStarred2() != null && response.body().getSubsonicResponse().getStarred2().getSongs() != null) {
+ List songs = response.body().getSubsonicResponse().getStarred2().getSongs();
+
+ setChildrenMetadata(songs);
+
+ List mediaItems = MappingUtil.mapMediaItems(songs);
+
+ LibraryResult> libraryResult = LibraryResult.ofItemList(ImmutableList.copyOf(mediaItems), null);
+
+ listenableFuture.set(libraryResult);
+ } else {
+ listenableFuture.set(LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE));
+ }
+ }
+
+ @Override
+ public void onFailure(@NonNull Call call, @NonNull Throwable t) {
+ listenableFuture.setException(t);
+ }
+ });
+
+ return listenableFuture;
+ }
+
+ public ListenableFuture>> getRandomSongs(int count) {
+ final SettableFuture>> listenableFuture = SettableFuture.create();
+
+ App.getSubsonicClientInstance(false)
+ .getAlbumSongListClient()
+ .getRandomSongs(100, null, null)
+ .enqueue(new Callback() {
+ @Override
+ public void onResponse(@NonNull Call call, @NonNull Response response) {
+ if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getRandomSongs() != null && response.body().getSubsonicResponse().getRandomSongs().getSongs() != null) {
+ List songs = response.body().getSubsonicResponse().getRandomSongs().getSongs();
+
+ setChildrenMetadata(songs);
+
+ List mediaItems = MappingUtil.mapMediaItems(songs);
+
+ LibraryResult> libraryResult = LibraryResult.ofItemList(ImmutableList.copyOf(mediaItems), null);
+
+ listenableFuture.set(libraryResult);
+ } else {
+ listenableFuture.set(LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE));
+ }
+ }
+
+ @Override
+ public void onFailure(@NonNull Call call, @NonNull Throwable t) {
+ listenableFuture.setException(t);
+ }
+ });
+
+ return listenableFuture;
+ }
+
+ public ListenableFuture>> getRecentlyPlayedSongs(String server, int count) {
+ final SettableFuture>> listenableFuture = SettableFuture.create();
+
+ chronologyDao.getLastPlayed(server, count).observeForever(new Observer>() {
+ @Override
+ public void onChanged(List chronology) {
+ if (chronology != null && !chronology.isEmpty()) {
+ List songs = new ArrayList<>(chronology);
+
+ setChildrenMetadata(songs);
+
+ List mediaItems = MappingUtil.mapMediaItems(songs);
+
+ LibraryResult> libraryResult = LibraryResult.ofItemList(ImmutableList.copyOf(mediaItems), null);
+
+ listenableFuture.set(libraryResult);
+ } else {
+ listenableFuture.set(LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE));
+ }
+
+ chronologyDao.getLastPlayed(server, count).removeObserver(this);
+ }
+ });
+
+ return listenableFuture;
+ }
+
+ public ListenableFuture>> getStarredAlbums(String prefix) {
+ final SettableFuture>> listenableFuture = SettableFuture.create();
+
+ App.getSubsonicClientInstance(false)
+ .getAlbumSongListClient()
+ .getStarred2()
+ .enqueue(new Callback() {
+ @Override
+ public void onResponse(@NonNull Call call, @NonNull Response response) {
+ if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getStarred2() != null && response.body().getSubsonicResponse().getStarred2().getAlbums() != null) {
+ List albums = response.body().getSubsonicResponse().getStarred2().getAlbums();
+
+ List mediaItems = new ArrayList<>();
+
+ for (AlbumID3 album : albums) {
+ Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(album.getCoverArtId(), Preferences.getImageSize()));
+
+ MediaMetadata mediaMetadata = new MediaMetadata.Builder()
+ .setTitle(album.getName())
+ .setArtist(album.getArtist())
+ .setGenre(album.getGenre())
+ .setIsBrowsable(true)
+ .setIsPlayable(false)
+ .setMediaType(MediaMetadata.MEDIA_TYPE_ALBUM)
+ .setArtworkUri(artworkUri)
+ .build();
+
+ MediaItem mediaItem = new MediaItem.Builder()
+ .setMediaId(prefix + album.getId())
+ .setMediaMetadata(mediaMetadata)
+ .setUri("")
+ .build();
+
+ mediaItems.add(mediaItem);
+ }
+
+ LibraryResult> libraryResult = LibraryResult.ofItemList(ImmutableList.copyOf(mediaItems), null);
+
+ listenableFuture.set(libraryResult);
+ } else {
+ listenableFuture.set(LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE));
+ }
+ }
+
+ @Override
+ public void onFailure(@NonNull Call call, @NonNull Throwable t) {
+
+ }
+ });
+
+ return listenableFuture;
+ }
+
+ public ListenableFuture>> getStarredArtists(String prefix) {
+ final SettableFuture>> listenableFuture = SettableFuture.create();
+
+ App.getSubsonicClientInstance(false)
+ .getAlbumSongListClient()
+ .getStarred2()
+ .enqueue(new Callback() {
+ @Override
+ public void onResponse(@NonNull Call call, @NonNull Response response) {
+ if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getStarred2() != null && response.body().getSubsonicResponse().getStarred2().getArtists() != null) {
+ List artists = response.body().getSubsonicResponse().getStarred2().getArtists();
+
+ Collections.shuffle(artists);
+
+ List mediaItems = new ArrayList<>();
+
+ for (ArtistID3 artist : artists) {
+ Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(artist.getCoverArtId(), Preferences.getImageSize()));
+
+ MediaMetadata mediaMetadata = new MediaMetadata.Builder()
+ .setTitle(artist.getName())
+ .setIsBrowsable(true)
+ .setIsPlayable(false)
+ .setMediaType(MediaMetadata.MEDIA_TYPE_PLAYLIST)
+ .setArtworkUri(artworkUri)
+ .build();
+
+ MediaItem mediaItem = new MediaItem.Builder()
+ .setMediaId(prefix + artist.getId())
+ .setMediaMetadata(mediaMetadata)
+ .setUri("")
+ .build();
+
+ mediaItems.add(mediaItem);
+ }
+
+ LibraryResult> libraryResult = LibraryResult.ofItemList(ImmutableList.copyOf(mediaItems), null);
+
+ listenableFuture.set(libraryResult);
+ } else {
+ listenableFuture.set(LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE));
+ }
+ }
+
+ @Override
+ public void onFailure(@NonNull Call call, @NonNull Throwable t) {
+ listenableFuture.setException(t);
+ }
+ });
+
+ return listenableFuture;
+ }
+
+ public ListenableFuture>> getMusicFolders(String prefix) {
+ final SettableFuture>> listenableFuture = SettableFuture.create();
+
+ App.getSubsonicClientInstance(false)
+ .getBrowsingClient()
+ .getMusicFolders()
+ .enqueue(new Callback() {
+ @Override
+ public void onResponse(@NonNull Call call, @NonNull Response response) {
+ if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getMusicFolders() != null && response.body().getSubsonicResponse().getMusicFolders().getMusicFolders() != null) {
+ List musicFolders = response.body().getSubsonicResponse().getMusicFolders().getMusicFolders();
+
+ List mediaItems = new ArrayList<>();
+
+ for (MusicFolder musicFolder : musicFolders) {
+ MediaMetadata mediaMetadata = new MediaMetadata.Builder()
+ .setTitle(musicFolder.getName())
+ .setIsBrowsable(true)
+ .setIsPlayable(false)
+ .setMediaType(MediaMetadata.MEDIA_TYPE_FOLDER_MIXED)
+ .build();
+
+ MediaItem mediaItem = new MediaItem.Builder()
+ .setMediaId(prefix + musicFolder.getId())
+ .setMediaMetadata(mediaMetadata)
+ .setUri("")
+ .build();
+
+ mediaItems.add(mediaItem);
+ }
+
+ LibraryResult> libraryResult = LibraryResult.ofItemList(ImmutableList.copyOf(mediaItems), null);
+
+ listenableFuture.set(libraryResult);
+ } else {
+ listenableFuture.set(LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE));
+ }
+ }
+
+ @Override
+ public void onFailure(@NonNull Call call, @NonNull Throwable t) {
+ listenableFuture.setException(t);
+ }
+ });
+
+ return listenableFuture;
+ }
+
+ public ListenableFuture>> getIndexes(String prefix, String id) {
+ final SettableFuture>> listenableFuture = SettableFuture.create();
+
+ App.getSubsonicClientInstance(false)
+ .getBrowsingClient()
+ .getIndexes(id, null)
+ .enqueue(new Callback() {
+ @Override
+ public void onResponse(@NonNull Call call, @NonNull Response response) {
+ if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getIndexes() != null) {
+ List mediaItems = new ArrayList<>();
+
+ if (response.body().getSubsonicResponse().getIndexes().getIndices() != null) {
+ List indices = response.body().getSubsonicResponse().getIndexes().getIndices();
+
+ for (Index index : indices) {
+ if (index.getArtists() != null) {
+ for (Artist artist : index.getArtists()) {
+ MediaMetadata mediaMetadata = new MediaMetadata.Builder()
+ .setTitle(artist.getName())
+ .setIsBrowsable(true)
+ .setIsPlayable(false)
+ .setMediaType(MediaMetadata.MEDIA_TYPE_ARTIST)
+ .build();
+
+ MediaItem mediaItem = new MediaItem.Builder()
+ .setMediaId(prefix + artist.getId())
+ .setMediaMetadata(mediaMetadata)
+ .setUri("")
+ .build();
+
+ mediaItems.add(mediaItem);
+ }
+ }
+ }
+ }
+
+ if (response.body().getSubsonicResponse().getIndexes().getChildren() != null) {
+ List children = response.body().getSubsonicResponse().getIndexes().getChildren();
+
+ for (Child song : children) {
+ Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(song.getCoverArtId(), Preferences.getImageSize()));
+
+ MediaMetadata mediaMetadata = new MediaMetadata.Builder()
+ .setTitle(song.getTitle())
+ .setAlbumTitle(song.getAlbum())
+ .setArtist(song.getArtist())
+ .setIsBrowsable(false)
+ .setIsPlayable(true)
+ .setMediaType(MediaMetadata.MEDIA_TYPE_MUSIC)
+ .setArtworkUri(artworkUri)
+ .build();
+
+ MediaItem mediaItem = new MediaItem.Builder()
+ .setMediaId(prefix + song.getId())
+ .setMediaMetadata(mediaMetadata)
+ .setUri(MusicUtil.getStreamUri(song.getId()))
+ .build();
+
+ mediaItems.add(mediaItem);
+ }
+
+ setChildrenMetadata(children);
+ }
+
+ LibraryResult> libraryResult = LibraryResult.ofItemList(ImmutableList.copyOf(mediaItems), null);
+
+ listenableFuture.set(libraryResult);
+ }
+ }
+
+ @Override
+ public void onFailure(@NonNull Call call, @NonNull Throwable t) {
+ listenableFuture.setException(t);
+ }
+ });
+
+ return listenableFuture;
+ }
+
+ public ListenableFuture>> getDirectories(String prefix, String id) {
+ final SettableFuture>> listenableFuture = SettableFuture.create();
+
+ App.getSubsonicClientInstance(false)
+ .getBrowsingClient()
+ .getMusicDirectory(id)
+ .enqueue(new Callback() {
+ @Override
+ public void onResponse(@NonNull Call call, @NonNull Response response) {
+ if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getDirectory() != null && response.body().getSubsonicResponse().getDirectory().getChildren() != null) {
+ Directory directory = response.body().getSubsonicResponse().getDirectory();
+
+ List mediaItems = new ArrayList<>();
+
+ for (Child child : directory.getChildren()) {
+ Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(child.getCoverArtId(), Preferences.getImageSize()));
+
+ MediaMetadata mediaMetadata = new MediaMetadata.Builder()
+ .setTitle(child.getTitle())
+ .setIsBrowsable(child.isDir())
+ .setIsPlayable(!child.isDir())
+ .setMediaType(MediaMetadata.MEDIA_TYPE_FOLDER_MIXED)
+ .setArtworkUri(artworkUri)
+ .build();
+
+ MediaItem mediaItem = new MediaItem.Builder()
+ .setMediaId(child.isDir() ? prefix + child.getId() : child.getId())
+ .setMediaMetadata(mediaMetadata)
+ .setUri(!child.isDir() ? MusicUtil.getStreamUri(child.getId()) : Uri.parse(""))
+ .build();
+
+ mediaItems.add(mediaItem);
+ }
+
+ setChildrenMetadata(directory.getChildren().stream().filter(child -> !child.isDir()).collect(Collectors.toList()));
+
+ LibraryResult> libraryResult = LibraryResult.ofItemList(ImmutableList.copyOf(mediaItems), null);
+
+ listenableFuture.set(libraryResult);
+ }
+ }
+
+ @Override
+ public void onFailure(@NonNull Call call, @NonNull Throwable t) {
+ listenableFuture.setException(t);
+ }
+ });
+
+ return listenableFuture;
+ }
+
+ public ListenableFuture>> getPlaylists(String prefix) {
+ final SettableFuture>> listenableFuture = SettableFuture.create();
+
+ App.getSubsonicClientInstance(false)
+ .getPlaylistClient()
+ .getPlaylists()
+ .enqueue(new Callback() {
+ @Override
+ public void onResponse(@NonNull Call call, @NonNull Response response) {
+ if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getPlaylists() != null && response.body().getSubsonicResponse().getPlaylists().getPlaylists() != null) {
+ List playlists = response.body().getSubsonicResponse().getPlaylists().getPlaylists();
+
+ List mediaItems = new ArrayList<>();
+
+ for (Playlist playlist : playlists) {
+ MediaMetadata mediaMetadata = new MediaMetadata.Builder()
+ .setTitle(playlist.getName())
+ .setIsBrowsable(true)
+ .setIsPlayable(false)
+ .setMediaType(MediaMetadata.MEDIA_TYPE_PLAYLIST)
+ .build();
+
+ MediaItem mediaItem = new MediaItem.Builder()
+ .setMediaId(prefix + playlist.getId())
+ .setMediaMetadata(mediaMetadata)
+ .setUri("")
+ .build();
+
+ mediaItems.add(mediaItem);
+ }
+
+ LibraryResult> libraryResult = LibraryResult.ofItemList(ImmutableList.copyOf(mediaItems), null);
+
+ listenableFuture.set(libraryResult);
+ } else {
+ listenableFuture.set(LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE));
+ }
+ }
+
+ @Override
+ public void onFailure(@NonNull Call call, @NonNull Throwable t) {
+ listenableFuture.setException(t);
+ }
+ });
+
+ return listenableFuture;
+ }
+
+ public ListenableFuture>> getNewestPodcastEpisodes(int count) {
+ final SettableFuture>> listenableFuture = SettableFuture.create();
+
+ App.getSubsonicClientInstance(false)
+ .getPodcastClient()
+ .getNewestPodcasts(count)
+ .enqueue(new Callback() {
+ @Override
+ public void onResponse(@NonNull Call call, @NonNull Response response) {
+ if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getNewestPodcasts() != null && response.body().getSubsonicResponse().getNewestPodcasts().getEpisodes() != null) {
+ List episodes = response.body().getSubsonicResponse().getNewestPodcasts().getEpisodes();
+
+ List mediaItems = new ArrayList<>();
+
+ for (PodcastEpisode episode : episodes) {
+ Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(episode.getCoverArtId(), Preferences.getImageSize()));
+
+ MediaMetadata mediaMetadata = new MediaMetadata.Builder()
+ .setTitle(episode.getTitle())
+ .setIsBrowsable(false)
+ .setIsPlayable(true)
+ .setMediaType(MediaMetadata.MEDIA_TYPE_PODCAST_EPISODE)
+ .setArtworkUri(artworkUri)
+ .build();
+
+ MediaItem mediaItem = new MediaItem.Builder()
+ .setMediaId(episode.getId())
+ .setMediaMetadata(mediaMetadata)
+ .setUri(MusicUtil.getStreamUri(episode.getStreamId()))
+ .build();
+
+ mediaItems.add(mediaItem);
+ }
+
+ setPodcastEpisodesMetadata(episodes);
+
+ LibraryResult> libraryResult = LibraryResult.ofItemList(ImmutableList.copyOf(mediaItems), null);
+
+ listenableFuture.set(libraryResult);
+ } else {
+ listenableFuture.set(LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE));
+ }
+ }
+
+ @Override
+ public void onFailure(@NonNull Call call, @NonNull Throwable t) {
+ listenableFuture.setException(t);
+ }
+ });
+
+ return listenableFuture;
+ }
+
+ public ListenableFuture>> getInternetRadioStations() {
+ final SettableFuture>> listenableFuture = SettableFuture.create();
+
+ App.getSubsonicClientInstance(false)
+ .getInternetRadioClient()
+ .getInternetRadioStations()
+ .enqueue(new Callback() {
+ @Override
+ public void onResponse(@NonNull Call call, @NonNull Response response) {
+ if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getInternetRadioStations() != null && response.body().getSubsonicResponse().getInternetRadioStations().getInternetRadioStations() != null) {
+
+ List radioStations = response.body().getSubsonicResponse().getInternetRadioStations().getInternetRadioStations();
+
+ List mediaItems = new ArrayList<>();
+
+ for (InternetRadioStation radioStation : radioStations) {
+ MediaMetadata mediaMetadata = new MediaMetadata.Builder()
+ .setTitle(radioStation.getName())
+ .setIsBrowsable(false)
+ .setIsPlayable(true)
+ .setMediaType(MediaMetadata.MEDIA_TYPE_RADIO_STATION)
+ .build();
+
+ MediaItem mediaItem = new MediaItem.Builder()
+ .setMediaId(radioStation.getId())
+ .setMediaMetadata(mediaMetadata)
+ .setUri(radioStation.getStreamUrl())
+ .build();
+
+ mediaItems.add(mediaItem);
+ }
+
+ setInternetRadioStationsMetadata(radioStations);
+
+ LibraryResult> libraryResult = LibraryResult.ofItemList(ImmutableList.copyOf(mediaItems), null);
+
+ listenableFuture.set(libraryResult);
+ } else {
+ listenableFuture.set(LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE));
+ }
+ }
+
+ @Override
+ public void onFailure(@NonNull Call call, @NonNull Throwable t) {
+ listenableFuture.setException(t);
+ }
+ });
+
+ return listenableFuture;
+ }
+
+ public ListenableFuture>> getAlbumTracks(String id) {
+ final SettableFuture>> listenableFuture = SettableFuture.create();
+
+ App.getSubsonicClientInstance(false)
+ .getBrowsingClient()
+ .getAlbum(id)
+ .enqueue(new Callback() {
+ @Override
+ public void onResponse(@NonNull Call call, @NonNull Response response) {
+ if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getAlbum() != null && response.body().getSubsonicResponse().getAlbum().getSongs() != null) {
+ List tracks = response.body().getSubsonicResponse().getAlbum().getSongs();
+
+ setChildrenMetadata(tracks);
+
+ List mediaItems = MappingUtil.mapMediaItems(tracks);
+
+ LibraryResult> libraryResult = LibraryResult.ofItemList(ImmutableList.copyOf(mediaItems), null);
+
+ listenableFuture.set(libraryResult);
+ } else {
+ listenableFuture.set(LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE));
+ }
+ }
+
+ @Override
+ public void onFailure(@NonNull Call call, @NonNull Throwable t) {
+ listenableFuture.setException(t);
+ }
+ });
+
+ return listenableFuture;
+ }
+
+ public ListenableFuture>> getArtistAlbum(String prefix, String id) {
+ final SettableFuture>> listenableFuture = SettableFuture.create();
+
+ App.getSubsonicClientInstance(false)
+ .getBrowsingClient()
+ .getArtist(id)
+ .enqueue(new Callback() {
+ @Override
+ public void onResponse(@NonNull Call call, @NonNull Response response) {
+ if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getArtist() != null && response.body().getSubsonicResponse().getArtist().getAlbums() != null) {
+ List albums = response.body().getSubsonicResponse().getArtist().getAlbums();
+
+ List mediaItems = new ArrayList<>();
+
+ for (AlbumID3 album : albums) {
+ Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(album.getCoverArtId(), Preferences.getImageSize()));
+
+ MediaMetadata mediaMetadata = new MediaMetadata.Builder()
+ .setTitle(album.getName())
+ .setAlbumTitle(album.getName())
+ .setArtist(album.getArtist())
+ .setGenre(album.getGenre())
+ .setIsBrowsable(true)
+ .setIsPlayable(false)
+ .setMediaType(MediaMetadata.MEDIA_TYPE_ALBUM)
+ .setArtworkUri(artworkUri)
+ .build();
+
+ MediaItem mediaItem = new MediaItem.Builder()
+ .setMediaId(prefix + album.getId())
+ .setMediaMetadata(mediaMetadata)
+ .setUri("")
+ .build();
+
+ mediaItems.add(mediaItem);
+ }
+
+ LibraryResult> libraryResult = LibraryResult.ofItemList(ImmutableList.copyOf(mediaItems), null);
+
+ listenableFuture.set(libraryResult);
+ }
+ }
+
+ @Override
+ public void onFailure(@NonNull Call call, @NonNull Throwable t) {
+ listenableFuture.setException(t);
+ }
+ });
+
+ return listenableFuture;
+ }
+
+ public ListenableFuture>> getPlaylistSongs(String id) {
+ final SettableFuture>> listenableFuture = SettableFuture.create();
+
+ App.getSubsonicClientInstance(false)
+ .getPlaylistClient()
+ .getPlaylist(id)
+ .enqueue(new Callback() {
+ @Override
+ public void onResponse(@NonNull Call call, @NonNull Response response) {
+ if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getPlaylist() != null && response.body().getSubsonicResponse().getPlaylist().getEntries() != null) {
+ List tracks = response.body().getSubsonicResponse().getPlaylist().getEntries();
+
+ setChildrenMetadata(tracks);
+
+ List mediaItems = MappingUtil.mapMediaItems(tracks);
+
+ LibraryResult> libraryResult = LibraryResult.ofItemList(ImmutableList.copyOf(mediaItems), null);
+
+ listenableFuture.set(libraryResult);
+ }
+ }
+
+ @Override
+ public void onFailure(@NonNull Call call, @NonNull Throwable t) {
+ listenableFuture.setException(t);
+ }
+ });
+
+ return listenableFuture;
+ }
+
+ public ListenableFuture>> getMadeForYou(String id, int count) {
+ final SettableFuture>> listenableFuture = SettableFuture.create();
+
+ App.getSubsonicClientInstance(false)
+ .getBrowsingClient()
+ .getSimilarSongs2(id, count)
+ .enqueue(new Callback() {
+ @Override
+ public void onResponse(@NonNull Call call, @NonNull Response response) {
+ if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getSimilarSongs2() != null && response.body().getSubsonicResponse().getSimilarSongs2().getSongs() != null) {
+ List tracks = response.body().getSubsonicResponse().getSimilarSongs2().getSongs();
+
+ setChildrenMetadata(tracks);
+
+ List mediaItems = MappingUtil.mapMediaItems(tracks);
+
+ LibraryResult> libraryResult = LibraryResult.ofItemList(ImmutableList.copyOf(mediaItems), null);
+
+ listenableFuture.set(libraryResult);
+ }
+ }
+
+ @Override
+ public void onFailure(@NonNull Call call, @NonNull Throwable t) {
+ listenableFuture.setException(t);
+ }
+ });
+
+ return listenableFuture;
+ }
+
+ public ListenableFuture>> search(String query, String albumPrefix, String artistPrefix) {
+ final SettableFuture>> listenableFuture = SettableFuture.create();
+
+ App.getSubsonicClientInstance(false)
+ .getSearchingClient()
+ .search3(query, 20, 20, 20)
+ .enqueue(new Callback() {
+ @Override
+ public void onResponse(@NonNull Call call, @NonNull Response response) {
+ if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getSearchResult3() != null) {
+ List mediaItems = new ArrayList<>();
+
+ if (response.body().getSubsonicResponse().getSearchResult3().getArtists() != null) {
+ for (ArtistID3 artist : response.body().getSubsonicResponse().getSearchResult3().getArtists()) {
+ Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(artist.getCoverArtId(), Preferences.getImageSize()));
+
+ MediaMetadata mediaMetadata = new MediaMetadata.Builder()
+ .setTitle(artist.getName())
+ .setIsBrowsable(true)
+ .setIsPlayable(false)
+ .setMediaType(MediaMetadata.MEDIA_TYPE_PLAYLIST)
+ .setArtworkUri(artworkUri)
+ .build();
+
+ MediaItem mediaItem = new MediaItem.Builder()
+ .setMediaId(artistPrefix + artist.getId())
+ .setMediaMetadata(mediaMetadata)
+ .setUri("")
+ .build();
+
+ mediaItems.add(mediaItem);
+ }
+ }
+
+ if (response.body().getSubsonicResponse().getSearchResult3().getAlbums() != null) {
+ for (AlbumID3 album : response.body().getSubsonicResponse().getSearchResult3().getAlbums()) {
+ Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(album.getCoverArtId(), Preferences.getImageSize()));
+
+ MediaMetadata mediaMetadata = new MediaMetadata.Builder()
+ .setTitle(album.getName())
+ .setAlbumTitle(album.getName())
+ .setArtist(album.getArtist())
+ .setGenre(album.getGenre())
+ .setIsBrowsable(true)
+ .setIsPlayable(false)
+ .setMediaType(MediaMetadata.MEDIA_TYPE_ALBUM)
+ .setArtworkUri(artworkUri)
+ .build();
+
+ MediaItem mediaItem = new MediaItem.Builder()
+ .setMediaId(albumPrefix + album.getId())
+ .setMediaMetadata(mediaMetadata)
+ .setUri("")
+ .build();
+
+ mediaItems.add(mediaItem);
+ }
+ }
+
+ if (response.body().getSubsonicResponse().getSearchResult3().getSongs() != null) {
+ List tracks = response.body().getSubsonicResponse().getSearchResult3().getSongs();
+ setChildrenMetadata(tracks);
+ mediaItems.addAll(MappingUtil.mapMediaItems(tracks));
+ }
+
+ LibraryResult> libraryResult = LibraryResult.ofItemList(ImmutableList.copyOf(mediaItems), null);
+
+ listenableFuture.set(libraryResult);
+ }
+ }
+
+ @Override
+ public void onFailure(@NonNull Call call, @NonNull Throwable t) {
+ listenableFuture.setException(t);
+ }
+ });
+
+ return listenableFuture;
+ }
+
+ @OptIn(markerClass = UnstableApi.class)
+ public void setChildrenMetadata(List children) {
+ long timestamp = System.currentTimeMillis();
+ ArrayList sessionMediaItems = new ArrayList<>();
+
+ for (Child child : children) {
+ SessionMediaItem sessionMediaItem = new SessionMediaItem(child);
+ sessionMediaItem.setTimestamp(timestamp);
+ sessionMediaItems.add(sessionMediaItem);
+ }
+
+ InsertAllThreadSafe insertAll = new InsertAllThreadSafe(sessionMediaItemDao, sessionMediaItems);
+ Thread thread = new Thread(insertAll);
+ thread.start();
+ }
+
+ @OptIn(markerClass = UnstableApi.class)
+ public void setPodcastEpisodesMetadata(List podcastEpisodes) {
+ long timestamp = System.currentTimeMillis();
+ ArrayList sessionMediaItems = new ArrayList<>();
+
+ for (PodcastEpisode podcastEpisode : podcastEpisodes) {
+ SessionMediaItem sessionMediaItem = new SessionMediaItem(podcastEpisode);
+ sessionMediaItem.setTimestamp(timestamp);
+ sessionMediaItems.add(sessionMediaItem);
+ }
+
+ InsertAllThreadSafe insertAll = new InsertAllThreadSafe(sessionMediaItemDao, sessionMediaItems);
+ Thread thread = new Thread(insertAll);
+ thread.start();
+ }
+
+ @OptIn(markerClass = UnstableApi.class)
+ public void setInternetRadioStationsMetadata(List internetRadioStations) {
+ long timestamp = System.currentTimeMillis();
+ ArrayList sessionMediaItems = new ArrayList<>();
+
+ for (InternetRadioStation internetRadioStation : internetRadioStations) {
+ SessionMediaItem sessionMediaItem = new SessionMediaItem(internetRadioStation);
+ sessionMediaItem.setTimestamp(timestamp);
+ sessionMediaItems.add(sessionMediaItem);
+ }
+
+ InsertAllThreadSafe insertAll = new InsertAllThreadSafe(sessionMediaItemDao, sessionMediaItems);
+ Thread thread = new Thread(insertAll);
+ thread.start();
+ }
+
+ public SessionMediaItem getSessionMediaItem(String id) {
+ SessionMediaItem sessionMediaItem = null;
+
+ GetMediaItemThreadSafe getMediaItemThreadSafe = new GetMediaItemThreadSafe(sessionMediaItemDao, id);
+ Thread thread = new Thread(getMediaItemThreadSafe);
+ thread.start();
+
+ try {
+ thread.join();
+ sessionMediaItem = getMediaItemThreadSafe.getSessionMediaItem();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+
+ return sessionMediaItem;
+ }
+
+ public List getMetadatas(long timestamp) {
+ List mediaItems = Collections.emptyList();
+
+ GetMediaItemsThreadSafe getMediaItemsThreadSafe = new GetMediaItemsThreadSafe(sessionMediaItemDao, timestamp);
+ Thread thread = new Thread(getMediaItemsThreadSafe);
+ thread.start();
+
+ try {
+ thread.join();
+ mediaItems = getMediaItemsThreadSafe.getMediaItems();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+
+ return mediaItems;
+ }
+
+ public void deleteMetadata() {
+ DeleteAllThreadSafe delete = new DeleteAllThreadSafe(sessionMediaItemDao);
+ Thread thread = new Thread(delete);
+ thread.start();
+ }
+
+ private static class GetMediaItemThreadSafe implements Runnable {
+ private final SessionMediaItemDao sessionMediaItemDao;
+ private final String id;
+
+ private SessionMediaItem sessionMediaItem;
+
+ public GetMediaItemThreadSafe(SessionMediaItemDao sessionMediaItemDao, String id) {
+ this.sessionMediaItemDao = sessionMediaItemDao;
+ this.id = id;
+ }
+
+ @Override
+ public void run() {
+ sessionMediaItem = sessionMediaItemDao.get(id);
+ }
+
+ public SessionMediaItem getSessionMediaItem() {
+ return sessionMediaItem;
+ }
+ }
+
+ @OptIn(markerClass = UnstableApi.class)
+ private static class GetMediaItemsThreadSafe implements Runnable {
+ private final SessionMediaItemDao sessionMediaItemDao;
+ private final Long timestamp;
+ private final List mediaItems = new ArrayList<>();
+
+ public GetMediaItemsThreadSafe(SessionMediaItemDao sessionMediaItemDao, Long timestamp) {
+ this.sessionMediaItemDao = sessionMediaItemDao;
+ this.timestamp = timestamp;
+ }
+
+ @Override
+ public void run() {
+ List sessionMediaItems = sessionMediaItemDao.get(timestamp);
+ sessionMediaItems.forEach(sessionMediaItem -> mediaItems.add(sessionMediaItem.getMediaItem()));
+ }
+
+ public List getMediaItems() {
+ return mediaItems;
+ }
+ }
+
+ private static class InsertAllThreadSafe implements Runnable {
+ private final SessionMediaItemDao sessionMediaItemDao;
+ private final List sessionMediaItems;
+
+ public InsertAllThreadSafe(SessionMediaItemDao sessionMediaItemDao, List sessionMediaItems) {
+ this.sessionMediaItemDao = sessionMediaItemDao;
+ this.sessionMediaItems = sessionMediaItems;
+ }
+
+ @Override
+ public void run() {
+ sessionMediaItemDao.insertAll(sessionMediaItems);
+ }
+ }
+
+ private static class DeleteAllThreadSafe implements Runnable {
+ private final SessionMediaItemDao sessionMediaItemDao;
+
+ public DeleteAllThreadSafe(SessionMediaItemDao sessionMediaItemDao) {
+ this.sessionMediaItemDao = sessionMediaItemDao;
+ }
+
+ @Override
+ public void run() {
+ sessionMediaItemDao.deleteAll();
+ }
+ }
+}
diff --git a/app/src/main/java/com/cappielloantonio/tempo/repository/GenreRepository.java b/app/src/main/java/com/cappielloantonio/tempo/repository/GenreRepository.java
index 58f00042..4e6addb0 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/repository/GenreRepository.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/repository/GenreRepository.java
@@ -8,7 +8,9 @@ import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
import com.cappielloantonio.tempo.subsonic.models.Genre;
import java.util.Collections;
+import java.util.Comparator;
import java.util.List;
+import java.util.stream.Collectors;
import retrofit2.Call;
import retrofit2.Callback;
@@ -39,7 +41,7 @@ public class GenreRepository {
if (size != -1) {
genres.setValue(genreList.subList(0, Math.min(size, genreList.size())));
} else {
- genres.setValue(genreList);
+ genres.setValue(genreList.stream().sorted(Comparator.comparing(Genre::getGenre)).collect(Collectors.toList()));
}
}
}
diff --git a/app/src/main/java/com/cappielloantonio/tempo/repository/OpenRepository.java b/app/src/main/java/com/cappielloantonio/tempo/repository/OpenRepository.java
new file mode 100644
index 00000000..86216034
--- /dev/null
+++ b/app/src/main/java/com/cappielloantonio/tempo/repository/OpenRepository.java
@@ -0,0 +1,37 @@
+package com.cappielloantonio.tempo.repository;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.MutableLiveData;
+
+import com.cappielloantonio.tempo.App;
+import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
+import com.cappielloantonio.tempo.subsonic.models.LyricsList;
+
+import retrofit2.Call;
+import retrofit2.Callback;
+import retrofit2.Response;
+
+public class OpenRepository {
+ public MutableLiveData getLyricsBySongId(String id) {
+ MutableLiveData lyricsList = new MutableLiveData<>();
+
+ App.getSubsonicClientInstance(false)
+ .getOpenClient()
+ .getLyricsBySongId(id)
+ .enqueue(new Callback() {
+ @Override
+ public void onResponse(@NonNull Call call, @NonNull Response response) {
+ if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getLyricsList() != null) {
+ lyricsList.setValue(response.body().getSubsonicResponse().getLyricsList());
+ }
+ }
+
+ @Override
+ public void onFailure(@NonNull Call call, @NonNull Throwable t) {
+
+ }
+ });
+
+ return lyricsList;
+ }
+}
diff --git a/app/src/main/java/com/cappielloantonio/tempo/repository/PlaylistRepository.java b/app/src/main/java/com/cappielloantonio/tempo/repository/PlaylistRepository.java
index 1db0453b..003f845a 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/repository/PlaylistRepository.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/repository/PlaylistRepository.java
@@ -1,7 +1,5 @@
package com.cappielloantonio.tempo.repository;
-import android.util.Log;
-
import androidx.annotation.NonNull;
import androidx.lifecycle.MutableLiveData;
diff --git a/app/src/main/java/com/cappielloantonio/tempo/repository/SongRepository.java b/app/src/main/java/com/cappielloantonio/tempo/repository/SongRepository.java
index db75f907..d8038e26 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/repository/SongRepository.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/repository/SongRepository.java
@@ -104,10 +104,10 @@ public class SongRepository {
return randomSongsSample;
}
- public void scrobble(String id) {
+ public void scrobble(String id, boolean submission) {
App.getSubsonicClientInstance(false)
.getMediaAnnotationClient()
- .scrobble(id)
+ .scrobble(id, submission)
.enqueue(new Callback() {
@Override
public void onResponse(@NonNull Call call, @NonNull Response response) {
diff --git a/app/src/main/java/com/cappielloantonio/tempo/repository/SystemRepository.java b/app/src/main/java/com/cappielloantonio/tempo/repository/SystemRepository.java
index 4dc1dea4..f477e6f0 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/repository/SystemRepository.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/repository/SystemRepository.java
@@ -6,7 +6,11 @@ import androidx.lifecycle.MutableLiveData;
import com.cappielloantonio.tempo.App;
import com.cappielloantonio.tempo.interfaces.SystemCallback;
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
+import com.cappielloantonio.tempo.subsonic.models.OpenSubsonicExtension;
import com.cappielloantonio.tempo.subsonic.models.ResponseStatus;
+import com.cappielloantonio.tempo.subsonic.models.SubsonicResponse;
+
+import java.util.List;
import retrofit2.Call;
import retrofit2.Callback;
@@ -43,8 +47,8 @@ public class SystemRepository {
});
}
- public MutableLiveData ping() {
- MutableLiveData pingResult = new MutableLiveData<>();
+ public MutableLiveData ping() {
+ MutableLiveData pingResult = new MutableLiveData<>();
App.getSubsonicClientInstance(false)
.getSystemClient()
@@ -53,16 +57,39 @@ public class SystemRepository {
@Override
public void onResponse(@NonNull Call call, @NonNull Response response) {
if (response.isSuccessful() && response.body() != null) {
- pingResult.postValue(true);
+ pingResult.postValue(response.body().getSubsonicResponse());
}
}
@Override
public void onFailure(@NonNull Call call, @NonNull Throwable t) {
- pingResult.postValue(false);
+ pingResult.postValue(null);
}
});
return pingResult;
}
+
+ public MutableLiveData> getOpenSubsonicExtensions() {
+ MutableLiveData> extensionsResult = new MutableLiveData<>();
+
+ App.getSubsonicClientInstance(false)
+ .getSystemClient()
+ .getOpenSubsonicExtensions()
+ .enqueue(new Callback() {
+ @Override
+ public void onResponse(@NonNull Call call, @NonNull Response response) {
+ if (response.isSuccessful() && response.body() != null) {
+ extensionsResult.postValue(response.body().getSubsonicResponse().getOpenSubsonicExtensions());
+ }
+ }
+
+ @Override
+ public void onFailure(@NonNull Call call, @NonNull Throwable t) {
+ extensionsResult.postValue(null);
+ }
+ });
+
+ return extensionsResult;
+ }
}
diff --git a/app/src/main/java/com/cappielloantonio/tempo/service/DownloaderManager.java b/app/src/main/java/com/cappielloantonio/tempo/service/DownloaderManager.java
index 66f13910..3a695fef 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/service/DownloaderManager.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/service/DownloaderManager.java
@@ -31,9 +31,10 @@ public class DownloaderManager {
private final Context context;
private final DataSource.Factory dataSourceFactory;
- private final HashMap downloads;
private final DownloadIndex downloadIndex;
+ private static HashMap downloads;
+
public DownloaderManager(Context context, DataSource.Factory dataSourceFactory, DownloadManager downloadManager) {
this.context = context.getApplicationContext();
this.dataSourceFactory = dataSourceFactory;
@@ -61,19 +62,11 @@ public class DownloaderManager {
}
public boolean isDownloaded(MediaItem mediaItem) {
- @Nullable Download download = downloads.get(mediaItem.mediaId);
- return download != null && download.state != Download.STATE_FAILED;
+ return isDownloaded(mediaItem.mediaId);
}
public boolean areDownloaded(List mediaItems) {
- for (MediaItem mediaItem : mediaItems) {
- @Nullable Download download = downloads.get(mediaItem.mediaId);
- if (download != null && download.state != Download.STATE_FAILED) {
- return true;
- }
- }
-
- return false;
+ return mediaItems.stream().anyMatch(this::isDownloaded);
}
public void download(MediaItem mediaItem, com.cappielloantonio.tempo.model.Download download) {
@@ -92,6 +85,7 @@ public class DownloaderManager {
public void remove(MediaItem mediaItem, com.cappielloantonio.tempo.model.Download download) {
DownloadService.sendRemoveDownload(context, DownloaderService.class, buildDownloadRequest(mediaItem).id, false);
deleteDatabase(download.getId());
+ downloads.remove(download.getId());
}
public void remove(List mediaItems, List downloads) {
@@ -122,23 +116,33 @@ public class DownloaderManager {
return download != null ? download.getTitle() : null;
}
+ public static void updateRequestDownload(Download download) {
+ updateDatabase(download.request.id);
+ downloads.put(download.request.id, download);
+ }
+
+ public static void removeRequestDownload(Download download) {
+ deleteDatabase(download.request.id);
+ downloads.remove(download.request.id);
+ }
+
private static DownloadRepository getDownloadRepository() {
return new DownloadRepository();
}
- public static void insertDatabase(com.cappielloantonio.tempo.model.Download download) {
+ private static void insertDatabase(com.cappielloantonio.tempo.model.Download download) {
getDownloadRepository().insert(download);
}
- public static void deleteDatabase(String id) {
+ private static void deleteDatabase(String id) {
getDownloadRepository().delete(id);
}
- public static void deleteAllDatabase() {
+ private static void deleteAllDatabase() {
getDownloadRepository().deleteAll();
}
- public static void updateDatabase(String id) {
+ private static void updateDatabase(String id) {
getDownloadRepository().update(id);
}
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/cappielloantonio/tempo/service/DownloaderService.java b/app/src/main/java/com/cappielloantonio/tempo/service/DownloaderService.java
index e34867a0..b34daf33 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/service/DownloaderService.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/service/DownloaderService.java
@@ -95,7 +95,7 @@ public class DownloaderService extends androidx.media3.exoplayer.offline.Downloa
notification = notificationHelper.buildDownloadCompletedNotification(context, R.drawable.ic_check_circle, null, DownloaderManager.getDownloadNotificationMessage(download.request.id));
notification = Notification.Builder.recoverBuilder(context, notification).setGroup(DownloadUtil.DOWNLOAD_NOTIFICATION_SUCCESSFUL_GROUP).build();
NotificationUtil.setNotification(this.context, successfulDownloadGroupNotificationId, successfulDownloadGroupNotification);
- DownloaderManager.updateDatabase(download.request.id);
+ DownloaderManager.updateRequestDownload(download);
} else if (download.state == Download.STATE_FAILED) {
notification = notificationHelper.buildDownloadFailedNotification(context, R.drawable.ic_error, null, DownloaderManager.getDownloadNotificationMessage(download.request.id));
notification = Notification.Builder.recoverBuilder(context, notification).setGroup(DownloadUtil.DOWNLOAD_NOTIFICATION_FAILED_GROUP).build();
@@ -109,7 +109,7 @@ public class DownloaderService extends androidx.media3.exoplayer.offline.Downloa
@Override
public void onDownloadRemoved(@NonNull DownloadManager downloadManager, Download download) {
- DownloaderManager.deleteDatabase(download.request.id);
+ DownloaderManager.removeRequestDownload(download);
}
}
}
diff --git a/app/src/main/java/com/cappielloantonio/tempo/service/MediaManager.java b/app/src/main/java/com/cappielloantonio/tempo/service/MediaManager.java
index b27b88a8..57962f23 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/service/MediaManager.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/service/MediaManager.java
@@ -293,9 +293,9 @@ public class MediaManager {
getQueueRepository().setPlayingPausedTimestamp(mediaItem.mediaId, ms);
}
- public static void scrobble(MediaItem mediaItem) {
+ public static void scrobble(MediaItem mediaItem, boolean submission) {
if (mediaItem != null && Preferences.isScrobblingEnabled()) {
- getSongRepository().scrobble(mediaItem.mediaMetadata.extras.getString("id"));
+ getSongRepository().scrobble(mediaItem.mediaMetadata.extras.getString("id"), submission);
}
}
diff --git a/app/src/main/java/com/cappielloantonio/tempo/subsonic/Subsonic.java b/app/src/main/java/com/cappielloantonio/tempo/subsonic/Subsonic.java
index c401bca7..de4b36b7 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/subsonic/Subsonic.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/subsonic/Subsonic.java
@@ -7,6 +7,7 @@ import com.cappielloantonio.tempo.subsonic.api.internetradio.InternetRadioClient
import com.cappielloantonio.tempo.subsonic.api.mediaannotation.MediaAnnotationClient;
import com.cappielloantonio.tempo.subsonic.api.medialibraryscanning.MediaLibraryScanningClient;
import com.cappielloantonio.tempo.subsonic.api.mediaretrieval.MediaRetrievalClient;
+import com.cappielloantonio.tempo.subsonic.api.open.OpenClient;
import com.cappielloantonio.tempo.subsonic.api.playlist.PlaylistClient;
import com.cappielloantonio.tempo.subsonic.api.podcast.PodcastClient;
import com.cappielloantonio.tempo.subsonic.api.searching.SearchingClient;
@@ -35,6 +36,7 @@ public class Subsonic {
private BookmarksClient bookmarksClient;
private InternetRadioClient internetRadioClient;
private SharingClient sharingClient;
+ private OpenClient openClient;
public Subsonic(SubsonicPreferences preferences) {
this.preferences = preferences;
@@ -128,6 +130,13 @@ public class Subsonic {
return sharingClient;
}
+ public OpenClient getOpenClient() {
+ if (openClient == null) {
+ openClient = new OpenClient(this);
+ }
+ return openClient;
+ }
+
public String getUrl() {
String url = preferences.getServerUrl() + "/rest/";
return url.replace("//rest", "/rest");
diff --git a/app/src/main/java/com/cappielloantonio/tempo/subsonic/api/mediaannotation/MediaAnnotationClient.java b/app/src/main/java/com/cappielloantonio/tempo/subsonic/api/mediaannotation/MediaAnnotationClient.java
index eb21b42b..c229aa89 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/subsonic/api/mediaannotation/MediaAnnotationClient.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/subsonic/api/mediaannotation/MediaAnnotationClient.java
@@ -9,7 +9,7 @@ import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
import retrofit2.Call;
public class MediaAnnotationClient {
- private static final String TAG = "BrowsingClient";
+ private static final String TAG = "MediaAnnotationClient";
private final Subsonic subsonic;
private final MediaAnnotationService mediaAnnotationService;
@@ -34,8 +34,8 @@ public class MediaAnnotationClient {
return mediaAnnotationService.setRating(subsonic.getParams(), id, rating);
}
- public Call scrobble(String id) {
+ public Call scrobble(String id, boolean submission) {
Log.d(TAG, "scrobble()");
- return mediaAnnotationService.scrobble(subsonic.getParams(), id);
+ return mediaAnnotationService.scrobble(subsonic.getParams(), id, submission);
}
}
diff --git a/app/src/main/java/com/cappielloantonio/tempo/subsonic/api/mediaannotation/MediaAnnotationService.java b/app/src/main/java/com/cappielloantonio/tempo/subsonic/api/mediaannotation/MediaAnnotationService.java
index af6aadf5..65c20e92 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/subsonic/api/mediaannotation/MediaAnnotationService.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/subsonic/api/mediaannotation/MediaAnnotationService.java
@@ -20,5 +20,5 @@ public interface MediaAnnotationService {
Call setRating(@QueryMap Map params, @Query("id") String id, @Query("rating") int rating);
@GET("scrobble")
- Call scrobble(@QueryMap Map params, @Query("id") String id);
+ Call scrobble(@QueryMap Map params, @Query("id") String id, @Query("submission") Boolean submission);
}
diff --git a/app/src/main/java/com/cappielloantonio/tempo/subsonic/api/medialibraryscanning/MediaLibraryScanningClient.java b/app/src/main/java/com/cappielloantonio/tempo/subsonic/api/medialibraryscanning/MediaLibraryScanningClient.java
index 51b89751..f4485151 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/subsonic/api/medialibraryscanning/MediaLibraryScanningClient.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/subsonic/api/medialibraryscanning/MediaLibraryScanningClient.java
@@ -9,7 +9,7 @@ import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
import retrofit2.Call;
public class MediaLibraryScanningClient {
- private static final String TAG = "SystemClient";
+ private static final String TAG = "MediaLibraryScanningClient";
private final Subsonic subsonic;
private final MediaLibraryScanningService mediaLibraryScanningService;
diff --git a/app/src/main/java/com/cappielloantonio/tempo/subsonic/api/mediaretrieval/MediaRetrievalClient.java b/app/src/main/java/com/cappielloantonio/tempo/subsonic/api/mediaretrieval/MediaRetrievalClient.java
index d14ff7d5..742a77d7 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/subsonic/api/mediaretrieval/MediaRetrievalClient.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/subsonic/api/mediaretrieval/MediaRetrievalClient.java
@@ -9,7 +9,7 @@ import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
import retrofit2.Call;
public class MediaRetrievalClient {
- private static final String TAG = "BrowsingClient";
+ private static final String TAG = "MediaRetrievalClient";
private final Subsonic subsonic;
private final MediaRetrievalService mediaRetrievalService;
diff --git a/app/src/main/java/com/cappielloantonio/tempo/subsonic/api/open/OpenClient.java b/app/src/main/java/com/cappielloantonio/tempo/subsonic/api/open/OpenClient.java
new file mode 100644
index 00000000..b37b03bb
--- /dev/null
+++ b/app/src/main/java/com/cappielloantonio/tempo/subsonic/api/open/OpenClient.java
@@ -0,0 +1,26 @@
+package com.cappielloantonio.tempo.subsonic.api.open;
+
+import android.util.Log;
+
+import com.cappielloantonio.tempo.subsonic.RetrofitClient;
+import com.cappielloantonio.tempo.subsonic.Subsonic;
+import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
+
+import retrofit2.Call;
+
+public class OpenClient {
+ private static final String TAG = "OpenClient";
+
+ private final Subsonic subsonic;
+ private final OpenService openService;
+
+ public OpenClient(Subsonic subsonic) {
+ this.subsonic = subsonic;
+ this.openService = new RetrofitClient(subsonic).getRetrofit().create(OpenService.class);
+ }
+
+ public Call getLyricsBySongId(String id) {
+ Log.d(TAG, "getLyricsBySongId()");
+ return openService.getLyricsBySongId(subsonic.getParams(), id);
+ }
+}
diff --git a/app/src/main/java/com/cappielloantonio/tempo/subsonic/api/open/OpenService.java b/app/src/main/java/com/cappielloantonio/tempo/subsonic/api/open/OpenService.java
new file mode 100644
index 00000000..3122a97c
--- /dev/null
+++ b/app/src/main/java/com/cappielloantonio/tempo/subsonic/api/open/OpenService.java
@@ -0,0 +1,15 @@
+package com.cappielloantonio.tempo.subsonic.api.open;
+
+import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
+
+import java.util.Map;
+
+import retrofit2.Call;
+import retrofit2.http.GET;
+import retrofit2.http.Query;
+import retrofit2.http.QueryMap;
+
+public interface OpenService {
+ @GET("getLyricsBySongId")
+ Call getLyricsBySongId(@QueryMap Map params, @Query("id") String id);
+}
diff --git a/app/src/main/java/com/cappielloantonio/tempo/subsonic/api/podcast/PodcastClient.java b/app/src/main/java/com/cappielloantonio/tempo/subsonic/api/podcast/PodcastClient.java
index 766c7e62..ca7fab82 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/subsonic/api/podcast/PodcastClient.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/subsonic/api/podcast/PodcastClient.java
@@ -9,7 +9,7 @@ import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
import retrofit2.Call;
public class PodcastClient {
- private static final String TAG = "SystemClient";
+ private static final String TAG = "PodcastClient";
private final Subsonic subsonic;
private final PodcastService podcastService;
diff --git a/app/src/main/java/com/cappielloantonio/tempo/subsonic/api/system/SystemClient.java b/app/src/main/java/com/cappielloantonio/tempo/subsonic/api/system/SystemClient.java
index 2d50c6eb..d4a6521a 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/subsonic/api/system/SystemClient.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/subsonic/api/system/SystemClient.java
@@ -28,4 +28,9 @@ public class SystemClient {
Log.d(TAG, "getLicense()");
return systemService.getLicense(subsonic.getParams());
}
+
+ public Call getOpenSubsonicExtensions() {
+ Log.d(TAG, "getOpenSubsonicExtensions()");
+ return systemService.getOpenSubsonicExtensions(subsonic.getParams());
+ }
}
diff --git a/app/src/main/java/com/cappielloantonio/tempo/subsonic/api/system/SystemService.java b/app/src/main/java/com/cappielloantonio/tempo/subsonic/api/system/SystemService.java
index 02c4d799..bc0f4c99 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/subsonic/api/system/SystemService.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/subsonic/api/system/SystemService.java
@@ -14,4 +14,7 @@ public interface SystemService {
@GET("getLicense")
Call getLicense(@QueryMap Map params);
+
+ @GET("getOpenSubsonicExtensions")
+ Call getOpenSubsonicExtensions(@QueryMap Map params);
}
diff --git a/app/src/main/java/com/cappielloantonio/tempo/subsonic/base/ApiResponse.kt b/app/src/main/java/com/cappielloantonio/tempo/subsonic/base/ApiResponse.kt
index c6715031..78745202 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/subsonic/base/ApiResponse.kt
+++ b/app/src/main/java/com/cappielloantonio/tempo/subsonic/base/ApiResponse.kt
@@ -7,5 +7,5 @@ import com.google.gson.annotations.SerializedName
@Keep
class ApiResponse {
@SerializedName("subsonic-response")
- lateinit var subsonicResponse: SubsonicResponse;
+ lateinit var subsonicResponse: SubsonicResponse
}
\ No newline at end of file
diff --git a/app/src/main/java/com/cappielloantonio/tempo/subsonic/models/Line.kt b/app/src/main/java/com/cappielloantonio/tempo/subsonic/models/Line.kt
new file mode 100644
index 00000000..1809091e
--- /dev/null
+++ b/app/src/main/java/com/cappielloantonio/tempo/subsonic/models/Line.kt
@@ -0,0 +1,9 @@
+package com.cappielloantonio.tempo.subsonic.models
+
+import androidx.annotation.Keep
+
+@Keep
+class Line {
+ var start: Int? = null
+ lateinit var value: String
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/cappielloantonio/tempo/subsonic/models/LyricsList.kt b/app/src/main/java/com/cappielloantonio/tempo/subsonic/models/LyricsList.kt
new file mode 100644
index 00000000..9c3b65e8
--- /dev/null
+++ b/app/src/main/java/com/cappielloantonio/tempo/subsonic/models/LyricsList.kt
@@ -0,0 +1,8 @@
+package com.cappielloantonio.tempo.subsonic.models
+
+import androidx.annotation.Keep
+
+@Keep
+class LyricsList {
+ var structuredLyrics: List? = null
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/cappielloantonio/tempo/subsonic/models/OpenSubsonicExtension.kt b/app/src/main/java/com/cappielloantonio/tempo/subsonic/models/OpenSubsonicExtension.kt
new file mode 100644
index 00000000..dd170c83
--- /dev/null
+++ b/app/src/main/java/com/cappielloantonio/tempo/subsonic/models/OpenSubsonicExtension.kt
@@ -0,0 +1,9 @@
+package com.cappielloantonio.tempo.subsonic.models
+
+import androidx.annotation.Keep
+
+@Keep
+class OpenSubsonicExtension {
+ var name: String? = null
+ var versions: List? = null
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/cappielloantonio/tempo/subsonic/models/StructuredLyrics.kt b/app/src/main/java/com/cappielloantonio/tempo/subsonic/models/StructuredLyrics.kt
new file mode 100644
index 00000000..d77b2e03
--- /dev/null
+++ b/app/src/main/java/com/cappielloantonio/tempo/subsonic/models/StructuredLyrics.kt
@@ -0,0 +1,13 @@
+package com.cappielloantonio.tempo.subsonic.models
+
+import androidx.annotation.Keep
+
+@Keep
+class StructuredLyrics {
+ var displayArtist: String? = null
+ var displayTitle: String? = null
+ var lang: String? = null
+ var offset: Int = 0
+ var synced: Boolean = false
+ var line: List? = null
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/cappielloantonio/tempo/subsonic/models/SubsonicResponse.kt b/app/src/main/java/com/cappielloantonio/tempo/subsonic/models/SubsonicResponse.kt
index 6a43c2da..f886f55c 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/subsonic/models/SubsonicResponse.kt
+++ b/app/src/main/java/com/cappielloantonio/tempo/subsonic/models/SubsonicResponse.kt
@@ -51,4 +51,7 @@ class SubsonicResponse {
var version: String? = null
var type: String? = null
var serverVersion: String? = null
+ var openSubsonic: Boolean? = null
+ var openSubsonicExtensions: List? = null
+ var lyricsList: LyricsList? = null
}
\ No newline at end of file
diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/activity/MainActivity.java b/app/src/main/java/com/cappielloantonio/tempo/ui/activity/MainActivity.java
index e40873e6..40574fce 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/ui/activity/MainActivity.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/ui/activity/MainActivity.java
@@ -70,6 +70,7 @@ public class MainActivity extends BaseActivity {
init();
checkConnectionType();
+ getOpenSubsonicExtensions();
}
@Override
@@ -94,7 +95,7 @@ public class MainActivity extends BaseActivity {
@Override
public void onBackPressed() {
if (bottomSheetBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED)
- collapseBottomSheet();
+ collapseBottomSheetDelayed();
else
super.onBackPressed();
}
@@ -118,7 +119,7 @@ public class MainActivity extends BaseActivity {
bottomSheetBehavior.addBottomSheetCallback(bottomSheetCallback);
fragmentManager.beginTransaction().replace(R.id.player_bottom_sheet, new PlayerBottomSheetFragment(), "PlayerBottomSheet").commit();
- setBottomSheetInPeek(mainViewModel.isQueueLoaded());
+ checkBottomSheetAfterStateChanged();
}
public void setBottomSheetInPeek(Boolean isVisible) {
@@ -137,7 +138,13 @@ public class MainActivity extends BaseActivity {
}
}
- public void collapseBottomSheet() {
+ private void checkBottomSheetAfterStateChanged() {
+ final Handler handler = new Handler();
+ final Runnable runnable = () -> setBottomSheetInPeek(mainViewModel.isQueueLoaded());
+ handler.postDelayed(runnable, 100);
+ }
+
+ public void collapseBottomSheetDelayed() {
final Handler handler = new Handler();
final Runnable runnable = () -> bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
handler.postDelayed(runnable, 100);
@@ -328,11 +335,25 @@ public class MainActivity extends BaseActivity {
private void pingServer() {
if (Preferences.getToken() != null) {
- mainViewModel.ping().observe(this, isPingSuccessfull -> {
- if (!isPingSuccessfull && Preferences.showServerUnreachableDialog()) {
+ mainViewModel.ping().observe(this, subsonicResponse -> {
+ if (subsonicResponse == null && Preferences.showServerUnreachableDialog()) {
ServerUnreachableDialog dialog = new ServerUnreachableDialog();
dialog.show(getSupportFragmentManager(), null);
}
+
+ if (subsonicResponse != null) {
+ Preferences.setOpenSubsonic(subsonicResponse.getOpenSubsonic() != null && subsonicResponse.getOpenSubsonic());
+ }
+ });
+ }
+ }
+
+ private void getOpenSubsonicExtensions() {
+ if (Preferences.getToken() != null) {
+ mainViewModel.getOpenSubsonicExtensions().observe(this, openSubsonicExtensions -> {
+ if (openSubsonicExtensions != null) {
+ Preferences.setOpenSubsonicExtensions(openSubsonicExtensions);
+ }
});
}
}
diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/activity/base/BaseActivity.java b/app/src/main/java/com/cappielloantonio/tempo/ui/activity/base/BaseActivity.java
index 37fb3ebf..9eaa2c36 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/ui/activity/base/BaseActivity.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/ui/activity/base/BaseActivity.java
@@ -6,6 +6,7 @@ import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.os.PowerManager;
+import android.view.WindowManager;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
@@ -37,6 +38,7 @@ public class BaseActivity extends AppCompatActivity {
initializeDownloader();
checkBatteryOptimization();
checkPermission();
+ checkAlwaysOnDisplay();
}
@Override
@@ -66,6 +68,12 @@ public class BaseActivity extends AppCompatActivity {
}
}
+ private void checkAlwaysOnDisplay() {
+ if (Preferences.isDisplayAlwaysOn()) {
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ }
+ }
+
private boolean detectBatteryOptimization() {
String packageName = getPackageName();
PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/adapter/AlbumCatalogueAdapter.java b/app/src/main/java/com/cappielloantonio/tempo/ui/adapter/AlbumCatalogueAdapter.java
index 9fedd9d1..8027578f 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/ui/adapter/AlbumCatalogueAdapter.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/ui/adapter/AlbumCatalogueAdapter.java
@@ -2,6 +2,7 @@ package com.cappielloantonio.tempo.ui.adapter;
import android.os.Bundle;
import android.view.LayoutInflater;
+import android.view.View;
import android.view.ViewGroup;
import android.widget.Filter;
import android.widget.Filterable;
@@ -23,6 +24,9 @@ import java.util.List;
public class AlbumCatalogueAdapter extends RecyclerView.Adapter implements Filterable {
private final ClickCallback click;
+ private String currentFilter;
+ private boolean showArtist;
+
private final Filter filtering = new Filter() {
@Override
protected FilterResults performFiltering(CharSequence constraint) {
@@ -32,6 +36,7 @@ public class AlbumCatalogueAdapter extends RecyclerView.Adapter) results.values;
notifyDataSetChanged();
}
};
@@ -57,9 +61,12 @@ public class AlbumCatalogueAdapter extends RecyclerView.Adapter albums;
private List albumsFull;
- public AlbumCatalogueAdapter(ClickCallback click) {
+ public AlbumCatalogueAdapter(ClickCallback click, boolean showArtist) {
this.click = click;
this.albums = Collections.emptyList();
+ this.albumsFull = Collections.emptyList();
+ this.currentFilter = "";
+ this.showArtist = showArtist;
}
@NonNull
@@ -75,6 +82,7 @@ public class AlbumCatalogueAdapter extends RecyclerView.Adapter albums) {
- this.albums = albums;
this.albumsFull = new ArrayList<>(albums);
- notifyDataSetChanged();
+ filtering.filter(currentFilter);
}
@Override
@@ -158,8 +165,12 @@ public class AlbumCatalogueAdapter extends RecyclerView.Adapter songs;
+ private List shuffling;
private List grouped;
public DownloadHorizontalAdapter(ClickCallback click) {
@@ -82,6 +83,7 @@ public class DownloadHorizontalAdapter extends RecyclerView.Adapter(songs));
notifyDataSetChanged();
}
@@ -90,6 +92,10 @@ public class DownloadHorizontalAdapter extends RecyclerView.Adapter getShuffling() {
+ return shuffling;
+ }
+
@Override
public int getItemViewType(int position) {
return position;
@@ -136,6 +142,27 @@ public class DownloadHorizontalAdapter extends RecyclerView.Adapter shufflingSong(List songs) {
+ if (filterValue == null) {
+ return songs;
+ }
+
+ 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());
+ default:
+ return songs;
+ }
+ }
+
private String countSong(String filterKey, String filterValue, List songs) {
if (filterValue != null) {
switch (filterKey) {
@@ -159,7 +186,15 @@ public class DownloadHorizontalAdapter extends RecyclerView.Adapter {
+ private List sectors;
+
+ public HomeSectorHorizontalAdapter() {
+ this.sectors = Collections.emptyList();
+ }
+
+ @NonNull
+ @Override
+ public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ ItemHorizontalHomeSectorBinding view = ItemHorizontalHomeSectorBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
+ return new ViewHolder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(ViewHolder holder, int position) {
+ HomeSector sector = sectors.get(position);
+
+ holder.item.homeSectorTitleCheckBox.setText(sector.getSectorTitle());
+ holder.item.homeSectorTitleCheckBox.setChecked(sector.isVisible());
+ }
+
+ @Override
+ public int getItemCount() {
+ return sectors.size();
+ }
+
+ public List getItems() {
+ return this.sectors;
+ }
+
+ public void setItems(List sectors) {
+ this.sectors = sectors;
+ notifyDataSetChanged();
+ }
+
+ public HomeSector getItem(int id) {
+ return sectors.get(id);
+ }
+
+ public class ViewHolder extends RecyclerView.ViewHolder {
+ ItemHorizontalHomeSectorBinding item;
+
+ ViewHolder(ItemHorizontalHomeSectorBinding item) {
+ super(item.getRoot());
+
+ this.item = item;
+
+ this.item.homeSectorTitleCheckBox.setOnCheckedChangeListener((buttonView, isChecked) -> onCheck(isChecked));
+ }
+
+ private void onCheck(boolean isChecked) {
+ sectors.get(getBindingAdapterPosition()).setVisible(isChecked);
+ }
+ }
+}
diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/adapter/PlayerSongQueueAdapter.java b/app/src/main/java/com/cappielloantonio/tempo/ui/adapter/PlayerSongQueueAdapter.java
index a0defa1e..d0b9764d 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/ui/adapter/PlayerSongQueueAdapter.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/ui/adapter/PlayerSongQueueAdapter.java
@@ -2,9 +2,11 @@ package com.cappielloantonio.tempo.ui.adapter;
import android.os.Bundle;
import android.view.LayoutInflater;
+import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
+import androidx.appcompat.content.res.AppCompatResources;
import androidx.media3.session.MediaBrowser;
import androidx.recyclerview.widget.RecyclerView;
@@ -17,6 +19,7 @@ import com.cappielloantonio.tempo.service.MediaManager;
import com.cappielloantonio.tempo.subsonic.models.Child;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil;
+import com.cappielloantonio.tempo.util.Preferences;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.ArrayList;
@@ -46,7 +49,14 @@ public class PlayerSongQueueAdapter extends RecyclerView.Adapter= 1 ? R.drawable.ic_star : R.drawable.ic_star_outlined));
+ holder.item.twoStarIcon.setImageDrawable(AppCompatResources.getDrawable(holder.itemView.getContext(), song.getUserRating() >= 2 ? R.drawable.ic_star : R.drawable.ic_star_outlined));
+ holder.item.threeStarIcon.setImageDrawable(AppCompatResources.getDrawable(holder.itemView.getContext(), song.getUserRating() >= 3 ? R.drawable.ic_star : R.drawable.ic_star_outlined));
+ holder.item.fourStarIcon.setImageDrawable(AppCompatResources.getDrawable(holder.itemView.getContext(), song.getUserRating() >= 4 ? R.drawable.ic_star : R.drawable.ic_star_outlined));
+ holder.item.fiveStarIcon.setImageDrawable(AppCompatResources.getDrawable(holder.itemView.getContext(), song.getUserRating() >= 5 ? R.drawable.ic_star : R.drawable.ic_star_outlined));
+ }
+ } else {
+ holder.item.ratingIndicatorImageView.setVisibility(View.GONE);
+ }
}
public List getItems() {
diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/adapter/SongHorizontalAdapter.java b/app/src/main/java/com/cappielloantonio/tempo/ui/adapter/SongHorizontalAdapter.java
index 367660ce..c2e1d7d6 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/ui/adapter/SongHorizontalAdapter.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/ui/adapter/SongHorizontalAdapter.java
@@ -6,6 +6,7 @@ import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
+import androidx.appcompat.content.res.AppCompatResources;
import androidx.media3.common.util.UnstableApi;
import androidx.recyclerview.widget.RecyclerView;
@@ -17,6 +18,7 @@ import com.cappielloantonio.tempo.subsonic.models.Child;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.DownloadUtil;
import com.cappielloantonio.tempo.util.MusicUtil;
+import com.cappielloantonio.tempo.util.Preferences;
import java.util.ArrayList;
import java.util.Collections;
@@ -49,13 +51,26 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter 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);
}
+
+ if (Preferences.showItemRating()) {
+ if (song.getStarred() == null && song.getUserRating() == null) {
+ holder.item.ratingIndicatorImageView.setVisibility(View.GONE);
+ }
+
+ holder.item.preferredIcon.setVisibility(song.getStarred() != null ? View.VISIBLE : View.GONE);
+ holder.item.ratingBarLayout.setVisibility(song.getUserRating() != null ? View.VISIBLE : View.GONE);
+
+ if (song.getUserRating() != null) {
+ holder.item.oneStarIcon.setImageDrawable(AppCompatResources.getDrawable(holder.itemView.getContext(), song.getUserRating() >= 1 ? R.drawable.ic_star : R.drawable.ic_star_outlined));
+ holder.item.twoStarIcon.setImageDrawable(AppCompatResources.getDrawable(holder.itemView.getContext(), song.getUserRating() >= 2 ? R.drawable.ic_star : R.drawable.ic_star_outlined));
+ holder.item.threeStarIcon.setImageDrawable(AppCompatResources.getDrawable(holder.itemView.getContext(), song.getUserRating() >= 3 ? R.drawable.ic_star : R.drawable.ic_star_outlined));
+ holder.item.fourStarIcon.setImageDrawable(AppCompatResources.getDrawable(holder.itemView.getContext(), song.getUserRating() >= 4 ? R.drawable.ic_star : R.drawable.ic_star_outlined));
+ holder.item.fiveStarIcon.setImageDrawable(AppCompatResources.getDrawable(holder.itemView.getContext(), song.getUserRating() >= 5 ? R.drawable.ic_star : R.drawable.ic_star_outlined));
+ }
+ } else {
+ holder.item.ratingIndicatorImageView.setVisibility(View.GONE);
+ }
}
@Override
diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/dialog/HomeRearrangementDialog.java b/app/src/main/java/com/cappielloantonio/tempo/ui/dialog/HomeRearrangementDialog.java
new file mode 100644
index 00000000..8ed67e42
--- /dev/null
+++ b/app/src/main/java/com/cappielloantonio/tempo/ui/dialog/HomeRearrangementDialog.java
@@ -0,0 +1,115 @@
+package com.cappielloantonio.tempo.ui.dialog;
+
+import android.app.Dialog;
+import android.os.Bundle;
+import androidx.annotation.NonNull;
+import androidx.fragment.app.DialogFragment;
+import androidx.lifecycle.ViewModelProvider;
+import androidx.recyclerview.widget.ItemTouchHelper;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.cappielloantonio.tempo.R;
+import com.cappielloantonio.tempo.databinding.DialogHomeRearrangementBinding;
+import com.cappielloantonio.tempo.ui.adapter.HomeSectorHorizontalAdapter;
+import com.cappielloantonio.tempo.viewmodel.HomeRearrangementViewModel;
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+
+import java.util.Collections;
+import java.util.Objects;
+
+public class HomeRearrangementDialog extends DialogFragment {
+ private DialogHomeRearrangementBinding bind;
+ private HomeRearrangementViewModel homeRearrangementViewModel;
+ private HomeSectorHorizontalAdapter homeSectorHorizontalAdapter;
+
+ @NonNull
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ bind = DialogHomeRearrangementBinding.inflate(getLayoutInflater());
+
+ homeRearrangementViewModel = new ViewModelProvider(requireActivity()).get(HomeRearrangementViewModel.class);
+
+ return new MaterialAlertDialogBuilder(requireContext())
+ .setView(bind.getRoot())
+ .setTitle(R.string.home_rearrangement_dialog_title)
+ .setPositiveButton(R.string.home_rearrangement_dialog_positive_button, (dialog, id) -> { })
+ .setNeutralButton(R.string.home_rearrangement_dialog_neutral_button, (dialog, id) -> { })
+ .setNegativeButton(R.string.home_rearrangement_dialog_negative_button, (dialog, id) -> dialog.cancel())
+ .create();
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+
+ setButtonAction();
+ initSectorView();
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ homeRearrangementViewModel.closeDialog();
+ bind = null;
+ }
+
+ private void setButtonAction() {
+ androidx.appcompat.app.AlertDialog alertDialog = (androidx.appcompat.app.AlertDialog) Objects.requireNonNull(getDialog());
+
+ alertDialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> {
+ homeRearrangementViewModel.saveHomeSectorList(homeSectorHorizontalAdapter.getItems());
+ dismiss();
+ });
+
+ alertDialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_NEUTRAL).setOnClickListener(v -> {
+ homeRearrangementViewModel.resetHomeSectorList();
+ dismiss();
+ });
+ }
+
+ private void initSectorView() {
+ bind.homeSectorItemRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
+ bind.homeSectorItemRecyclerView.setHasFixedSize(true);
+
+ homeSectorHorizontalAdapter = new HomeSectorHorizontalAdapter();
+ bind.homeSectorItemRecyclerView.setAdapter(homeSectorHorizontalAdapter);
+ homeSectorHorizontalAdapter.setItems(homeRearrangementViewModel.getHomeSectorList());
+
+ new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN, 0) {
+ int originalPosition = -1;
+ int fromPosition = -1;
+ int toPosition = -1;
+
+ @Override
+ public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
+ if (originalPosition == -1) originalPosition = viewHolder.getBindingAdapterPosition();
+
+ fromPosition = viewHolder.getBindingAdapterPosition();
+ toPosition = target.getBindingAdapterPosition();
+
+ Collections.swap(homeSectorHorizontalAdapter.getItems(), fromPosition, toPosition);
+ Objects.requireNonNull(recyclerView.getAdapter()).notifyItemMoved(fromPosition, toPosition);
+
+ return false;
+ }
+
+ @Override
+ public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
+ super.clearView(recyclerView, viewHolder);
+
+ homeRearrangementViewModel.orderSectorLiveListAfterSwap(homeSectorHorizontalAdapter.getItems());
+
+ originalPosition = -1;
+ fromPosition = -1;
+ toPosition = -1;
+ }
+
+ @Override
+ public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
+
+ }
+ }
+ ).attachToRecyclerView(bind.homeSectorItemRecyclerView);
+ }
+}
diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/dialog/PlaylistChooserDialog.java b/app/src/main/java/com/cappielloantonio/tempo/ui/dialog/PlaylistChooserDialog.java
index 8ce5c48e..f0266c84 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/ui/dialog/PlaylistChooserDialog.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/ui/dialog/PlaylistChooserDialog.java
@@ -83,7 +83,7 @@ public class PlaylistChooserDialog extends DialogFragment implements ClickCallba
playlistChooserViewModel.getPlaylistList(requireActivity()).observe(requireActivity(), playlists -> {
if (playlists != null) {
- if (playlists.size() > 0) {
+ if (!playlists.isEmpty()) {
if (bind != null) bind.noPlaylistsCreatedTextView.setVisibility(View.GONE);
if (bind != null) bind.playlistDialogRecyclerView.setVisibility(View.VISIBLE);
playlistDialogHorizontalAdapter.setItems(playlists);
diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/dialog/PlaylistEditorDialog.java b/app/src/main/java/com/cappielloantonio/tempo/ui/dialog/PlaylistEditorDialog.java
index 9a5c759f..edc949c9 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/ui/dialog/PlaylistEditorDialog.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/ui/dialog/PlaylistEditorDialog.java
@@ -31,7 +31,8 @@ import java.util.Objects;
public class PlaylistEditorDialog extends DialogFragment {
private DialogPlaylistEditorBinding bind;
private PlaylistEditorViewModel playlistEditorViewModel;
- private PlaylistCallback playlistCallback;
+
+ private final PlaylistCallback playlistCallback;
private String playlistName;
private PlaylistDialogSongHorizontalAdapter playlistDialogSongHorizontalAdapter;
diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/dialog/PodcastChannelEditorDialog.java b/app/src/main/java/com/cappielloantonio/tempo/ui/dialog/PodcastChannelEditorDialog.java
index 09d92b3c..2226ab85 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/ui/dialog/PodcastChannelEditorDialog.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/ui/dialog/PodcastChannelEditorDialog.java
@@ -20,7 +20,8 @@ import java.util.Objects;
public class PodcastChannelEditorDialog extends DialogFragment {
private DialogPodcastChannelEditorBinding bind;
private PodcastChannelEditorViewModel podcastChannelEditorViewModel;
- private PodcastCallback podcastCallback;
+
+ private final PodcastCallback podcastCallback;
private String channelUrl;
diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/dialog/RadioEditorDialog.java b/app/src/main/java/com/cappielloantonio/tempo/ui/dialog/RadioEditorDialog.java
index 38cb7a82..b4aba968 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/ui/dialog/RadioEditorDialog.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/ui/dialog/RadioEditorDialog.java
@@ -21,7 +21,8 @@ import java.util.Objects;
public class RadioEditorDialog extends DialogFragment {
private DialogRadioEditorBinding bind;
private RadioEditorViewModel radioEditorViewModel;
- private RadioCallback radioCallback;
+
+ private final RadioCallback radioCallback;
private String radioName;
private String radioStreamURL;
diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/dialog/TrackInfoDialog.java b/app/src/main/java/com/cappielloantonio/tempo/ui/dialog/TrackInfoDialog.java
index aad842c3..ca9b6d20 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/ui/dialog/TrackInfoDialog.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/ui/dialog/TrackInfoDialog.java
@@ -17,7 +17,8 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder;
public class TrackInfoDialog extends DialogFragment {
private DialogTrackInfoBinding bind;
- private MediaMetadata mediaMetadata;
+
+ private final MediaMetadata mediaMetadata;
public TrackInfoDialog(MediaMetadata mediaMetadata) {
this.mediaMetadata = mediaMetadata;
@@ -28,7 +29,7 @@ public class TrackInfoDialog extends DialogFragment {
public Dialog onCreateDialog(Bundle savedInstanceState) {
bind = DialogTrackInfoBinding.inflate(getLayoutInflater());
- return new MaterialAlertDialogBuilder(getActivity())
+ return new MaterialAlertDialogBuilder(requireActivity())
.setView(bind.getRoot())
.setPositiveButton(R.string.track_info_dialog_positive_button, (dialog, id) -> dialog.cancel())
.create();
diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/AlbumCatalogueFragment.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/AlbumCatalogueFragment.java
index 3ccf2c99..e33b5142 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/AlbumCatalogueFragment.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/AlbumCatalogueFragment.java
@@ -52,6 +52,12 @@ public class AlbumCatalogueFragment extends Fragment implements ClickCallback {
initData();
}
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ albumCatalogueViewModel.stopLoading();
+ }
+
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
@@ -61,6 +67,7 @@ public class AlbumCatalogueFragment extends Fragment implements ClickCallback {
initAppBar();
initAlbumCatalogueView();
+ initProgressLoader();
return view;
}
@@ -73,7 +80,7 @@ public class AlbumCatalogueFragment extends Fragment implements ClickCallback {
private void initData() {
albumCatalogueViewModel = new ViewModelProvider(requireActivity()).get(AlbumCatalogueViewModel.class);
- albumCatalogueViewModel.loadAlbums(500);
+ albumCatalogueViewModel.loadAlbums();
}
private void initAppBar() {
@@ -105,7 +112,7 @@ public class AlbumCatalogueFragment extends Fragment implements ClickCallback {
bind.albumCatalogueRecyclerView.addItemDecoration(new GridItemDecoration(2, 20, false));
bind.albumCatalogueRecyclerView.setHasFixedSize(true);
- albumAdapter = new AlbumCatalogueAdapter(this);
+ albumAdapter = new AlbumCatalogueAdapter(this, true);
albumAdapter.setStateRestorationPolicy(RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY);
bind.albumCatalogueRecyclerView.setAdapter(albumAdapter);
albumCatalogueViewModel.getAlbumList().observe(getViewLifecycleOwner(), albums -> albumAdapter.setItems(albums));
@@ -118,6 +125,18 @@ public class AlbumCatalogueFragment extends Fragment implements ClickCallback {
bind.albumListSortImageView.setOnClickListener(view -> showPopupMenu(view, R.menu.sort_album_popup_menu));
}
+ private void initProgressLoader() {
+ albumCatalogueViewModel.getLoadingStatus().observe(getViewLifecycleOwner(), isLoading -> {
+ if (isLoading) {
+ bind.albumListSortImageView.setEnabled(false);
+ bind.albumListProgressLoader.setVisibility(View.VISIBLE);
+ } else {
+ bind.albumListSortImageView.setEnabled(true);
+ bind.albumListProgressLoader.setVisibility(View.GONE);
+ }
+ });
+ }
+
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
inflater.inflate(R.menu.toolbar_menu, menu);
@@ -165,6 +184,9 @@ public class AlbumCatalogueFragment extends Fragment implements ClickCallback {
} else if (menuItem.getItemId() == R.id.menu_album_sort_random) {
albumAdapter.sort(Constants.ALBUM_ORDER_BY_RANDOM);
return true;
+ } else if (menuItem.getItemId() == R.id.menu_album_sort_recently_added) {
+ albumAdapter.sort(Constants.ALBUM_ORDER_BY_RECENTLY_ADDED);
+ return true;
}
return false;
diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/AlbumPageFragment.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/AlbumPageFragment.java
index 1bba506b..99b797fe 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/AlbumPageFragment.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/AlbumPageFragment.java
@@ -34,6 +34,7 @@ import com.cappielloantonio.tempo.util.DownloadUtil;
import com.cappielloantonio.tempo.util.MappingUtil;
import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.viewmodel.AlbumPageViewModel;
+import com.google.android.material.chip.Chip;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.Collections;
@@ -73,6 +74,7 @@ public class AlbumPageFragment extends Fragment implements ClickCallback {
init();
initAppBar();
initAlbumInfoTextButton();
+ initAlbumNotes();
initMusicButton();
initBackCover();
initSongsView();
@@ -131,10 +133,20 @@ public class AlbumPageFragment extends Fragment implements ClickCallback {
bind.albumNameLabel.setText(MusicUtil.getReadableString(albumPageViewModel.getAlbum().getName()));
bind.albumArtistLabel.setText(MusicUtil.getReadableString(albumPageViewModel.getAlbum().getArtist()));
bind.albumReleaseYearLabel.setText(albumPageViewModel.getAlbum().getYear() != 0 ? String.valueOf(albumPageViewModel.getAlbum().getYear()) : "");
+ bind.albumSongCountDurationTextview.setText(getString(R.string.album_page_tracks_count_and_duration, albumPageViewModel.getAlbum().getSongCount(), albumPageViewModel.getAlbum().getDuration() != null ? albumPageViewModel.getAlbum().getDuration() / 60 : 0));
+ bind.albumGenresTextview.setText(albumPageViewModel.getAlbum().getGenre());
bind.animToolbar.setNavigationOnClickListener(v -> activity.navController.navigateUp());
Objects.requireNonNull(bind.animToolbar.getOverflowIcon()).setTint(requireContext().getResources().getColor(R.color.titleTextColor, null));
+
+ bind.albumOtherInfoButton.setOnClickListener(v -> {
+ if (bind.albumDetailView.getVisibility() == View.GONE) {
+ bind.albumDetailView.setVisibility(View.VISIBLE);
+ } else if (bind.albumDetailView.getVisibility() == View.VISIBLE) {
+ bind.albumDetailView.setVisibility(View.GONE);
+ }
+ });
}
private void initAlbumInfoTextButton() {
@@ -148,6 +160,18 @@ public class AlbumPageFragment extends Fragment implements ClickCallback {
}));
}
+ private void initAlbumNotes() {
+ albumPageViewModel.getAlbumInfo().observe(getViewLifecycleOwner(), albumInfo -> {
+ if (albumInfo != null) {
+ if (bind != null) bind.albumNotesTextview.setVisibility(View.VISIBLE);
+ if (bind != null)
+ bind.albumNotesTextview.setText(MusicUtil.getReadableString(albumInfo.getNotes()));
+ } else {
+ if (bind != null) bind.albumNotesTextview.setVisibility(View.GONE);
+ }
+ });
+ }
+
private void initMusicButton() {
albumPageViewModel.getAlbumSongLiveList().observe(getViewLifecycleOwner(), songs -> {
if (bind != null && !songs.isEmpty()) {
diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/ArtistPageFragment.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/ArtistPageFragment.java
index 0e4f050c..23c034fd 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/ArtistPageFragment.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/ArtistPageFragment.java
@@ -27,16 +27,23 @@ import com.cappielloantonio.tempo.helper.recyclerview.GridItemDecoration;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.service.MediaManager;
import com.cappielloantonio.tempo.service.MediaService;
+import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.AlbumArtistPageOrSimilarAdapter;
import com.cappielloantonio.tempo.ui.adapter.AlbumCatalogueAdapter;
+import com.cappielloantonio.tempo.ui.adapter.ArtistCatalogueAdapter;
import com.cappielloantonio.tempo.ui.adapter.ArtistSimilarAdapter;
import com.cappielloantonio.tempo.ui.adapter.SongHorizontalAdapter;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil;
+import com.cappielloantonio.tempo.util.Preferences;
import com.cappielloantonio.tempo.viewmodel.ArtistPageViewModel;
import com.google.common.util.concurrent.ListenableFuture;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
@UnstableApi
public class ArtistPageFragment extends Fragment implements ClickCallback {
private FragmentArtistPageBinding bind;
@@ -44,9 +51,8 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
private ArtistPageViewModel artistPageViewModel;
private SongHorizontalAdapter songHorizontalAdapter;
- private AlbumArtistPageOrSimilarAdapter albumArtistPageOrSimilarAdapter;
private AlbumCatalogueAdapter albumCatalogueAdapter;
- private ArtistSimilarAdapter artistSimilarAdapter;
+ private ArtistCatalogueAdapter artistCatalogueAdapter;
private ListenableFuture mediaBrowserListenableFuture;
@@ -63,8 +69,7 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
initArtistInfo();
initPlayButtons();
initTopSongsView();
- initHorizontalAlbumsView();
- initVerticalAlbumsView();
+ initAlbumsView();
initSimilarArtistsView();
return view;
@@ -98,13 +103,6 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
bundle.putParcelable(Constants.ARTIST_OBJECT, artistPageViewModel.getArtist());
activity.navController.navigate(R.id.action_artistPageFragment_to_songListPageFragment, bundle);
});
-
- bind.artistPageAlbumsSwitchLayoutTextViewClickable.setOnClickListener(view -> {
- boolean isHorizontalRecyclerViewVisible = bind.albumsHorizontalRecyclerView.getVisibility() == View.VISIBLE;
-
- bind.albumsHorizontalRecyclerView.setVisibility(isHorizontalRecyclerViewVisible ? View.GONE : View.VISIBLE);
- bind.albumsVerticalRecyclerView.setVisibility(isHorizontalRecyclerViewVisible ? View.VISIBLE : View.GONE);
- });
}
private void initAppBar() {
@@ -120,8 +118,6 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
private void initArtistInfo() {
artistPageViewModel.getArtistInfo(artistPageViewModel.getArtist().getId()).observe(getViewLifecycleOwner(), artistInfo -> {
if (artistInfo == null) {
- if (bind != null)
- bind.artistPageBioPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.artistPageBioSector.setVisibility(View.GONE);
} else {
String normalizedBio = MusicUtil.forceReadableString(artistInfo.getBiography());
@@ -144,8 +140,6 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
startActivity(intent);
});
- if (bind != null)
- bind.artistPageBioPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null) bind.artistPageBioSector.setVisibility(View.VISIBLE);
}
});
@@ -154,7 +148,7 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
private void initPlayButtons() {
bind.artistPageShuffleButton.setOnClickListener(v -> {
artistPageViewModel.getArtistShuffleList().observe(getViewLifecycleOwner(), songs -> {
- if (songs.size() > 0) {
+ if (!songs.isEmpty()) {
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
activity.setBottomSheetInPeek(true);
} else {
@@ -165,7 +159,7 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
bind.artistPageRadioButton.setOnClickListener(v -> {
artistPageViewModel.getArtistInstantMix().observe(getViewLifecycleOwner(), songs -> {
- if (songs.size() > 0) {
+ if (!songs.isEmpty()) {
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
activity.setBottomSheetInPeek(true);
} else {
@@ -182,58 +176,29 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
bind.mostStreamedSongRecyclerView.setAdapter(songHorizontalAdapter);
artistPageViewModel.getArtistTopSongList().observe(getViewLifecycleOwner(), songs -> {
if (songs == null) {
- if (bind != null)
- bind.artistPageTopTracksPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.artistPageTopSongsSector.setVisibility(View.GONE);
} else {
- if (bind != null)
- bind.artistPageTopTracksPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.artistPageTopSongsSector.setVisibility(!songs.isEmpty() ? View.VISIBLE : View.GONE);
+ if (bind != null)
+ bind.artistPageShuffleButton.setEnabled(!songs.isEmpty());
songHorizontalAdapter.setItems(songs);
}
});
}
- private void initHorizontalAlbumsView() {
- bind.albumsHorizontalRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false));
+ private void initAlbumsView() {
+ bind.albumsRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), 2));
+ bind.albumsRecyclerView.addItemDecoration(new GridItemDecoration(2, 20, false));
+ bind.albumsRecyclerView.setHasFixedSize(true);
- albumArtistPageOrSimilarAdapter = new AlbumArtistPageOrSimilarAdapter(this);
- bind.albumsHorizontalRecyclerView.setAdapter(albumArtistPageOrSimilarAdapter);
- artistPageViewModel.getAlbumList().observe(getViewLifecycleOwner(), albums -> {
- if (albums == null) {
- if (bind != null)
- bind.artistPageAlbumPlaceholder.placeholder.setVisibility(View.VISIBLE);
- if (bind != null) bind.artistPageAlbumsSector.setVisibility(View.GONE);
- } else {
- if (bind != null)
- bind.artistPageAlbumPlaceholder.placeholder.setVisibility(View.GONE);
- if (bind != null)
- bind.artistPageAlbumsSector.setVisibility(!albums.isEmpty() ? View.VISIBLE : View.GONE);
- albumArtistPageOrSimilarAdapter.setItems(albums);
- }
- });
-
- CustomLinearSnapHelper albumSnapHelper = new CustomLinearSnapHelper();
- albumSnapHelper.attachToRecyclerView(bind.albumsHorizontalRecyclerView);
- }
-
- private void initVerticalAlbumsView() {
- bind.albumsVerticalRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), 2));
- bind.albumsVerticalRecyclerView.addItemDecoration(new GridItemDecoration(2, 20, false));
- bind.albumsVerticalRecyclerView.setHasFixedSize(true);
-
- albumCatalogueAdapter = new AlbumCatalogueAdapter(this);
- bind.albumsVerticalRecyclerView.setAdapter(albumCatalogueAdapter);
+ albumCatalogueAdapter = new AlbumCatalogueAdapter(this, false);
+ bind.albumsRecyclerView.setAdapter(albumCatalogueAdapter);
artistPageViewModel.getAlbumList().observe(getViewLifecycleOwner(), albums -> {
if (albums == null) {
- if (bind != null)
- bind.artistPageAlbumPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.artistPageAlbumsSector.setVisibility(View.GONE);
} else {
- if (bind != null)
- bind.artistPageAlbumPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.artistPageAlbumsSector.setVisibility(!albums.isEmpty() ? View.VISIBLE : View.GONE);
albumCatalogueAdapter.setItems(albums);
@@ -242,22 +207,27 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
}
private void initSimilarArtistsView() {
- bind.similarArtistsRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false));
+ bind.similarArtistsRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), 2));
+ bind.similarArtistsRecyclerView.addItemDecoration(new GridItemDecoration(2, 20, false));
bind.similarArtistsRecyclerView.setHasFixedSize(true);
- artistSimilarAdapter = new ArtistSimilarAdapter(this);
- bind.similarArtistsRecyclerView.setAdapter(artistSimilarAdapter);
+ artistCatalogueAdapter = new ArtistCatalogueAdapter(this);
+ bind.similarArtistsRecyclerView.setAdapter(artistCatalogueAdapter);
+
artistPageViewModel.getArtistInfo(artistPageViewModel.getArtist().getId()).observe(getViewLifecycleOwner(), artist -> {
if (artist == null) {
- if (bind != null)
- bind.artistPageSimilarArtistPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.similarArtistSector.setVisibility(View.GONE);
} else {
- if (bind != null)
- bind.artistPageSimilarArtistPlaceholder.placeholder.setVisibility(View.GONE);
- if (bind != null)
+ if (bind != null && artist.getSimilarArtists() != null)
bind.similarArtistSector.setVisibility(!artist.getSimilarArtists().isEmpty() ? View.VISIBLE : View.GONE);
- artistSimilarAdapter.setItems(artist.getSimilarArtists());
+
+ List artists = new ArrayList<>();
+
+ if (artist.getSimilarArtists() != null) {
+ artists.addAll(artist.getSimilarArtists());
+ }
+
+ artistCatalogueAdapter.setItems(artists);
}
});
diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/DownloadFragment.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/DownloadFragment.java
index 83995d8a..b8108fe3 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/DownloadFragment.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/DownloadFragment.java
@@ -33,6 +33,7 @@ import com.cappielloantonio.tempo.viewmodel.DownloadViewModel;
import com.google.android.material.appbar.MaterialToolbar;
import com.google.common.util.concurrent.ListenableFuture;
+import java.util.Collections;
import java.util.List;
import java.util.Objects;
@@ -110,20 +111,14 @@ public class DownloadFragment extends Fragment implements ClickCallback {
if (bind != null) {
bind.emptyDownloadLayout.setVisibility(View.VISIBLE);
bind.fragmentDownloadNestedScrollView.setVisibility(View.GONE);
-
- bind.downloadDownloadedPlaceholder.placeholder.setVisibility(View.VISIBLE);
bind.downloadDownloadedSector.setVisibility(View.GONE);
-
bind.downloadedGroupByImageView.setVisibility(View.GONE);
}
} else {
if (bind != null) {
bind.emptyDownloadLayout.setVisibility(View.GONE);
bind.fragmentDownloadNestedScrollView.setVisibility(View.VISIBLE);
-
- bind.downloadDownloadedPlaceholder.placeholder.setVisibility(View.GONE);
bind.downloadDownloadedSector.setVisibility(View.VISIBLE);
-
bind.downloadedGroupByImageView.setVisibility(View.VISIBLE);
finishDownloadView(songs);
@@ -165,6 +160,19 @@ public class DownloadFragment extends Fragment implements ClickCallback {
bind.downloadedGoBackImageView.setVisibility(stack.size() > 1 ? View.VISIBLE : View.GONE);
setupBackPressing(stack.size());
+ setupShuffleButton();
+ });
+ }
+
+ private void setupShuffleButton() {
+ bind.shuffleDownloadedTextViewClickable.setOnClickListener(view -> {
+ List songs = downloadHorizontalAdapter.getShuffling();
+
+ if (songs != null && !songs.isEmpty()) {
+ Collections.shuffle(songs);
+ MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
+ activity.setBottomSheetInPeek(true);
+ }
});
}
diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/HomeTabMusicFragment.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/HomeTabMusicFragment.java
index 251699f0..a4cd411f 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/HomeTabMusicFragment.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/HomeTabMusicFragment.java
@@ -32,6 +32,7 @@ import com.cappielloantonio.tempo.helper.recyclerview.DotsIndicatorDecoration;
import com.cappielloantonio.tempo.helper.recyclerview.GridItemDecoration;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.model.Download;
+import com.cappielloantonio.tempo.model.HomeSector;
import com.cappielloantonio.tempo.service.DownloaderManager;
import com.cappielloantonio.tempo.service.MediaManager;
import com.cappielloantonio.tempo.service.MediaService;
@@ -48,6 +49,7 @@ import com.cappielloantonio.tempo.ui.adapter.ShareHorizontalAdapter;
import com.cappielloantonio.tempo.ui.adapter.SimilarTrackAdapter;
import com.cappielloantonio.tempo.ui.adapter.SongHorizontalAdapter;
import com.cappielloantonio.tempo.ui.adapter.YearAdapter;
+import com.cappielloantonio.tempo.ui.dialog.HomeRearrangementDialog;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.DownloadUtil;
import com.cappielloantonio.tempo.util.MappingUtil;
@@ -119,6 +121,9 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
initRecentAddedAlbumView();
initGridView();
initSharesView();
+ initHomeReorganizer();
+
+ reorder();
}
@Override
@@ -156,7 +161,7 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
homeViewModel.getRandomShuffleSample().observe(getViewLifecycleOwner(), songs -> {
MusicUtil.ratingFilter(songs);
- if (songs.size() > 0) {
+ if (!songs.isEmpty()) {
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
activity.setBottomSheetInPeek(true);
}
@@ -303,6 +308,8 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
}
private void initDiscoverSongSlideView() {
+ if (homeViewModel.checkHomeSectorVisibility(Constants.HOME_SECTOR_DISCOVERY)) return;
+
bind.discoverSongViewPager.setOrientation(ViewPager2.ORIENTATION_HORIZONTAL);
discoverSongAdapter = new DiscoverSongAdapter(this);
@@ -312,12 +319,8 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
MusicUtil.ratingFilter(songs);
if (songs == null) {
- if (bind != null)
- bind.homeDiscoveryPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.homeDiscoverSector.setVisibility(View.GONE);
} else {
- if (bind != null)
- bind.homeDiscoveryPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.homeDiscoverSector.setVisibility(!songs.isEmpty() ? View.VISIBLE : View.GONE);
@@ -329,6 +332,8 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
}
private void initSimilarSongView() {
+ if (homeViewModel.checkHomeSectorVisibility(Constants.HOME_SECTOR_MADE_FOR_YOU)) return;
+
bind.similarTracksRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false));
bind.similarTracksRecyclerView.setHasFixedSize(true);
@@ -338,12 +343,8 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
MusicUtil.ratingFilter(songs);
if (songs == null) {
- if (bind != null)
- bind.homeSimilarTracksPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.homeSimilarTracksSector.setVisibility(View.GONE);
} else {
- if (bind != null)
- bind.homeSimilarTracksPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.homeSimilarTracksSector.setVisibility(!songs.isEmpty() ? View.VISIBLE : View.GONE);
@@ -356,6 +357,8 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
}
private void initArtistBestOf() {
+ if (homeViewModel.checkHomeSectorVisibility(Constants.HOME_SECTOR_BEST_OF)) return;
+
bind.bestOfArtistRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false));
bind.bestOfArtistRecyclerView.setHasFixedSize(true);
@@ -363,12 +366,8 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
bind.bestOfArtistRecyclerView.setAdapter(bestOfArtistAdapter);
homeViewModel.getBestOfArtists(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), artists -> {
if (artists == null) {
- if (bind != null)
- bind.homeBestOfArtistPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.homeBestOfArtistSector.setVisibility(View.GONE);
} else {
- if (bind != null)
- bind.homeBestOfArtistPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.homeBestOfArtistSector.setVisibility(!artists.isEmpty() ? View.VISIBLE : View.GONE);
@@ -381,6 +380,8 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
}
private void initArtistRadio() {
+ if (homeViewModel.checkHomeSectorVisibility(Constants.HOME_SECTOR_RADIO_STATION)) return;
+
bind.radioArtistRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false));
bind.radioArtistRecyclerView.setHasFixedSize(true);
@@ -388,12 +389,8 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
bind.radioArtistRecyclerView.setAdapter(radioArtistAdapter);
homeViewModel.getStarredArtistsSample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), artists -> {
if (artists == null) {
- if (bind != null)
- bind.homeRadioArtistPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.homeRadioArtistSector.setVisibility(View.GONE);
} else {
- if (bind != null)
- bind.homeRadioArtistPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.homeRadioArtistSector.setVisibility(!artists.isEmpty() ? View.VISIBLE : View.GONE);
if (bind != null)
@@ -408,6 +405,8 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
}
private void initGridView() {
+ if (homeViewModel.checkHomeSectorVisibility(Constants.HOME_SECTOR_TOP_SONGS)) return;
+
bind.gridTracksRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), 3));
bind.gridTracksRecyclerView.addItemDecoration(new GridItemDecoration(3, 8, false));
bind.gridTracksRecyclerView.setHasFixedSize(true);
@@ -418,7 +417,7 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
homeViewModel.getDiscoverSongSample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), music -> {
if (music != null) {
homeViewModel.getGridSongSample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), chronologies -> {
- if (chronologies == null || chronologies.size() == 0) {
+ if (chronologies == null || chronologies.isEmpty()) {
if (bind != null) bind.homeGridTracksSector.setVisibility(View.GONE);
if (bind != null) bind.afterGridDivider.setVisibility(View.GONE);
} else {
@@ -432,18 +431,16 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
}
private void initStarredTracksView() {
+ if (homeViewModel.checkHomeSectorVisibility(Constants.HOME_SECTOR_STARRED_TRACKS)) return;
+
bind.starredTracksRecyclerView.setHasFixedSize(true);
starredSongAdapter = new SongHorizontalAdapter(this, true, false);
bind.starredTracksRecyclerView.setAdapter(starredSongAdapter);
homeViewModel.getStarredTracks(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), songs -> {
if (songs == null) {
- if (bind != null)
- bind.starredTracksPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.starredTracksSector.setVisibility(View.GONE);
} else {
- if (bind != null)
- bind.starredTracksPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.starredTracksSector.setVisibility(!songs.isEmpty() ? View.VISIBLE : View.GONE);
if (bind != null)
@@ -467,18 +464,16 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
}
private void initStarredAlbumsView() {
+ if (homeViewModel.checkHomeSectorVisibility(Constants.HOME_SECTOR_STARRED_ALBUMS)) return;
+
bind.starredAlbumsRecyclerView.setHasFixedSize(true);
starredAlbumAdapter = new AlbumHorizontalAdapter(this, false);
bind.starredAlbumsRecyclerView.setAdapter(starredAlbumAdapter);
homeViewModel.getStarredAlbums(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), albums -> {
if (albums == null) {
- if (bind != null)
- bind.starredAlbumsPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.starredAlbumsSector.setVisibility(View.GONE);
} else {
- if (bind != null)
- bind.starredAlbumsPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.starredAlbumsSector.setVisibility(!albums.isEmpty() ? View.VISIBLE : View.GONE);
if (bind != null)
@@ -502,18 +497,16 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
}
private void initStarredArtistsView() {
+ if (homeViewModel.checkHomeSectorVisibility(Constants.HOME_SECTOR_STARRED_ARTISTS)) return;
+
bind.starredArtistsRecyclerView.setHasFixedSize(true);
starredArtistAdapter = new ArtistHorizontalAdapter(this);
bind.starredArtistsRecyclerView.setAdapter(starredArtistAdapter);
homeViewModel.getStarredArtists(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), artists -> {
if (artists == null) {
- if (bind != null)
- bind.starredArtistsPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.starredArtistsSector.setVisibility(View.GONE);
} else {
- if (bind != null)
- bind.starredArtistsPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.starredArtistsSector.setVisibility(!artists.isEmpty() ? View.VISIBLE : View.GONE);
if (bind != null)
@@ -539,18 +532,16 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
}
private void initNewReleasesView() {
+ if (homeViewModel.checkHomeSectorVisibility(Constants.HOME_SECTOR_NEW_RELEASES)) return;
+
bind.newReleasesRecyclerView.setHasFixedSize(true);
newReleasesAlbumAdapter = new AlbumHorizontalAdapter(this, false);
bind.newReleasesRecyclerView.setAdapter(newReleasesAlbumAdapter);
homeViewModel.getRecentlyReleasedAlbums(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), albums -> {
if (albums == null) {
- if (bind != null)
- bind.homeNewReleasesPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.homeNewReleasesSector.setVisibility(View.GONE);
} else {
- if (bind != null)
- bind.homeNewReleasesPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.homeNewReleasesSector.setVisibility(!albums.isEmpty() ? View.VISIBLE : View.GONE);
if (bind != null)
@@ -574,6 +565,8 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
}
private void initYearSongView() {
+ if (homeViewModel.checkHomeSectorVisibility(Constants.HOME_SECTOR_FLASHBACK)) return;
+
bind.yearsRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false));
bind.yearsRecyclerView.setHasFixedSize(true);
@@ -581,12 +574,8 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
bind.yearsRecyclerView.setAdapter(yearAdapter);
homeViewModel.getYearList(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), years -> {
if (years == null) {
- if (bind != null)
- bind.homeFlashbackPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.homeFlashbackSector.setVisibility(View.GONE);
} else {
- if (bind != null)
- bind.homeFlashbackPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.homeFlashbackSector.setVisibility(!years.isEmpty() ? View.VISIBLE : View.GONE);
@@ -599,6 +588,8 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
}
private void initMostPlayedAlbumView() {
+ if (homeViewModel.checkHomeSectorVisibility(Constants.HOME_SECTOR_MOST_PLAYED)) return;
+
bind.mostPlayedAlbumsRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false));
bind.mostPlayedAlbumsRecyclerView.setHasFixedSize(true);
@@ -606,15 +597,10 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
bind.mostPlayedAlbumsRecyclerView.setAdapter(mostPlayedAlbumAdapter);
homeViewModel.getMostPlayedAlbums(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), albums -> {
if (albums == null) {
- if (bind != null)
- bind.homeMostPlayedAlbumsPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.homeMostPlayedAlbumsSector.setVisibility(View.GONE);
} else {
- if (bind != null)
- bind.homeMostPlayedAlbumsPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.homeMostPlayedAlbumsSector.setVisibility(!albums.isEmpty() ? View.VISIBLE : View.GONE);
- // if (albums.size() < 5) reorder();
mostPlayedAlbumAdapter.setItems(albums);
}
@@ -625,6 +611,8 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
}
private void initRecentPlayedAlbumView() {
+ if (homeViewModel.checkHomeSectorVisibility(Constants.HOME_SECTOR_LAST_PLAYED)) return;
+
bind.recentlyPlayedAlbumsRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false));
bind.recentlyPlayedAlbumsRecyclerView.setHasFixedSize(true);
@@ -632,12 +620,8 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
bind.recentlyPlayedAlbumsRecyclerView.setAdapter(recentlyPlayedAlbumAdapter);
homeViewModel.getRecentlyPlayedAlbumList(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), albums -> {
if (albums == null) {
- if (bind != null)
- bind.homeRecentlyPlayedAlbumsPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.homeRecentlyPlayedAlbumsSector.setVisibility(View.GONE);
} else {
- if (bind != null)
- bind.homeRecentlyPlayedAlbumsPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.homeRecentlyPlayedAlbumsSector.setVisibility(!albums.isEmpty() ? View.VISIBLE : View.GONE);
@@ -650,6 +634,8 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
}
private void initRecentAddedAlbumView() {
+ if (homeViewModel.checkHomeSectorVisibility(Constants.HOME_SECTOR_RECENTLY_ADDED)) return;
+
bind.recentlyAddedAlbumsRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false));
bind.recentlyAddedAlbumsRecyclerView.setHasFixedSize(true);
@@ -657,12 +643,8 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
bind.recentlyAddedAlbumsRecyclerView.setAdapter(recentlyAddedAlbumAdapter);
homeViewModel.getMostRecentlyAddedAlbums(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), albums -> {
if (albums == null) {
- if (bind != null)
- bind.homeRecentlyAddedAlbumsPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.homeRecentlyAddedAlbumsSector.setVisibility(View.GONE);
} else {
- if (bind != null)
- bind.homeRecentlyAddedAlbumsPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.homeRecentlyAddedAlbumsSector.setVisibility(!albums.isEmpty() ? View.VISIBLE : View.GONE);
@@ -675,6 +657,8 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
}
private void initSharesView() {
+ if (homeViewModel.checkHomeSectorVisibility(Constants.HOME_SECTOR_SHARED)) return;
+
bind.sharesRecyclerView.setHasFixedSize(true);
shareHorizontalAdapter = new ShareHorizontalAdapter(this);
@@ -682,11 +666,8 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
if (Preferences.isSharingEnabled()) {
homeViewModel.getShares(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), shares -> {
if (shares == null) {
- if (bind != null)
- bind.sharesPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.sharesSector.setVisibility(View.GONE);
} else {
- if (bind != null) bind.sharesPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.sharesSector.setVisibility(!shares.isEmpty() ? View.VISIBLE : View.GONE);
if (bind != null)
@@ -710,6 +691,17 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
);
}
+ private void initHomeReorganizer() {
+ final Handler handler = new Handler();
+ final Runnable runnable = () -> { if (bind != null) bind.homeSectorRearrangementButton.setVisibility(View.VISIBLE); };
+ handler.postDelayed(runnable, 5000);
+
+ bind.homeSectorRearrangementButton.setOnClickListener(v -> {
+ HomeRearrangementDialog dialog = new HomeRearrangementDialog();
+ dialog.show(requireActivity().getSupportFragmentManager(), null);
+ });
+ }
+
private void refreshSharesView() {
final Handler handler = new Handler();
final Runnable runnable = () -> {
@@ -735,6 +727,63 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
});
}
+ public void reorder() {
+ if (bind != null && homeViewModel.getHomeSectorList() != null) {
+ bind.homeLinearLayoutContainer.removeAllViews();
+
+ for (HomeSector sector : homeViewModel.getHomeSectorList()) {
+ if (!sector.isVisible()) continue;
+
+ switch (sector.getId()) {
+ case Constants.HOME_SECTOR_DISCOVERY:
+ bind.homeLinearLayoutContainer.addView(bind.homeDiscoverSector);
+ break;
+ case Constants.HOME_SECTOR_MADE_FOR_YOU:
+ bind.homeLinearLayoutContainer.addView(bind.homeSimilarTracksSector);
+ break;
+ case Constants.HOME_SECTOR_BEST_OF:
+ bind.homeLinearLayoutContainer.addView(bind.homeBestOfArtistSector);
+ break;
+ case Constants.HOME_SECTOR_RADIO_STATION:
+ bind.homeLinearLayoutContainer.addView(bind.homeRadioArtistSector);
+ break;
+ case Constants.HOME_SECTOR_TOP_SONGS:
+ bind.homeLinearLayoutContainer.addView(bind.homeGridTracksSector);
+ break;
+ case Constants.HOME_SECTOR_STARRED_TRACKS:
+ bind.homeLinearLayoutContainer.addView(bind.starredTracksSector);
+ break;
+ case Constants.HOME_SECTOR_STARRED_ALBUMS:
+ bind.homeLinearLayoutContainer.addView(bind.starredAlbumsSector);
+ break;
+ case Constants.HOME_SECTOR_STARRED_ARTISTS:
+ bind.homeLinearLayoutContainer.addView(bind.starredArtistsSector);
+ break;
+ case Constants.HOME_SECTOR_NEW_RELEASES:
+ bind.homeLinearLayoutContainer.addView(bind.homeNewReleasesSector);
+ break;
+ case Constants.HOME_SECTOR_FLASHBACK:
+ bind.homeLinearLayoutContainer.addView(bind.homeFlashbackSector);
+ break;
+ case Constants.HOME_SECTOR_MOST_PLAYED:
+ bind.homeLinearLayoutContainer.addView(bind.homeMostPlayedAlbumsSector);
+ break;
+ case Constants.HOME_SECTOR_LAST_PLAYED:
+ bind.homeLinearLayoutContainer.addView(bind.homeRecentlyPlayedAlbumsSector);
+ break;
+ case Constants.HOME_SECTOR_RECENTLY_ADDED:
+ bind.homeLinearLayoutContainer.addView(bind.homeRecentlyAddedAlbumsSector);
+ break;
+ case Constants.HOME_SECTOR_SHARED:
+ bind.homeLinearLayoutContainer.addView(bind.sharesSector);
+ break;
+ }
+ }
+
+ bind.homeLinearLayoutContainer.addView(bind.homeSectorRearrangementButton);
+ }
+ }
+
private void initializeMediaBrowser() {
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
}
@@ -753,7 +802,7 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
homeViewModel.getMediaInstantMix(getViewLifecycleOwner(), bundle.getParcelable(Constants.TRACK_OBJECT)).observe(getViewLifecycleOwner(), songs -> {
MusicUtil.ratingFilter(songs);
- if (songs != null && songs.size() > 0) {
+ if (songs != null && !songs.isEmpty()) {
MediaManager.enqueue(mediaBrowserListenableFuture, songs, true);
}
});
@@ -794,7 +843,7 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
homeViewModel.getArtistInstantMix(getViewLifecycleOwner(), bundle.getParcelable(Constants.ARTIST_OBJECT)).observe(getViewLifecycleOwner(), songs -> {
MusicUtil.ratingFilter(songs);
- if (songs.size() > 0) {
+ if (!songs.isEmpty()) {
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
activity.setBottomSheetInPeek(true);
}
@@ -805,7 +854,7 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
homeViewModel.getArtistBestOf(getViewLifecycleOwner(), bundle.getParcelable(Constants.ARTIST_OBJECT)).observe(getViewLifecycleOwner(), songs -> {
MusicUtil.ratingFilter(songs);
- if (songs.size() > 0) {
+ if (!songs.isEmpty()) {
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
activity.setBottomSheetInPeek(true);
}
diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/LibraryFragment.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/LibraryFragment.java
index b395ab77..711b1c6e 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/LibraryFragment.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/LibraryFragment.java
@@ -142,12 +142,8 @@ public class LibraryFragment extends Fragment implements ClickCallback {
bind.musicFolderRecyclerView.setAdapter(musicFolderAdapter);
libraryViewModel.getMusicFolders(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), musicFolders -> {
if (musicFolders == null) {
- if (bind != null)
- bind.libraryMusicFolderPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.libraryMusicFolderSector.setVisibility(View.GONE);
} else {
- if (bind != null)
- bind.libraryMusicFolderPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.libraryMusicFolderSector.setVisibility(!musicFolders.isEmpty() ? View.VISIBLE : View.GONE);
@@ -164,11 +160,8 @@ public class LibraryFragment extends Fragment implements ClickCallback {
bind.albumRecyclerView.setAdapter(albumAdapter);
libraryViewModel.getAlbumSample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), albums -> {
if (albums == null) {
- if (bind != null)
- bind.libraryAlbumPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.libraryAlbumSector.setVisibility(View.GONE);
} else {
- if (bind != null) bind.libraryAlbumPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.libraryAlbumSector.setVisibility(!albums.isEmpty() ? View.VISIBLE : View.GONE);
@@ -188,12 +181,8 @@ public class LibraryFragment extends Fragment implements ClickCallback {
bind.artistRecyclerView.setAdapter(artistAdapter);
libraryViewModel.getArtistSample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), artists -> {
if (artists == null) {
- if (bind != null)
- bind.libraryArtistPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.libraryArtistSector.setVisibility(View.GONE);
} else {
- if (bind != null)
- bind.libraryArtistPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.libraryArtistSector.setVisibility(!artists.isEmpty() ? View.VISIBLE : View.GONE);
@@ -214,11 +203,8 @@ public class LibraryFragment extends Fragment implements ClickCallback {
libraryViewModel.getGenreSample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), genres -> {
if (genres == null) {
- if (bind != null)
- bind.libraryGenrePlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.libraryGenresSector.setVisibility(View.GONE);
} else {
- if (bind != null) bind.libraryGenrePlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.libraryGenresSector.setVisibility(!genres.isEmpty() ? View.VISIBLE : View.GONE);
@@ -238,12 +224,8 @@ public class LibraryFragment extends Fragment implements ClickCallback {
bind.playlistRecyclerView.setAdapter(playlistHorizontalAdapter);
libraryViewModel.getPlaylistSample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), playlists -> {
if (playlists == null) {
- if (bind != null)
- bind.libraryPlaylistPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.libraryPlaylistSector.setVisibility(View.GONE);
} else {
- if (bind != null)
- bind.libraryPlaylistPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.libraryPlaylistSector.setVisibility(!playlists.isEmpty() ? View.VISIBLE : View.GONE);
diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/LoginFragment.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/LoginFragment.java
index f88e19b5..63f6fe3b 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/LoginFragment.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/LoginFragment.java
@@ -92,7 +92,7 @@ public class LoginFragment extends Fragment implements ClickCallback {
serverAdapter = new ServerAdapter(this);
bind.serverListRecyclerView.setAdapter(serverAdapter);
loginViewModel.getServerList().observe(getViewLifecycleOwner(), servers -> {
- if (servers.size() > 0) {
+ if (!servers.isEmpty()) {
if (bind != null) bind.noServerAddedTextView.setVisibility(View.GONE);
if (bind != null) bind.serverListRecyclerView.setVisibility(View.VISIBLE);
serverAdapter.setItems(servers);
diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerBottomSheetFragment.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerBottomSheetFragment.java
index 761c6f3c..e618ec12 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerBottomSheetFragment.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerBottomSheetFragment.java
@@ -249,6 +249,11 @@ public class PlayerBottomSheetFragment extends Fragment {
bind.playerBodyLayout.playerBodyBottomSheetViewPager.setCurrentItem(1, true);
}
+ public void setPlayerControllerVerticalPagerDraggableState(Boolean isDraggable) {
+ ViewPager2 playerControllerVerticalPager = (ViewPager2) bind.playerBodyLayout.playerBodyBottomSheetViewPager;
+ playerControllerVerticalPager.setUserInputEnabled(isDraggable);
+ }
+
private void defineProgressBarHandler(MediaBrowser mediaBrowser) {
progressBarHandler = new Handler();
progressBarRunnable = () -> {
diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerControllerFragment.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerControllerFragment.java
index 08e36a4a..2b48c1ad 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerControllerFragment.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerControllerFragment.java
@@ -257,10 +257,20 @@ public class PlayerControllerFragment extends Fragment {
public void onPageSelected(int position) {
super.onPageSelected(position);
+ PlayerBottomSheetFragment playerBottomSheetFragment = (PlayerBottomSheetFragment) requireActivity().getSupportFragmentManager().findFragmentByTag("PlayerBottomSheet");
+
if (position == 0) {
activity.setBottomSheetDraggableState(true);
+
+ if (playerBottomSheetFragment != null) {
+ playerBottomSheetFragment.setPlayerControllerVerticalPagerDraggableState(true);
+ }
} else if (position == 1) {
activity.setBottomSheetDraggableState(false);
+
+ if (playerBottomSheetFragment != null) {
+ playerBottomSheetFragment.setPlayerControllerVerticalPagerDraggableState(false);
+ }
}
}
});
@@ -296,7 +306,7 @@ public class PlayerControllerFragment extends Fragment {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.ARTIST_OBJECT, artist);
NavHostFragment.findNavController(this).navigate(R.id.artistPageFragment, bundle);
- activity.collapseBottomSheet();
+ activity.collapseBottomSheetDelayed();
});
}
});
diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerCoverFragment.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerCoverFragment.java
index d29b0f53..f44ed41e 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerCoverFragment.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerCoverFragment.java
@@ -80,7 +80,10 @@ public class PlayerCoverFragment extends Fragment {
handler.removeCallbacksAndMessages(null);
- final Runnable runnable = () -> bind.nowPlayingTapButton.setVisibility(View.GONE);
+ final Runnable runnable = () -> {
+ if (bind != null) bind.nowPlayingTapButton.setVisibility(View.GONE);
+ };
+
handler.postDelayed(runnable, 10000);
}
diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerLyricsFragment.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerLyricsFragment.java
index a492014e..58e7a7c5 100644
--- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerLyricsFragment.java
+++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/PlayerLyricsFragment.java
@@ -1,31 +1,60 @@
package com.cappielloantonio.tempo.ui.fragment;
+import android.annotation.SuppressLint;
+import android.content.ComponentName;
import android.os.Bundle;
+import android.os.Handler;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.style.ForegroundColorSpan;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.OptIn;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
+import androidx.media3.common.util.UnstableApi;
+import androidx.media3.session.MediaBrowser;
+import androidx.media3.session.SessionToken;
+import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.InnerFragmentPlayerLyricsBinding;
+import com.cappielloantonio.tempo.service.MediaService;
+import com.cappielloantonio.tempo.subsonic.models.Line;
+import com.cappielloantonio.tempo.subsonic.models.LyricsList;
import com.cappielloantonio.tempo.util.MusicUtil;
+import com.cappielloantonio.tempo.util.OpenSubsonicExtensionsUtil;
import com.cappielloantonio.tempo.viewmodel.PlayerBottomSheetViewModel;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.MoreExecutors;
+import java.util.List;
+
+
+@OptIn(markerClass = UnstableApi.class)
public class PlayerLyricsFragment extends Fragment {
private static final String TAG = "PlayerLyricsFragment";
private InnerFragmentPlayerLyricsBinding bind;
private PlayerBottomSheetViewModel playerBottomSheetViewModel;
+ private ListenableFuture mediaBrowserListenableFuture;
+ private MediaBrowser mediaBrowser;
+ private Handler syncLyricsHandler;
+ private Runnable syncLyricsRunnable;
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
bind = InnerFragmentPlayerLyricsBinding.inflate(inflater, container, false);
View view = bind.getRoot();
+
playerBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(PlayerBottomSheetViewModel.class);
+ initOverlay();
+
return view;
}
@@ -33,7 +62,32 @@ public class PlayerLyricsFragment extends Fragment {
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
- initLyrics();
+ initPanelContent();
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ initializeBrowser();
+
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ bindMediaController();
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ releaseHandler();
+ }
+
+ @Override
+ public void onStop() {
+ releaseBrowser();
+ super.onStop();
}
@Override
@@ -42,27 +96,195 @@ public class PlayerLyricsFragment extends Fragment {
bind = null;
}
- private void initLyrics() {
- playerBottomSheetViewModel.getLiveLyrics().observe(getViewLifecycleOwner(), lyrics -> {
- playerBottomSheetViewModel.getLiveDescription().observe(getViewLifecycleOwner(), description -> {
- if (bind != null) {
- if (lyrics != null && !lyrics.trim().equals("")) {
- bind.nowPlayingSongLyricsTextView.setText(MusicUtil.getReadableLyrics(lyrics));
- bind.nowPlayingSongLyricsTextView.setVisibility(View.VISIBLE);
- bind.emptyDescriptionImageView.setVisibility(View.GONE);
- bind.titleEmptyDescriptionLabel.setVisibility(View.GONE);
- } else if (description != null && !description.trim().equals("")) {
- bind.nowPlayingSongLyricsTextView.setText(MusicUtil.getReadableLyrics(description));
- bind.nowPlayingSongLyricsTextView.setVisibility(View.VISIBLE);
- bind.emptyDescriptionImageView.setVisibility(View.GONE);
- bind.titleEmptyDescriptionLabel.setVisibility(View.GONE);
- } else {
- bind.nowPlayingSongLyricsTextView.setVisibility(View.GONE);
- bind.emptyDescriptionImageView.setVisibility(View.VISIBLE);
- bind.titleEmptyDescriptionLabel.setVisibility(View.VISIBLE);
- }
- }
- });
+ private void initOverlay() {
+ bind.syncLyricsTapButton.setOnClickListener(view -> {
+ playerBottomSheetViewModel.changeSyncLyricsState();
});
}
+
+ private void initializeBrowser() {
+ mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
+ }
+
+ private void releaseHandler() {
+ if (syncLyricsHandler != null) {
+ syncLyricsHandler.removeCallbacks(syncLyricsRunnable);
+ syncLyricsHandler = null;
+ }
+ }
+
+ private void releaseBrowser() {
+ MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
+ }
+
+ private void bindMediaController() {
+ mediaBrowserListenableFuture.addListener(() -> {
+ try {
+ mediaBrowser = mediaBrowserListenableFuture.get();
+ defineProgressHandler();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }, MoreExecutors.directExecutor());
+ }
+
+ private void initPanelContent() {
+ if (OpenSubsonicExtensionsUtil.isSongLyricsExtensionAvailable()) {
+ playerBottomSheetViewModel.getLiveLyricsList().observe(getViewLifecycleOwner(), lyricsList -> {
+ setPanelContent(null, lyricsList);
+ });
+ } else {
+ playerBottomSheetViewModel.getLiveLyrics().observe(getViewLifecycleOwner(), lyrics -> {
+ setPanelContent(lyrics, null);
+ });
+ }
+ }
+
+ private void setPanelContent(String lyrics, LyricsList lyricsList) {
+ playerBottomSheetViewModel.getLiveDescription().observe(getViewLifecycleOwner(), description -> {
+ if (bind != null) {
+ bind.nowPlayingSongLyricsSrollView.smoothScrollTo(0, 0);
+
+ if (lyrics != null && !lyrics.trim().equals("")) {
+ bind.nowPlayingSongLyricsTextView.setText(MusicUtil.getReadableLyrics(lyrics));
+ bind.nowPlayingSongLyricsTextView.setVisibility(View.VISIBLE);
+ bind.emptyDescriptionImageView.setVisibility(View.GONE);
+ bind.titleEmptyDescriptionLabel.setVisibility(View.GONE);
+ bind.syncLyricsTapButton.setVisibility(View.GONE);
+ } else if (lyricsList != null && lyricsList.getStructuredLyrics() != null) {
+ setSyncLirics(lyricsList);
+ bind.nowPlayingSongLyricsTextView.setVisibility(View.VISIBLE);
+ bind.emptyDescriptionImageView.setVisibility(View.GONE);
+ bind.titleEmptyDescriptionLabel.setVisibility(View.GONE);
+ bind.syncLyricsTapButton.setVisibility(View.VISIBLE);
+ } else if (description != null && !description.trim().equals("")) {
+ bind.nowPlayingSongLyricsTextView.setText(MusicUtil.getReadableLyrics(description));
+ bind.nowPlayingSongLyricsTextView.setVisibility(View.VISIBLE);
+ bind.emptyDescriptionImageView.setVisibility(View.GONE);
+ bind.titleEmptyDescriptionLabel.setVisibility(View.GONE);
+ bind.syncLyricsTapButton.setVisibility(View.GONE);
+ } else {
+ bind.nowPlayingSongLyricsTextView.setVisibility(View.GONE);
+ bind.emptyDescriptionImageView.setVisibility(View.VISIBLE);
+ bind.titleEmptyDescriptionLabel.setVisibility(View.VISIBLE);
+ bind.syncLyricsTapButton.setVisibility(View.GONE);
+ }
+ }
+ });
+ }
+
+ @SuppressLint("DefaultLocale")
+ private void setSyncLirics(LyricsList lyricsList) {
+ if (lyricsList.getStructuredLyrics() != null && !lyricsList.getStructuredLyrics().isEmpty() && lyricsList.getStructuredLyrics().get(0).getLine() != null) {
+ StringBuilder lyricsBuilder = new StringBuilder();
+ List lines = lyricsList.getStructuredLyrics().get(0).getLine();
+
+ if (lines != null) {
+ for (Line line : lines) {
+ lyricsBuilder.append(line.getValue().trim()).append("\n");
+ }
+ }
+
+ bind.nowPlayingSongLyricsTextView.setText(lyricsBuilder.toString());
+ }
+ }
+
+ private void defineProgressHandler() {
+ playerBottomSheetViewModel.getLiveLyricsList().observe(getViewLifecycleOwner(), lyricsList -> {
+ if (lyricsList != null) {
+
+ if (lyricsList.getStructuredLyrics() != null && lyricsList.getStructuredLyrics().get(0) != null && !lyricsList.getStructuredLyrics().get(0).getSynced()) {
+ releaseHandler();
+ return;
+ }
+
+ syncLyricsHandler = new Handler();
+ syncLyricsRunnable = () -> {
+ if (syncLyricsHandler != null) {
+ if (bind != null) {
+ displaySyncedLyrics();
+ }
+
+ syncLyricsHandler.postDelayed(syncLyricsRunnable, 250);
+ }
+ };
+
+ syncLyricsHandler.postDelayed(syncLyricsRunnable, 250);
+ } else {
+ releaseHandler();
+ }
+ });
+ }
+
+ private void displaySyncedLyrics() {
+ LyricsList lyricsList = playerBottomSheetViewModel.getLiveLyricsList().getValue();
+ int timestamp = (int) (mediaBrowser.getCurrentPosition());
+
+ if (lyricsList != null && lyricsList.getStructuredLyrics() != null && !lyricsList.getStructuredLyrics().isEmpty() && lyricsList.getStructuredLyrics().get(0).getLine() != null) {
+ StringBuilder lyricsBuilder = new StringBuilder();
+ List