mirror of
https://github.com/CappielloAntonio/tempo.git
synced 2026-01-31 14:43:36 +00:00
Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aa68c2c3d8 | ||
|
|
9c0ebca66f | ||
|
|
888f177597 | ||
|
|
c69fbcfa68 | ||
|
|
f044db142c | ||
|
|
9af7bc3ac8 | ||
|
|
cd87fcde26 | ||
|
|
9e1a6c804f | ||
|
|
4c15f6eb01 | ||
|
|
4ad2722e81 | ||
|
|
b267b904cc | ||
|
|
1ebe9ff8ba | ||
|
|
623a4956a5 | ||
|
|
6572b846c8 | ||
|
|
fe3ba9fb89 | ||
|
|
c4b9db303a | ||
|
|
aed52fdbf8 | ||
|
|
f9573b3eab | ||
|
|
7a8880ee68 | ||
|
|
68aae32d06 | ||
|
|
4b07f37378 | ||
|
|
4d573c6b9d | ||
|
|
10dcb2380c |
@@ -74,7 +74,7 @@ dependencies {
|
|||||||
implementation 'androidx.preference:preference-ktx:1.2.0'
|
implementation 'androidx.preference:preference-ktx:1.2.0'
|
||||||
implementation 'androidx.navigation:navigation-fragment-ktx:2.6.0'
|
implementation 'androidx.navigation:navigation-fragment-ktx:2.6.0'
|
||||||
implementation 'androidx.navigation:navigation-ui-ktx:2.6.0'
|
implementation 'androidx.navigation:navigation-ui-ktx:2.6.0'
|
||||||
implementation 'androidx.recyclerview:recyclerview:1.3.0'
|
implementation 'androidx.recyclerview:recyclerview:1.3.1'
|
||||||
implementation 'androidx.room:room-runtime:2.5.2'
|
implementation 'androidx.room:room-runtime:2.5.2'
|
||||||
implementation 'androidx.core:core-splashscreen:1.0.1'
|
implementation 'androidx.core:core-splashscreen:1.0.1'
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,746 @@
|
|||||||
|
{
|
||||||
|
"formatVersion": 1,
|
||||||
|
"database": {
|
||||||
|
"version": 1,
|
||||||
|
"identityHash": "1f4e50f90f58fb9cb53c89747d142fd9",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"tableName": "queue",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `track_order` INTEGER NOT NULL, `last_play` INTEGER NOT NULL, `playing_changed` INTEGER NOT NULL, `stream_id` TEXT, `parent_id` TEXT, `is_dir` INTEGER NOT NULL, `title` TEXT, `album` TEXT, `artist` TEXT, `track` INTEGER, `year` INTEGER, `genre` TEXT, `cover_art_id` TEXT, `size` INTEGER, `content_type` TEXT, `suffix` TEXT, `transcoding_content_type` TEXT, `transcoded_suffix` TEXT, `duration` INTEGER, `bitrate` INTEGER, `path` TEXT, `is_video` INTEGER NOT NULL, `user_rating` INTEGER, `average_rating` REAL, `play_count` INTEGER, `disc_number` INTEGER, `created` INTEGER, `starred` INTEGER, `album_id` TEXT, `artist_id` TEXT, `type` TEXT, `bookmark_position` INTEGER, `original_width` INTEGER, `original_height` INTEGER, PRIMARY KEY(`track_order`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "trackOrder",
|
||||||
|
"columnName": "track_order",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lastPlay",
|
||||||
|
"columnName": "last_play",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "playingChanged",
|
||||||
|
"columnName": "playing_changed",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "streamId",
|
||||||
|
"columnName": "stream_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "parentId",
|
||||||
|
"columnName": "parent_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isDir",
|
||||||
|
"columnName": "is_dir",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "title",
|
||||||
|
"columnName": "title",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "album",
|
||||||
|
"columnName": "album",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "artist",
|
||||||
|
"columnName": "artist",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "track",
|
||||||
|
"columnName": "track",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "year",
|
||||||
|
"columnName": "year",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "genre",
|
||||||
|
"columnName": "genre",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "coverArtId",
|
||||||
|
"columnName": "cover_art_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "size",
|
||||||
|
"columnName": "size",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "contentType",
|
||||||
|
"columnName": "content_type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "suffix",
|
||||||
|
"columnName": "suffix",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "transcodedContentType",
|
||||||
|
"columnName": "transcoding_content_type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "transcodedSuffix",
|
||||||
|
"columnName": "transcoded_suffix",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "duration",
|
||||||
|
"columnName": "duration",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "bitrate",
|
||||||
|
"columnName": "bitrate",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "path",
|
||||||
|
"columnName": "path",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isVideo",
|
||||||
|
"columnName": "is_video",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "userRating",
|
||||||
|
"columnName": "user_rating",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "averageRating",
|
||||||
|
"columnName": "average_rating",
|
||||||
|
"affinity": "REAL",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "playCount",
|
||||||
|
"columnName": "play_count",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "discNumber",
|
||||||
|
"columnName": "disc_number",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "created",
|
||||||
|
"columnName": "created",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "starred",
|
||||||
|
"columnName": "starred",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "albumId",
|
||||||
|
"columnName": "album_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "artistId",
|
||||||
|
"columnName": "artist_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "type",
|
||||||
|
"columnName": "type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "bookmarkPosition",
|
||||||
|
"columnName": "bookmark_position",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "originalWidth",
|
||||||
|
"columnName": "original_width",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "originalHeight",
|
||||||
|
"columnName": "original_height",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"track_order"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "server",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `server_name` TEXT NOT NULL, `username` TEXT NOT NULL, `password` TEXT NOT NULL, `address` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, `low_security` INTEGER NOT NULL DEFAULT false, PRIMARY KEY(`id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "serverId",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "serverName",
|
||||||
|
"columnName": "server_name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "username",
|
||||||
|
"columnName": "username",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "password",
|
||||||
|
"columnName": "password",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "address",
|
||||||
|
"columnName": "address",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "timestamp",
|
||||||
|
"columnName": "timestamp",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isLowSecurity",
|
||||||
|
"columnName": "low_security",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "recent_search",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`search` TEXT NOT NULL, PRIMARY KEY(`search`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "search",
|
||||||
|
"columnName": "search",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"search"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "download",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `playlist_id` TEXT, `playlist_name` TEXT, `download_state` INTEGER NOT NULL DEFAULT 1, `parent_id` TEXT, `is_dir` INTEGER NOT NULL, `title` TEXT, `album` TEXT, `artist` TEXT, `track` INTEGER, `year` INTEGER, `genre` TEXT, `cover_art_id` TEXT, `size` INTEGER, `content_type` TEXT, `suffix` TEXT, `transcoding_content_type` TEXT, `transcoded_suffix` TEXT, `duration` INTEGER, `bitrate` INTEGER, `path` TEXT, `is_video` INTEGER NOT NULL, `user_rating` INTEGER, `average_rating` REAL, `play_count` INTEGER, `disc_number` INTEGER, `created` INTEGER, `starred` INTEGER, `album_id` TEXT, `artist_id` TEXT, `type` TEXT, `bookmark_position` INTEGER, `original_width` INTEGER, `original_height` INTEGER, PRIMARY KEY(`id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "playlistId",
|
||||||
|
"columnName": "playlist_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "playlistName",
|
||||||
|
"columnName": "playlist_name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "downloadState",
|
||||||
|
"columnName": "download_state",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "parentId",
|
||||||
|
"columnName": "parent_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isDir",
|
||||||
|
"columnName": "is_dir",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "title",
|
||||||
|
"columnName": "title",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "album",
|
||||||
|
"columnName": "album",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "artist",
|
||||||
|
"columnName": "artist",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "track",
|
||||||
|
"columnName": "track",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "year",
|
||||||
|
"columnName": "year",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "genre",
|
||||||
|
"columnName": "genre",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "coverArtId",
|
||||||
|
"columnName": "cover_art_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "size",
|
||||||
|
"columnName": "size",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "contentType",
|
||||||
|
"columnName": "content_type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "suffix",
|
||||||
|
"columnName": "suffix",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "transcodedContentType",
|
||||||
|
"columnName": "transcoding_content_type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "transcodedSuffix",
|
||||||
|
"columnName": "transcoded_suffix",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "duration",
|
||||||
|
"columnName": "duration",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "bitrate",
|
||||||
|
"columnName": "bitrate",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "path",
|
||||||
|
"columnName": "path",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isVideo",
|
||||||
|
"columnName": "is_video",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "userRating",
|
||||||
|
"columnName": "user_rating",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "averageRating",
|
||||||
|
"columnName": "average_rating",
|
||||||
|
"affinity": "REAL",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "playCount",
|
||||||
|
"columnName": "play_count",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "discNumber",
|
||||||
|
"columnName": "disc_number",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "created",
|
||||||
|
"columnName": "created",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "starred",
|
||||||
|
"columnName": "starred",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "albumId",
|
||||||
|
"columnName": "album_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "artistId",
|
||||||
|
"columnName": "artist_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "type",
|
||||||
|
"columnName": "type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "bookmarkPosition",
|
||||||
|
"columnName": "bookmark_position",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "originalWidth",
|
||||||
|
"columnName": "original_width",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "originalHeight",
|
||||||
|
"columnName": "original_height",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "chronology",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, `server` TEXT, `parent_id` TEXT, `is_dir` INTEGER NOT NULL, `title` TEXT, `album` TEXT, `artist` TEXT, `track` INTEGER, `year` INTEGER, `genre` TEXT, `cover_art_id` TEXT, `size` INTEGER, `content_type` TEXT, `suffix` TEXT, `transcoding_content_type` TEXT, `transcoded_suffix` TEXT, `duration` INTEGER, `bitrate` INTEGER, `path` TEXT, `is_video` INTEGER NOT NULL, `user_rating` INTEGER, `average_rating` REAL, `play_count` INTEGER, `disc_number` INTEGER, `created` INTEGER, `starred` INTEGER, `album_id` TEXT, `artist_id` TEXT, `type` TEXT, `bookmark_position` INTEGER, `original_width` INTEGER, `original_height` INTEGER, PRIMARY KEY(`id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "timestamp",
|
||||||
|
"columnName": "timestamp",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "server",
|
||||||
|
"columnName": "server",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "parentId",
|
||||||
|
"columnName": "parent_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isDir",
|
||||||
|
"columnName": "is_dir",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "title",
|
||||||
|
"columnName": "title",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "album",
|
||||||
|
"columnName": "album",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "artist",
|
||||||
|
"columnName": "artist",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "track",
|
||||||
|
"columnName": "track",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "year",
|
||||||
|
"columnName": "year",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "genre",
|
||||||
|
"columnName": "genre",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "coverArtId",
|
||||||
|
"columnName": "cover_art_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "size",
|
||||||
|
"columnName": "size",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "contentType",
|
||||||
|
"columnName": "content_type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "suffix",
|
||||||
|
"columnName": "suffix",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "transcodedContentType",
|
||||||
|
"columnName": "transcoding_content_type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "transcodedSuffix",
|
||||||
|
"columnName": "transcoded_suffix",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "duration",
|
||||||
|
"columnName": "duration",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "bitrate",
|
||||||
|
"columnName": "bitrate",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "path",
|
||||||
|
"columnName": "path",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isVideo",
|
||||||
|
"columnName": "is_video",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "userRating",
|
||||||
|
"columnName": "user_rating",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "averageRating",
|
||||||
|
"columnName": "average_rating",
|
||||||
|
"affinity": "REAL",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "playCount",
|
||||||
|
"columnName": "play_count",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "discNumber",
|
||||||
|
"columnName": "disc_number",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "created",
|
||||||
|
"columnName": "created",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "starred",
|
||||||
|
"columnName": "starred",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "albumId",
|
||||||
|
"columnName": "album_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "artistId",
|
||||||
|
"columnName": "artist_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "type",
|
||||||
|
"columnName": "type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "bookmarkPosition",
|
||||||
|
"columnName": "bookmark_position",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "originalWidth",
|
||||||
|
"columnName": "original_width",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "originalHeight",
|
||||||
|
"columnName": "original_height",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"views": [],
|
||||||
|
"setupQueries": [
|
||||||
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||||
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '1f4e50f90f58fb9cb53c89747d142fd9')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,790 @@
|
|||||||
|
{
|
||||||
|
"formatVersion": 1,
|
||||||
|
"database": {
|
||||||
|
"version": 2,
|
||||||
|
"identityHash": "ff99e331b4c34a82c560588c4dd5735f",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"tableName": "queue",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `track_order` INTEGER NOT NULL, `last_play` INTEGER NOT NULL, `playing_changed` INTEGER NOT NULL, `stream_id` TEXT, `parent_id` TEXT, `is_dir` INTEGER NOT NULL, `title` TEXT, `album` TEXT, `artist` TEXT, `track` INTEGER, `year` INTEGER, `genre` TEXT, `cover_art_id` TEXT, `size` INTEGER, `content_type` TEXT, `suffix` TEXT, `transcoding_content_type` TEXT, `transcoded_suffix` TEXT, `duration` INTEGER, `bitrate` INTEGER, `path` TEXT, `is_video` INTEGER NOT NULL, `user_rating` INTEGER, `average_rating` REAL, `play_count` INTEGER, `disc_number` INTEGER, `created` INTEGER, `starred` INTEGER, `album_id` TEXT, `artist_id` TEXT, `type` TEXT, `bookmark_position` INTEGER, `original_width` INTEGER, `original_height` INTEGER, PRIMARY KEY(`track_order`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "trackOrder",
|
||||||
|
"columnName": "track_order",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lastPlay",
|
||||||
|
"columnName": "last_play",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "playingChanged",
|
||||||
|
"columnName": "playing_changed",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "streamId",
|
||||||
|
"columnName": "stream_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "parentId",
|
||||||
|
"columnName": "parent_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isDir",
|
||||||
|
"columnName": "is_dir",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "title",
|
||||||
|
"columnName": "title",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "album",
|
||||||
|
"columnName": "album",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "artist",
|
||||||
|
"columnName": "artist",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "track",
|
||||||
|
"columnName": "track",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "year",
|
||||||
|
"columnName": "year",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "genre",
|
||||||
|
"columnName": "genre",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "coverArtId",
|
||||||
|
"columnName": "cover_art_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "size",
|
||||||
|
"columnName": "size",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "contentType",
|
||||||
|
"columnName": "content_type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "suffix",
|
||||||
|
"columnName": "suffix",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "transcodedContentType",
|
||||||
|
"columnName": "transcoding_content_type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "transcodedSuffix",
|
||||||
|
"columnName": "transcoded_suffix",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "duration",
|
||||||
|
"columnName": "duration",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "bitrate",
|
||||||
|
"columnName": "bitrate",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "path",
|
||||||
|
"columnName": "path",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isVideo",
|
||||||
|
"columnName": "is_video",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "userRating",
|
||||||
|
"columnName": "user_rating",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "averageRating",
|
||||||
|
"columnName": "average_rating",
|
||||||
|
"affinity": "REAL",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "playCount",
|
||||||
|
"columnName": "play_count",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "discNumber",
|
||||||
|
"columnName": "disc_number",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "created",
|
||||||
|
"columnName": "created",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "starred",
|
||||||
|
"columnName": "starred",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "albumId",
|
||||||
|
"columnName": "album_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "artistId",
|
||||||
|
"columnName": "artist_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "type",
|
||||||
|
"columnName": "type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "bookmarkPosition",
|
||||||
|
"columnName": "bookmark_position",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "originalWidth",
|
||||||
|
"columnName": "original_width",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "originalHeight",
|
||||||
|
"columnName": "original_height",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"track_order"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "server",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `server_name` TEXT NOT NULL, `username` TEXT NOT NULL, `password` TEXT NOT NULL, `address` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, `low_security` INTEGER NOT NULL DEFAULT false, PRIMARY KEY(`id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "serverId",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "serverName",
|
||||||
|
"columnName": "server_name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "username",
|
||||||
|
"columnName": "username",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "password",
|
||||||
|
"columnName": "password",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "address",
|
||||||
|
"columnName": "address",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "timestamp",
|
||||||
|
"columnName": "timestamp",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isLowSecurity",
|
||||||
|
"columnName": "low_security",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "recent_search",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`search` TEXT NOT NULL, PRIMARY KEY(`search`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "search",
|
||||||
|
"columnName": "search",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"search"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "download",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `playlist_id` TEXT, `playlist_name` TEXT, `download_state` INTEGER NOT NULL DEFAULT 1, `parent_id` TEXT, `is_dir` INTEGER NOT NULL, `title` TEXT, `album` TEXT, `artist` TEXT, `track` INTEGER, `year` INTEGER, `genre` TEXT, `cover_art_id` TEXT, `size` INTEGER, `content_type` TEXT, `suffix` TEXT, `transcoding_content_type` TEXT, `transcoded_suffix` TEXT, `duration` INTEGER, `bitrate` INTEGER, `path` TEXT, `is_video` INTEGER NOT NULL, `user_rating` INTEGER, `average_rating` REAL, `play_count` INTEGER, `disc_number` INTEGER, `created` INTEGER, `starred` INTEGER, `album_id` TEXT, `artist_id` TEXT, `type` TEXT, `bookmark_position` INTEGER, `original_width` INTEGER, `original_height` INTEGER, PRIMARY KEY(`id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "playlistId",
|
||||||
|
"columnName": "playlist_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "playlistName",
|
||||||
|
"columnName": "playlist_name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "downloadState",
|
||||||
|
"columnName": "download_state",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "parentId",
|
||||||
|
"columnName": "parent_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isDir",
|
||||||
|
"columnName": "is_dir",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "title",
|
||||||
|
"columnName": "title",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "album",
|
||||||
|
"columnName": "album",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "artist",
|
||||||
|
"columnName": "artist",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "track",
|
||||||
|
"columnName": "track",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "year",
|
||||||
|
"columnName": "year",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "genre",
|
||||||
|
"columnName": "genre",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "coverArtId",
|
||||||
|
"columnName": "cover_art_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "size",
|
||||||
|
"columnName": "size",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "contentType",
|
||||||
|
"columnName": "content_type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "suffix",
|
||||||
|
"columnName": "suffix",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "transcodedContentType",
|
||||||
|
"columnName": "transcoding_content_type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "transcodedSuffix",
|
||||||
|
"columnName": "transcoded_suffix",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "duration",
|
||||||
|
"columnName": "duration",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "bitrate",
|
||||||
|
"columnName": "bitrate",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "path",
|
||||||
|
"columnName": "path",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isVideo",
|
||||||
|
"columnName": "is_video",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "userRating",
|
||||||
|
"columnName": "user_rating",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "averageRating",
|
||||||
|
"columnName": "average_rating",
|
||||||
|
"affinity": "REAL",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "playCount",
|
||||||
|
"columnName": "play_count",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "discNumber",
|
||||||
|
"columnName": "disc_number",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "created",
|
||||||
|
"columnName": "created",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "starred",
|
||||||
|
"columnName": "starred",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "albumId",
|
||||||
|
"columnName": "album_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "artistId",
|
||||||
|
"columnName": "artist_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "type",
|
||||||
|
"columnName": "type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "bookmarkPosition",
|
||||||
|
"columnName": "bookmark_position",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "originalWidth",
|
||||||
|
"columnName": "original_width",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "originalHeight",
|
||||||
|
"columnName": "original_height",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "chronology",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, `server` TEXT, `parent_id` TEXT, `is_dir` INTEGER NOT NULL, `title` TEXT, `album` TEXT, `artist` TEXT, `track` INTEGER, `year` INTEGER, `genre` TEXT, `cover_art_id` TEXT, `size` INTEGER, `content_type` TEXT, `suffix` TEXT, `transcoding_content_type` TEXT, `transcoded_suffix` TEXT, `duration` INTEGER, `bitrate` INTEGER, `path` TEXT, `is_video` INTEGER NOT NULL, `user_rating` INTEGER, `average_rating` REAL, `play_count` INTEGER, `disc_number` INTEGER, `created` INTEGER, `starred` INTEGER, `album_id` TEXT, `artist_id` TEXT, `type` TEXT, `bookmark_position` INTEGER, `original_width` INTEGER, `original_height` INTEGER, PRIMARY KEY(`id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "timestamp",
|
||||||
|
"columnName": "timestamp",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "server",
|
||||||
|
"columnName": "server",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "parentId",
|
||||||
|
"columnName": "parent_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isDir",
|
||||||
|
"columnName": "is_dir",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "title",
|
||||||
|
"columnName": "title",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "album",
|
||||||
|
"columnName": "album",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "artist",
|
||||||
|
"columnName": "artist",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "track",
|
||||||
|
"columnName": "track",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "year",
|
||||||
|
"columnName": "year",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "genre",
|
||||||
|
"columnName": "genre",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "coverArtId",
|
||||||
|
"columnName": "cover_art_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "size",
|
||||||
|
"columnName": "size",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "contentType",
|
||||||
|
"columnName": "content_type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "suffix",
|
||||||
|
"columnName": "suffix",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "transcodedContentType",
|
||||||
|
"columnName": "transcoding_content_type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "transcodedSuffix",
|
||||||
|
"columnName": "transcoded_suffix",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "duration",
|
||||||
|
"columnName": "duration",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "bitrate",
|
||||||
|
"columnName": "bitrate",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "path",
|
||||||
|
"columnName": "path",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isVideo",
|
||||||
|
"columnName": "is_video",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "userRating",
|
||||||
|
"columnName": "user_rating",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "averageRating",
|
||||||
|
"columnName": "average_rating",
|
||||||
|
"affinity": "REAL",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "playCount",
|
||||||
|
"columnName": "play_count",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "discNumber",
|
||||||
|
"columnName": "disc_number",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "created",
|
||||||
|
"columnName": "created",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "starred",
|
||||||
|
"columnName": "starred",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "albumId",
|
||||||
|
"columnName": "album_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "artistId",
|
||||||
|
"columnName": "artist_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "type",
|
||||||
|
"columnName": "type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "bookmarkPosition",
|
||||||
|
"columnName": "bookmark_position",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "originalWidth",
|
||||||
|
"columnName": "original_width",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "originalHeight",
|
||||||
|
"columnName": "original_height",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "favorite",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`timestamp` INTEGER NOT NULL, `songId` TEXT, `albumId` TEXT, `artistId` TEXT, `toStar` INTEGER NOT NULL, PRIMARY KEY(`timestamp`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "timestamp",
|
||||||
|
"columnName": "timestamp",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "songId",
|
||||||
|
"columnName": "songId",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "albumId",
|
||||||
|
"columnName": "albumId",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "artistId",
|
||||||
|
"columnName": "artistId",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "toStar",
|
||||||
|
"columnName": "toStar",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"timestamp"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"views": [],
|
||||||
|
"setupQueries": [
|
||||||
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||||
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'ff99e331b4c34a82c560588c4dd5735f')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.cappielloantonio.tempo.database;
|
package com.cappielloantonio.tempo.database;
|
||||||
|
|
||||||
|
import androidx.room.AutoMigration;
|
||||||
import androidx.room.Database;
|
import androidx.room.Database;
|
||||||
import androidx.room.Room;
|
import androidx.room.Room;
|
||||||
import androidx.room.RoomDatabase;
|
import androidx.room.RoomDatabase;
|
||||||
@@ -9,19 +10,21 @@ import com.cappielloantonio.tempo.App;
|
|||||||
import com.cappielloantonio.tempo.database.converter.DateConverters;
|
import com.cappielloantonio.tempo.database.converter.DateConverters;
|
||||||
import com.cappielloantonio.tempo.database.dao.ChronologyDao;
|
import com.cappielloantonio.tempo.database.dao.ChronologyDao;
|
||||||
import com.cappielloantonio.tempo.database.dao.DownloadDao;
|
import com.cappielloantonio.tempo.database.dao.DownloadDao;
|
||||||
|
import com.cappielloantonio.tempo.database.dao.FavoriteDao;
|
||||||
import com.cappielloantonio.tempo.database.dao.QueueDao;
|
import com.cappielloantonio.tempo.database.dao.QueueDao;
|
||||||
import com.cappielloantonio.tempo.database.dao.RecentSearchDao;
|
import com.cappielloantonio.tempo.database.dao.RecentSearchDao;
|
||||||
import com.cappielloantonio.tempo.database.dao.ServerDao;
|
import com.cappielloantonio.tempo.database.dao.ServerDao;
|
||||||
import com.cappielloantonio.tempo.model.Chronology;
|
import com.cappielloantonio.tempo.model.Chronology;
|
||||||
import com.cappielloantonio.tempo.model.Download;
|
import com.cappielloantonio.tempo.model.Download;
|
||||||
|
import com.cappielloantonio.tempo.model.Favorite;
|
||||||
import com.cappielloantonio.tempo.model.Queue;
|
import com.cappielloantonio.tempo.model.Queue;
|
||||||
import com.cappielloantonio.tempo.model.RecentSearch;
|
import com.cappielloantonio.tempo.model.RecentSearch;
|
||||||
import com.cappielloantonio.tempo.model.Server;
|
import com.cappielloantonio.tempo.model.Server;
|
||||||
|
|
||||||
@Database(
|
@Database(
|
||||||
version = 1,
|
version = 2,
|
||||||
entities = {Queue.class, Server.class, RecentSearch.class, Download.class, Chronology.class}
|
entities = {Queue.class, Server.class, RecentSearch.class, Download.class, Chronology.class, Favorite.class},
|
||||||
// autoMigrations = {@AutoMigration(from = 61, to = 62)}
|
autoMigrations = {@AutoMigration(from = 1, to = 2)}
|
||||||
)
|
)
|
||||||
@TypeConverters({DateConverters.class})
|
@TypeConverters({DateConverters.class})
|
||||||
public abstract class AppDatabase extends RoomDatabase {
|
public abstract class AppDatabase extends RoomDatabase {
|
||||||
@@ -47,4 +50,6 @@ public abstract class AppDatabase extends RoomDatabase {
|
|||||||
public abstract DownloadDao downloadDao();
|
public abstract DownloadDao downloadDao();
|
||||||
|
|
||||||
public abstract ChronologyDao chronologyDao();
|
public abstract ChronologyDao chronologyDao();
|
||||||
|
|
||||||
|
public abstract FavoriteDao favoriteDao();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package com.cappielloantonio.tempo.database.dao;
|
||||||
|
|
||||||
|
import androidx.room.Dao;
|
||||||
|
import androidx.room.Delete;
|
||||||
|
import androidx.room.Insert;
|
||||||
|
import androidx.room.OnConflictStrategy;
|
||||||
|
import androidx.room.Query;
|
||||||
|
|
||||||
|
import com.cappielloantonio.tempo.model.Favorite;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
public interface FavoriteDao {
|
||||||
|
@Query("SELECT * FROM favorite")
|
||||||
|
List<Favorite> getAll();
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||||
|
void insert(Favorite favorite);
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
void delete(Favorite favorite);
|
||||||
|
|
||||||
|
@Query("DELETE FROM favorite")
|
||||||
|
void deleteAll();
|
||||||
|
}
|
||||||
@@ -0,0 +1,197 @@
|
|||||||
|
package com.cappielloantonio.tempo.helper.recyclerview;
|
||||||
|
|
||||||
|
import android.animation.Animator;
|
||||||
|
import android.animation.AnimatorListenerAdapter;
|
||||||
|
import android.animation.ObjectAnimator;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.IdRes;
|
||||||
|
import androidx.annotation.LayoutRes;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.core.view.ViewCompat;
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
public class FastScrollbar extends LinearLayout {
|
||||||
|
private static final int BUBBLE_ANIMATION_DURATION = 100;
|
||||||
|
private static final int TRACK_SNAP_RANGE = 5;
|
||||||
|
|
||||||
|
private TextView bubble;
|
||||||
|
private View handle;
|
||||||
|
private RecyclerView recyclerView;
|
||||||
|
private int height;
|
||||||
|
private boolean isInitialized = false;
|
||||||
|
private ObjectAnimator currentAnimator = null;
|
||||||
|
|
||||||
|
private final RecyclerView.OnScrollListener onScrollListener = new RecyclerView.OnScrollListener() {
|
||||||
|
@Override
|
||||||
|
public void onScrolled(@NonNull final RecyclerView recyclerView, final int dx, final int dy) {
|
||||||
|
updateBubbleAndHandlePosition();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public interface BubbleTextGetter {
|
||||||
|
String getTextToShowInBubble(int pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FastScrollbar(final Context context, final AttributeSet attrs, final int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
init(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FastScrollbar(final Context context) {
|
||||||
|
super(context);
|
||||||
|
init(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FastScrollbar(final Context context, final AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
init(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void init(Context context) {
|
||||||
|
if (isInitialized) return;
|
||||||
|
isInitialized = true;
|
||||||
|
setOrientation(HORIZONTAL);
|
||||||
|
setClipChildren(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setViewsToUse(@LayoutRes int layoutResId, @IdRes int bubbleResId, @IdRes int handleResId) {
|
||||||
|
final LayoutInflater inflater = LayoutInflater.from(getContext());
|
||||||
|
inflater.inflate(layoutResId, this, true);
|
||||||
|
bubble = findViewById(bubbleResId);
|
||||||
|
if (bubble != null) bubble.setVisibility(INVISIBLE);
|
||||||
|
handle = findViewById(handleResId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||||
|
super.onSizeChanged(w, h, oldw, oldh);
|
||||||
|
height = h;
|
||||||
|
updateBubbleAndHandlePosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onTouchEvent(@NonNull MotionEvent event) {
|
||||||
|
final int action = event.getAction();
|
||||||
|
switch (action) {
|
||||||
|
case MotionEvent.ACTION_DOWN:
|
||||||
|
if (event.getX() < handle.getX() - ViewCompat.getPaddingStart(handle)) return false;
|
||||||
|
if (currentAnimator != null) currentAnimator.cancel();
|
||||||
|
if (bubble != null && bubble.getVisibility() == INVISIBLE) showBubble();
|
||||||
|
handle.setSelected(true);
|
||||||
|
case MotionEvent.ACTION_MOVE:
|
||||||
|
final float y = event.getY();
|
||||||
|
setBubbleAndHandlePosition(y);
|
||||||
|
setRecyclerViewPosition(y);
|
||||||
|
return true;
|
||||||
|
case MotionEvent.ACTION_UP:
|
||||||
|
case MotionEvent.ACTION_CANCEL:
|
||||||
|
handle.setSelected(false);
|
||||||
|
hideBubble();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return super.onTouchEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRecyclerView(final RecyclerView recyclerView) {
|
||||||
|
if (this.recyclerView != recyclerView) {
|
||||||
|
if (this.recyclerView != null)
|
||||||
|
this.recyclerView.removeOnScrollListener(onScrollListener);
|
||||||
|
this.recyclerView = recyclerView;
|
||||||
|
if (this.recyclerView == null) return;
|
||||||
|
recyclerView.addOnScrollListener(onScrollListener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDetachedFromWindow() {
|
||||||
|
super.onDetachedFromWindow();
|
||||||
|
if (recyclerView != null) {
|
||||||
|
recyclerView.removeOnScrollListener(onScrollListener);
|
||||||
|
recyclerView = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setRecyclerViewPosition(float y) {
|
||||||
|
if (recyclerView != null) {
|
||||||
|
final int itemCount = recyclerView.getAdapter().getItemCount();
|
||||||
|
float proportion;
|
||||||
|
if (handle.getY() == 0) proportion = 0f;
|
||||||
|
else if (handle.getY() + handle.getHeight() >= height - TRACK_SNAP_RANGE)
|
||||||
|
proportion = 1f;
|
||||||
|
else proportion = y / (float) height;
|
||||||
|
final int targetPos = getValueInRange(0, itemCount - 1, (int) (proportion * (float) itemCount));
|
||||||
|
((LinearLayoutManager) recyclerView.getLayoutManager()).scrollToPositionWithOffset(targetPos, 0);
|
||||||
|
final String bubbleText = ((BubbleTextGetter) recyclerView.getAdapter()).getTextToShowInBubble(targetPos);
|
||||||
|
if (bubble != null) {
|
||||||
|
bubble.setText(bubbleText);
|
||||||
|
if (TextUtils.isEmpty(bubbleText)) {
|
||||||
|
hideBubble();
|
||||||
|
} else if (bubble.getVisibility() == View.INVISIBLE) {
|
||||||
|
showBubble();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getValueInRange(int min, int max, int value) {
|
||||||
|
int minimum = Math.max(min, value);
|
||||||
|
return Math.min(minimum, max);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateBubbleAndHandlePosition() {
|
||||||
|
if (bubble == null || handle.isSelected()) return;
|
||||||
|
|
||||||
|
final int verticalScrollOffset = recyclerView.computeVerticalScrollOffset();
|
||||||
|
final int verticalScrollRange = recyclerView.computeVerticalScrollRange();
|
||||||
|
float proportion = (float) verticalScrollOffset / ((float) verticalScrollRange - height);
|
||||||
|
setBubbleAndHandlePosition(height * proportion);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setBubbleAndHandlePosition(float y) {
|
||||||
|
final int handleHeight = handle.getHeight();
|
||||||
|
handle.setY(getValueInRange(0, height - handleHeight, (int) (y - handleHeight / 2)));
|
||||||
|
if (bubble != null) {
|
||||||
|
int bubbleHeight = bubble.getHeight();
|
||||||
|
bubble.setY(getValueInRange(0, height - bubbleHeight - handleHeight / 2, (int) (y - bubbleHeight)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showBubble() {
|
||||||
|
if (bubble == null) return;
|
||||||
|
bubble.setVisibility(VISIBLE);
|
||||||
|
if (currentAnimator != null) currentAnimator.cancel();
|
||||||
|
currentAnimator = ObjectAnimator.ofFloat(bubble, "alpha", 0f, 1f).setDuration(BUBBLE_ANIMATION_DURATION);
|
||||||
|
currentAnimator.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void hideBubble() {
|
||||||
|
if (bubble == null) return;
|
||||||
|
if (currentAnimator != null) currentAnimator.cancel();
|
||||||
|
currentAnimator = ObjectAnimator.ofFloat(bubble, "alpha", 1f, 0f).setDuration(BUBBLE_ANIMATION_DURATION);
|
||||||
|
currentAnimator.addListener(new AnimatorListenerAdapter() {
|
||||||
|
@Override
|
||||||
|
public void onAnimationEnd(Animator animation) {
|
||||||
|
super.onAnimationEnd(animation);
|
||||||
|
bubble.setVisibility(INVISIBLE);
|
||||||
|
currentAnimator = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnimationCancel(Animator animation) {
|
||||||
|
super.onAnimationCancel(animation);
|
||||||
|
bubble.setVisibility(INVISIBLE);
|
||||||
|
currentAnimator = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
currentAnimator.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,6 +20,7 @@ public interface ClickCallback {
|
|||||||
default void onServerClick(Bundle bundle) {}
|
default void onServerClick(Bundle bundle) {}
|
||||||
default void onServerLongClick(Bundle bundle) {}
|
default void onServerLongClick(Bundle bundle) {}
|
||||||
default void onPodcastEpisodeClick(Bundle bundle) {}
|
default void onPodcastEpisodeClick(Bundle bundle) {}
|
||||||
|
default void onPodcastEpisodeAltClick(Bundle bundle) {}
|
||||||
default void onPodcastEpisodeLongClick(Bundle bundle) {}
|
default void onPodcastEpisodeLongClick(Bundle bundle) {}
|
||||||
default void onPodcastChannelClick(Bundle bundle) {}
|
default void onPodcastChannelClick(Bundle bundle) {}
|
||||||
default void onPodcastChannelLongClick(Bundle bundle) {}
|
default void onPodcastChannelLongClick(Bundle bundle) {}
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.cappielloantonio.tempo.interfaces;
|
||||||
|
|
||||||
|
import androidx.annotation.Keep;
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
public interface StarCallback {
|
||||||
|
default void onError() {}
|
||||||
|
default void onSuccess() {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package com.cappielloantonio.tempo.model
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import androidx.annotation.Keep
|
||||||
|
import androidx.annotation.Nullable
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
@Parcelize
|
||||||
|
@Entity(tableName = "favorite")
|
||||||
|
data class Favorite(
|
||||||
|
@PrimaryKey
|
||||||
|
@ColumnInfo(name = "timestamp")
|
||||||
|
var timestamp: Long,
|
||||||
|
|
||||||
|
@ColumnInfo(name = "songId")
|
||||||
|
val songId: String?,
|
||||||
|
|
||||||
|
@ColumnInfo(name = "albumId")
|
||||||
|
val albumId: String?,
|
||||||
|
|
||||||
|
@ColumnInfo(name = "artistId")
|
||||||
|
val artistId: String?,
|
||||||
|
|
||||||
|
@ColumnInfo(name = "toStar")
|
||||||
|
val toStar: Boolean,
|
||||||
|
) : Parcelable {
|
||||||
|
override fun toString(): String = (songId ?: "null") + (albumId ?: "null") + (artistId ?: "null")
|
||||||
|
}
|
||||||
@@ -21,8 +21,6 @@ import retrofit2.Callback;
|
|||||||
import retrofit2.Response;
|
import retrofit2.Response;
|
||||||
|
|
||||||
public class AlbumRepository {
|
public class AlbumRepository {
|
||||||
private static final String TAG = "AlbumRepository";
|
|
||||||
|
|
||||||
public MutableLiveData<List<AlbumID3>> getAlbums(String type, int size, Integer fromYear, Integer toYear) {
|
public MutableLiveData<List<AlbumID3>> getAlbums(String type, int size, Integer fromYear, Integer toYear) {
|
||||||
MutableLiveData<List<AlbumID3>> listLiveAlbums = new MutableLiveData<>(new ArrayList<>());
|
MutableLiveData<List<AlbumID3>> listLiveAlbums = new MutableLiveData<>(new ArrayList<>());
|
||||||
|
|
||||||
@@ -78,40 +76,6 @@ public class AlbumRepository {
|
|||||||
return starredAlbums;
|
return starredAlbums;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void star(String id) {
|
|
||||||
App.getSubsonicClientInstance(false)
|
|
||||||
.getMediaAnnotationClient()
|
|
||||||
.star(null, id, null)
|
|
||||||
.enqueue(new Callback<ApiResponse>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void unstar(String id) {
|
|
||||||
App.getSubsonicClientInstance(false)
|
|
||||||
.getMediaAnnotationClient()
|
|
||||||
.unstar(null, id, null)
|
|
||||||
.enqueue(new Callback<ApiResponse>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRating(String id, int rating) {
|
public void setRating(String id, int rating) {
|
||||||
App.getSubsonicClientInstance(false)
|
App.getSubsonicClientInstance(false)
|
||||||
.getMediaAnnotationClient()
|
.getMediaAnnotationClient()
|
||||||
|
|||||||
@@ -135,9 +135,6 @@ public class ArtistRepository {
|
|||||||
return artist;
|
return artist;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Metodo che mi restituisce le informazioni complete dell'artista (bio, immagini prese da last.fm, artisti simili...)
|
|
||||||
*/
|
|
||||||
public MutableLiveData<ArtistInfo2> getArtistFullInfo(String id) {
|
public MutableLiveData<ArtistInfo2> getArtistFullInfo(String id) {
|
||||||
MutableLiveData<ArtistInfo2> artistFullInfo = new MutableLiveData<>(null);
|
MutableLiveData<ArtistInfo2> artistFullInfo = new MutableLiveData<>(null);
|
||||||
|
|
||||||
@@ -161,40 +158,6 @@ public class ArtistRepository {
|
|||||||
return artistFullInfo;
|
return artistFullInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void star(String id) {
|
|
||||||
App.getSubsonicClientInstance(false)
|
|
||||||
.getMediaAnnotationClient()
|
|
||||||
.star(null, null, id)
|
|
||||||
.enqueue(new Callback<ApiResponse>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void unstar(String id) {
|
|
||||||
App.getSubsonicClientInstance(false)
|
|
||||||
.getMediaAnnotationClient()
|
|
||||||
.unstar(null, null, id)
|
|
||||||
.enqueue(new Callback<ApiResponse>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRating(String id, int rating) {
|
public void setRating(String id, int rating) {
|
||||||
App.getSubsonicClientInstance(false)
|
App.getSubsonicClientInstance(false)
|
||||||
.getMediaAnnotationClient()
|
.getMediaAnnotationClient()
|
||||||
|
|||||||
@@ -0,0 +1,140 @@
|
|||||||
|
package com.cappielloantonio.tempo.repository;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import com.cappielloantonio.tempo.App;
|
||||||
|
import com.cappielloantonio.tempo.database.AppDatabase;
|
||||||
|
import com.cappielloantonio.tempo.database.dao.FavoriteDao;
|
||||||
|
import com.cappielloantonio.tempo.interfaces.StarCallback;
|
||||||
|
import com.cappielloantonio.tempo.model.Favorite;
|
||||||
|
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import retrofit2.Call;
|
||||||
|
import retrofit2.Callback;
|
||||||
|
import retrofit2.Response;
|
||||||
|
|
||||||
|
public class FavoriteRepository {
|
||||||
|
private final FavoriteDao favoriteDao = AppDatabase.getInstance().favoriteDao();
|
||||||
|
|
||||||
|
public void star(String id, String albumId, String artistId, StarCallback starCallback) {
|
||||||
|
App.getSubsonicClientInstance(false)
|
||||||
|
.getMediaAnnotationClient()
|
||||||
|
.star(id, albumId, artistId)
|
||||||
|
.enqueue(new Callback<ApiResponse>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||||
|
if (response.isSuccessful()) {
|
||||||
|
starCallback.onSuccess();
|
||||||
|
} else {
|
||||||
|
starCallback.onError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||||
|
starCallback.onError();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unstar(String id, String albumId, String artistId, StarCallback starCallback) {
|
||||||
|
App.getSubsonicClientInstance(false)
|
||||||
|
.getMediaAnnotationClient()
|
||||||
|
.unstar(id, albumId, artistId)
|
||||||
|
.enqueue(new Callback<ApiResponse>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||||
|
if (response.isSuccessful()) {
|
||||||
|
starCallback.onSuccess();
|
||||||
|
} else {
|
||||||
|
starCallback.onError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||||
|
starCallback.onError();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Favorite> getFavorites() {
|
||||||
|
List<Favorite> favorites = new ArrayList<>();
|
||||||
|
|
||||||
|
GetAllThreadSafe getAllThreadSafe = new GetAllThreadSafe(favoriteDao);
|
||||||
|
Thread thread = new Thread(getAllThreadSafe);
|
||||||
|
thread.start();
|
||||||
|
|
||||||
|
try {
|
||||||
|
thread.join();
|
||||||
|
favorites = getAllThreadSafe.getFavorites();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return favorites;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class GetAllThreadSafe implements Runnable {
|
||||||
|
private final FavoriteDao favoriteDao;
|
||||||
|
private List<Favorite> favorites = new ArrayList<>();
|
||||||
|
|
||||||
|
public GetAllThreadSafe(FavoriteDao favoriteDao) {
|
||||||
|
this.favoriteDao = favoriteDao;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
favorites = favoriteDao.getAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Favorite> getFavorites() {
|
||||||
|
return favorites;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void starLater(String id, String albumId, String artistId, boolean toStar) {
|
||||||
|
InsertThreadSafe insert = new InsertThreadSafe(favoriteDao, new Favorite(System.currentTimeMillis(), id, albumId, artistId, toStar));
|
||||||
|
Thread thread = new Thread(insert);
|
||||||
|
thread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class InsertThreadSafe implements Runnable {
|
||||||
|
private final FavoriteDao favoriteDao;
|
||||||
|
private final Favorite favorite;
|
||||||
|
|
||||||
|
public InsertThreadSafe(FavoriteDao favoriteDao, Favorite favorite) {
|
||||||
|
this.favoriteDao = favoriteDao;
|
||||||
|
this.favorite = favorite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
favoriteDao.insert(favorite);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void delete(Favorite favorite) {
|
||||||
|
DeleteThreadSafe delete = new DeleteThreadSafe(favoriteDao, favorite);
|
||||||
|
Thread thread = new Thread(delete);
|
||||||
|
thread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class DeleteThreadSafe implements Runnable {
|
||||||
|
private final FavoriteDao favoriteDao;
|
||||||
|
private final Favorite favorite;
|
||||||
|
|
||||||
|
public DeleteThreadSafe(FavoriteDao favoriteDao, Favorite favorite) {
|
||||||
|
this.favoriteDao = favoriteDao;
|
||||||
|
this.favorite = favorite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
favoriteDao.delete(favorite);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -133,4 +133,21 @@ public class PodcastRepository {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void downloadPodcastEpisode(String episodeId) {
|
||||||
|
App.getSubsonicClientInstance(false)
|
||||||
|
.getPodcastClient()
|
||||||
|
.downloadPodcastEpisode(episodeId)
|
||||||
|
.enqueue(new Callback<ApiResponse>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.lifecycle.MutableLiveData;
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
|
||||||
import com.cappielloantonio.tempo.App;
|
import com.cappielloantonio.tempo.App;
|
||||||
|
import com.cappielloantonio.tempo.database.AppDatabase;
|
||||||
|
import com.cappielloantonio.tempo.database.dao.FavoriteDao;
|
||||||
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||||
|
|
||||||
@@ -119,40 +121,6 @@ public class SongRepository {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void star(String id) {
|
|
||||||
App.getSubsonicClientInstance(false)
|
|
||||||
.getMediaAnnotationClient()
|
|
||||||
.star(id, null, null)
|
|
||||||
.enqueue(new Callback<ApiResponse>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void unstar(String id) {
|
|
||||||
App.getSubsonicClientInstance(false)
|
|
||||||
.getMediaAnnotationClient()
|
|
||||||
.unstar(id, null, null)
|
|
||||||
.enqueue(new Callback<ApiResponse>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRating(String id, int rating) {
|
public void setRating(String id, int rating) {
|
||||||
App.getSubsonicClientInstance(false)
|
App.getSubsonicClientInstance(false)
|
||||||
.getMediaAnnotationClient()
|
.getMediaAnnotationClient()
|
||||||
@@ -173,8 +141,6 @@ public class SongRepository {
|
|||||||
public MutableLiveData<List<Child>> getSongsByGenre(String id, int page) {
|
public MutableLiveData<List<Child>> getSongsByGenre(String id, int page) {
|
||||||
MutableLiveData<List<Child>> songsByGenre = new MutableLiveData<>();
|
MutableLiveData<List<Child>> songsByGenre = new MutableLiveData<>();
|
||||||
|
|
||||||
Log.d(TAG, "onScrolled PAGE: " + page);
|
|
||||||
|
|
||||||
App.getSubsonicClientInstance(false)
|
App.getSubsonicClientInstance(false)
|
||||||
.getAlbumSongListClient()
|
.getAlbumSongListClient()
|
||||||
.getSongsByGenre(id, 100, 100 * page)
|
.getSongsByGenre(id, 100, 100 * page)
|
||||||
|
|||||||
@@ -48,4 +48,9 @@ public class PodcastClient {
|
|||||||
Log.d(TAG, "deletePodcastEpisode()");
|
Log.d(TAG, "deletePodcastEpisode()");
|
||||||
return podcastService.deletePodcastEpisode(subsonic.getParams(), episodeId);
|
return podcastService.deletePodcastEpisode(subsonic.getParams(), episodeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Call<ApiResponse> downloadPodcastEpisode(String episodeId) {
|
||||||
|
Log.d(TAG, "downloadPodcastEpisode()");
|
||||||
|
return podcastService.downloadPodcastEpisode(subsonic.getParams(), episodeId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,4 +27,7 @@ public interface PodcastService {
|
|||||||
|
|
||||||
@GET("deletePodcastEpisode")
|
@GET("deletePodcastEpisode")
|
||||||
Call<ApiResponse> deletePodcastEpisode(@QueryMap Map<String, String> params, @Query("id") String id);
|
Call<ApiResponse> deletePodcastEpisode(@QueryMap Map<String, String> params, @Query("id") String id);
|
||||||
|
|
||||||
|
@GET("downloadPodcastEpisode")
|
||||||
|
Call<ApiResponse> downloadPodcastEpisode(@QueryMap Map<String, String> params, @Query("id") String id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package com.cappielloantonio.tempo.subsonic.models
|
|||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import androidx.annotation.Keep
|
import androidx.annotation.Keep
|
||||||
import androidx.room.ColumnInfo
|
|
||||||
import com.google.gson.annotations.SerializedName
|
import com.google.gson.annotations.SerializedName
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@@ -29,11 +28,9 @@ class PodcastEpisode : Parcelable {
|
|||||||
var transcodedContentType: String? = null
|
var transcodedContentType: String? = null
|
||||||
var transcodedSuffix: String? = null
|
var transcodedSuffix: String? = null
|
||||||
var duration: Int? = null
|
var duration: Int? = null
|
||||||
@ColumnInfo("bitrate")
|
|
||||||
@SerializedName("bitRate")
|
@SerializedName("bitRate")
|
||||||
var bitrate: Int? = null
|
var bitrate: Int? = null
|
||||||
var path: String? = null
|
var path: String? = null
|
||||||
@ColumnInfo(name = "is_video")
|
|
||||||
@SerializedName("isVideo")
|
@SerializedName("isVideo")
|
||||||
var isVideo: Boolean = false
|
var isVideo: Boolean = false
|
||||||
var userRating: Int? = null
|
var userRating: Int? = null
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.cappielloantonio.tempo.ui.activity;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
import android.net.ConnectivityManager;
|
import android.net.ConnectivityManager;
|
||||||
|
import android.net.NetworkInfo;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -338,7 +339,9 @@ public class MainActivity extends BaseActivity {
|
|||||||
private void checkConnectionType() {
|
private void checkConnectionType() {
|
||||||
if (Preferences.isWifiOnly()) {
|
if (Preferences.isWifiOnly()) {
|
||||||
ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
|
ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||||
if (connectivityManager.getActiveNetworkInfo().getType() != ConnectivityManager.TYPE_WIFI) {
|
NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
|
||||||
|
|
||||||
|
if (networkInfo != null && networkInfo.getType() != ConnectivityManager.TYPE_WIFI) {
|
||||||
ConnectionAlertDialog dialog = new ConnectionAlertDialog();
|
ConnectionAlertDialog dialog = new ConnectionAlertDialog();
|
||||||
dialog.show(getSupportFragmentManager(), null);
|
dialog.show(getSupportFragmentManager(), null);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ public class BaseActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void checkBatteryOptimization() {
|
private void checkBatteryOptimization() {
|
||||||
if (detectBatteryOptimization() && Boolean.TRUE.equals(Preferences.askForOptimization())) {
|
if (detectBatteryOptimization() && Preferences.askForOptimization()) {
|
||||||
showBatteryOptimizationDialog();
|
showBatteryOptimizationDialog();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,16 +9,17 @@ import androidx.media3.common.util.UnstableApi;
|
|||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import com.cappielloantonio.tempo.databinding.ItemLibraryMusicIndexBinding;
|
import com.cappielloantonio.tempo.databinding.ItemLibraryMusicIndexBinding;
|
||||||
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
import com.cappielloantonio.tempo.helper.recyclerview.FastScrollbar;
|
||||||
import com.cappielloantonio.tempo.interfaces.ClickCallback;
|
import com.cappielloantonio.tempo.interfaces.ClickCallback;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.Artist;
|
import com.cappielloantonio.tempo.subsonic.models.Artist;
|
||||||
import com.cappielloantonio.tempo.util.Constants;
|
import com.cappielloantonio.tempo.util.Constants;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
public class MusicIndexAdapter extends RecyclerView.Adapter<MusicIndexAdapter.ViewHolder> {
|
public class MusicIndexAdapter extends RecyclerView.Adapter<MusicIndexAdapter.ViewHolder> implements FastScrollbar.BubbleTextGetter {
|
||||||
private final ClickCallback click;
|
private final ClickCallback click;
|
||||||
|
|
||||||
private List<Artist> artists;
|
private List<Artist> artists;
|
||||||
@@ -41,10 +42,10 @@ public class MusicIndexAdapter extends RecyclerView.Adapter<MusicIndexAdapter.Vi
|
|||||||
|
|
||||||
holder.item.musicIndexTitleTextView.setText(artist.getName());
|
holder.item.musicIndexTitleTextView.setText(artist.getName());
|
||||||
|
|
||||||
CustomGlideRequest.Builder
|
/* CustomGlideRequest.Builder
|
||||||
.from(holder.itemView.getContext(), artist.getName())
|
.from(holder.itemView.getContext(), artist.getName())
|
||||||
.build()
|
.build()
|
||||||
.into(holder.item.musicIndexCoverImageView);
|
.into(holder.item.musicIndexCoverImageView); */
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -57,6 +58,11 @@ public class MusicIndexAdapter extends RecyclerView.Adapter<MusicIndexAdapter.Vi
|
|||||||
notifyDataSetChanged();
|
notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getTextToShowInBubble(int pos) {
|
||||||
|
return Character.toString(Objects.requireNonNull(artists.get(pos).getName().toUpperCase()).charAt(0));
|
||||||
|
}
|
||||||
|
|
||||||
public class ViewHolder extends RecyclerView.ViewHolder {
|
public class ViewHolder extends RecyclerView.ViewHolder {
|
||||||
ItemLibraryMusicIndexBinding item;
|
ItemLibraryMusicIndexBinding item;
|
||||||
|
|
||||||
|
|||||||
@@ -128,14 +128,14 @@ public class PodcastChannelCatalogueAdapter extends RecyclerView.Adapter<Podcast
|
|||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putParcelable(Constants.PODCAST_CHANNEL_OBJECT, podcastChannels.get(getBindingAdapterPosition()));
|
bundle.putParcelable(Constants.PODCAST_CHANNEL_OBJECT, podcastChannels.get(getBindingAdapterPosition()));
|
||||||
|
|
||||||
click.onAlbumClick(bundle);
|
click.onPodcastChannelClick(bundle);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean onLongClick() {
|
private boolean onLongClick() {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putParcelable(Constants.PODCAST_CHANNEL_OBJECT, podcastChannels.get(getBindingAdapterPosition()));
|
bundle.putParcelable(Constants.PODCAST_CHANNEL_OBJECT, podcastChannels.get(getBindingAdapterPosition()));
|
||||||
|
|
||||||
click.onAlbumLongClick(bundle);
|
click.onPodcastChannelLongClick(bundle);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.cappielloantonio.tempo.ui.adapter;
|
|||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
@@ -18,11 +19,14 @@ import com.cappielloantonio.tempo.util.MusicUtil;
|
|||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class PodcastEpisodeAdapter extends RecyclerView.Adapter<PodcastEpisodeAdapter.ViewHolder> {
|
public class PodcastEpisodeAdapter extends RecyclerView.Adapter<PodcastEpisodeAdapter.ViewHolder> {
|
||||||
private final ClickCallback click;
|
private final ClickCallback click;
|
||||||
|
|
||||||
private List<PodcastEpisode> podcastEpisodes;
|
private List<PodcastEpisode> podcastEpisodes;
|
||||||
|
private List<PodcastEpisode> podcastEpisodesFull;
|
||||||
|
|
||||||
public PodcastEpisodeAdapter(ClickCallback click) {
|
public PodcastEpisodeAdapter(ClickCallback click) {
|
||||||
this.click = click;
|
this.click = click;
|
||||||
@@ -50,6 +54,10 @@ public class PodcastEpisodeAdapter extends RecyclerView.Adapter<PodcastEpisodeAd
|
|||||||
.from(holder.itemView.getContext(), podcastEpisode.getCoverArtId())
|
.from(holder.itemView.getContext(), podcastEpisode.getCoverArtId())
|
||||||
.build()
|
.build()
|
||||||
.into(holder.item.podcastCoverImageView);
|
.into(holder.item.podcastCoverImageView);
|
||||||
|
|
||||||
|
holder.item.podcastPlayButton.setEnabled(podcastEpisode.getStatus().equals("completed"));
|
||||||
|
holder.item.podcastMoreButton.setVisibility(podcastEpisode.getStatus().equals("completed") ? View.VISIBLE : View.GONE);
|
||||||
|
holder.item.podcastDownloadRequestButton.setVisibility(podcastEpisode.getStatus().equals("completed") ? View.GONE : View.VISIBLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -58,7 +66,8 @@ public class PodcastEpisodeAdapter extends RecyclerView.Adapter<PodcastEpisodeAd
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setItems(List<PodcastEpisode> podcastEpisodes) {
|
public void setItems(List<PodcastEpisode> podcastEpisodes) {
|
||||||
this.podcastEpisodes = podcastEpisodes;
|
this.podcastEpisodesFull = podcastEpisodes;
|
||||||
|
this.podcastEpisodes = podcastEpisodesFull.stream().filter(podcastEpisode -> Objects.equals(podcastEpisode.getStatus(), "completed")).collect(Collectors.toList());
|
||||||
notifyDataSetChanged();
|
notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,22 +94,57 @@ public class PodcastEpisodeAdapter extends RecyclerView.Adapter<PodcastEpisodeAd
|
|||||||
|
|
||||||
item.podcastPlayButton.setOnClickListener(v -> onClick());
|
item.podcastPlayButton.setOnClickListener(v -> onClick());
|
||||||
item.podcastMoreButton.setOnClickListener(v -> openMore());
|
item.podcastMoreButton.setOnClickListener(v -> openMore());
|
||||||
|
item.podcastDownloadRequestButton.setOnClickListener(v -> requestDownload());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onClick() {
|
public void onClick() {
|
||||||
Bundle bundle = new Bundle();
|
PodcastEpisode podcastEpisode = podcastEpisodes.get(getBindingAdapterPosition());
|
||||||
bundle.putParcelable(Constants.PODCAST_OBJECT, podcastEpisodes.get(getBindingAdapterPosition()));
|
|
||||||
|
|
||||||
click.onPodcastEpisodeClick(bundle);
|
if (podcastEpisode.getStatus().equals("completed")) {
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
bundle.putParcelable(Constants.PODCAST_OBJECT, podcastEpisodes.get(getBindingAdapterPosition()));
|
||||||
|
|
||||||
|
click.onPodcastEpisodeClick(bundle);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean openMore() {
|
private boolean openMore() {
|
||||||
Bundle bundle = new Bundle();
|
PodcastEpisode podcastEpisode = podcastEpisodes.get(getBindingAdapterPosition());
|
||||||
bundle.putParcelable(Constants.PODCAST_OBJECT, podcastEpisodes.get(getBindingAdapterPosition()));
|
|
||||||
|
|
||||||
click.onPodcastEpisodeLongClick(bundle);
|
if (podcastEpisode.getStatus().equals("completed")) {
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
bundle.putParcelable(Constants.PODCAST_OBJECT, podcastEpisodes.get(getBindingAdapterPosition()));
|
||||||
|
|
||||||
return true;
|
click.onPodcastEpisodeLongClick(bundle);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void requestDownload() {
|
||||||
|
PodcastEpisode podcastEpisode = podcastEpisodes.get(getBindingAdapterPosition());
|
||||||
|
|
||||||
|
if (!podcastEpisode.getStatus().equals("completed")) {
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
bundle.putParcelable(Constants.PODCAST_OBJECT, podcastEpisodes.get(getBindingAdapterPosition()));
|
||||||
|
|
||||||
|
click.onPodcastEpisodeAltClick(bundle);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void sort(String order) {
|
||||||
|
switch (order) {
|
||||||
|
case Constants.PODCAST_FILTER_BY_DOWNLOAD:
|
||||||
|
podcastEpisodes = podcastEpisodesFull.stream().filter(podcastEpisode -> Objects.equals(podcastEpisode.getStatus(), "completed")).collect(Collectors.toList());
|
||||||
|
break;
|
||||||
|
case Constants.PODCAST_FILTER_BY_ALL:
|
||||||
|
podcastEpisodes = podcastEpisodesFull;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -96,6 +96,9 @@ public class IndexFragment extends Fragment implements ClickCallback {
|
|||||||
musicIndexAdapter.setItems(IndexUtil.getArtist(indexes));
|
musicIndexAdapter.setItems(IndexUtil.getArtist(indexes));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
bind.fastScrollbar.setRecyclerView(bind.indexRecyclerView);
|
||||||
|
bind.fastScrollbar.setViewsToUse(R.layout.layout_fast_scrollbar, R.id.fastscroller_bubble, R.id.fastscroller_handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import androidx.media3.session.SessionToken;
|
|||||||
import androidx.navigation.Navigation;
|
import androidx.navigation.Navigation;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
|
||||||
|
import com.bumptech.glide.load.resource.bitmap.GranularRoundedCorners;
|
||||||
import com.cappielloantonio.tempo.R;
|
import com.cappielloantonio.tempo.R;
|
||||||
import com.cappielloantonio.tempo.databinding.FragmentPlaylistPageBinding;
|
import com.cappielloantonio.tempo.databinding.FragmentPlaylistPageBinding;
|
||||||
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
||||||
@@ -146,13 +147,13 @@ public class PlaylistPageFragment extends Fragment implements ClickCallback {
|
|||||||
playlistPageViewModel.getPlaylistSongLiveList().observe(getViewLifecycleOwner(), songs -> {
|
playlistPageViewModel.getPlaylistSongLiveList().observe(getViewLifecycleOwner(), songs -> {
|
||||||
if (bind != null) {
|
if (bind != null) {
|
||||||
bind.playlistPagePlayButton.setOnClickListener(v -> {
|
bind.playlistPagePlayButton.setOnClickListener(v -> {
|
||||||
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
|
MediaManager.startQueue(mediaBrowserListenableFuture, songs.subList(0, Math.min(100, songs.size())), 0);
|
||||||
activity.setBottomSheetInPeek(true);
|
activity.setBottomSheetInPeek(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
bind.playlistPageShuffleButton.setOnClickListener(v -> {
|
bind.playlistPageShuffleButton.setOnClickListener(v -> {
|
||||||
Collections.shuffle(songs);
|
Collections.shuffle(songs);
|
||||||
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
|
MediaManager.startQueue(mediaBrowserListenableFuture, songs.subList(0, Math.min(100, songs.size())), 0);
|
||||||
activity.setBottomSheetInPeek(true);
|
activity.setBottomSheetInPeek(true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -160,10 +161,39 @@ public class PlaylistPageFragment extends Fragment implements ClickCallback {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void initBackCover() {
|
private void initBackCover() {
|
||||||
CustomGlideRequest.Builder
|
playlistPageViewModel.getPlaylistSongLiveList().observe(requireActivity(), songs -> {
|
||||||
.from(requireContext(), playlistPageViewModel.getPlaylist().getCoverArtId())
|
if (bind != null) {
|
||||||
.build()
|
Collections.shuffle(songs);
|
||||||
.into(bind.playlistCoverImageView);
|
|
||||||
|
// Pic top-left
|
||||||
|
CustomGlideRequest.Builder
|
||||||
|
.from(requireContext(), songs.size() > 0 ? songs.get(0).getCoverArtId() : playlistPageViewModel.getPlaylist().getCoverArtId())
|
||||||
|
.build()
|
||||||
|
.transform(new GranularRoundedCorners(CustomGlideRequest.CORNER_RADIUS, 0, 0, 0))
|
||||||
|
.into(bind.playlistCoverImageViewTopLeft);
|
||||||
|
|
||||||
|
// Pic top-right
|
||||||
|
CustomGlideRequest.Builder
|
||||||
|
.from(requireContext(), songs.size() > 1 ? songs.get(1).getCoverArtId() : playlistPageViewModel.getPlaylist().getCoverArtId())
|
||||||
|
.build()
|
||||||
|
.transform(new GranularRoundedCorners(0, CustomGlideRequest.CORNER_RADIUS, 0, 0))
|
||||||
|
.into(bind.playlistCoverImageViewTopRight);
|
||||||
|
|
||||||
|
// Pic bottom-left
|
||||||
|
CustomGlideRequest.Builder
|
||||||
|
.from(requireContext(), songs.size() > 2 ? songs.get(2).getCoverArtId() : playlistPageViewModel.getPlaylist().getCoverArtId())
|
||||||
|
.build()
|
||||||
|
.transform(new GranularRoundedCorners(0, 0, 0, CustomGlideRequest.CORNER_RADIUS))
|
||||||
|
.into(bind.playlistCoverImageViewBottomLeft);
|
||||||
|
|
||||||
|
// Pic bottom-right
|
||||||
|
CustomGlideRequest.Builder
|
||||||
|
.from(requireContext(), songs.size() > 3 ? songs.get(3).getCoverArtId() : playlistPageViewModel.getPlaylist().getCoverArtId())
|
||||||
|
.build()
|
||||||
|
.transform(new GranularRoundedCorners(0, 0, CustomGlideRequest.CORNER_RADIUS, 0))
|
||||||
|
.into(bind.playlistCoverImageViewBottomRight);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initSongsView() {
|
private void initSongsView() {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import android.os.Bundle;
|
|||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.PopupMenu;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
@@ -17,7 +18,6 @@ import androidx.recyclerview.widget.LinearLayoutManager;
|
|||||||
|
|
||||||
import com.cappielloantonio.tempo.R;
|
import com.cappielloantonio.tempo.R;
|
||||||
import com.cappielloantonio.tempo.databinding.FragmentPodcastChannelPageBinding;
|
import com.cappielloantonio.tempo.databinding.FragmentPodcastChannelPageBinding;
|
||||||
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
|
||||||
import com.cappielloantonio.tempo.interfaces.ClickCallback;
|
import com.cappielloantonio.tempo.interfaces.ClickCallback;
|
||||||
import com.cappielloantonio.tempo.service.MediaManager;
|
import com.cappielloantonio.tempo.service.MediaManager;
|
||||||
import com.cappielloantonio.tempo.service.MediaService;
|
import com.cappielloantonio.tempo.service.MediaService;
|
||||||
@@ -28,11 +28,10 @@ import com.cappielloantonio.tempo.util.Constants;
|
|||||||
import com.cappielloantonio.tempo.util.MusicUtil;
|
import com.cappielloantonio.tempo.util.MusicUtil;
|
||||||
import com.cappielloantonio.tempo.util.UIUtil;
|
import com.cappielloantonio.tempo.util.UIUtil;
|
||||||
import com.cappielloantonio.tempo.viewmodel.PodcastChannelPageViewModel;
|
import com.cappielloantonio.tempo.viewmodel.PodcastChannelPageViewModel;
|
||||||
|
import com.google.android.material.snackbar.Snackbar;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
public class PodcastChannelPageFragment extends Fragment implements ClickCallback {
|
public class PodcastChannelPageFragment extends Fragment implements ClickCallback {
|
||||||
@@ -84,28 +83,26 @@ public class PodcastChannelPageFragment extends Fragment implements ClickCallbac
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void initAppBar() {
|
private void initAppBar() {
|
||||||
activity.setSupportActionBar(bind.animToolbar);
|
activity.setSupportActionBar(bind.toolbar);
|
||||||
if (activity.getSupportActionBar() != null)
|
|
||||||
activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
|
||||||
|
|
||||||
bind.collapsingToolbar.setTitle(MusicUtil.getReadableString(podcastChannelPageViewModel.getPodcastChannel().getTitle()));
|
if (activity.getSupportActionBar() != null) {
|
||||||
bind.animToolbar.setNavigationOnClickListener(v -> activity.navController.navigateUp());
|
activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
bind.collapsingToolbar.setExpandedTitleColor(getResources().getColor(R.color.white, null));
|
activity.getSupportActionBar().setDisplayShowHomeEnabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
bind.toolbar.setTitle(MusicUtil.getReadableString(podcastChannelPageViewModel.getPodcastChannel().getTitle()));
|
||||||
|
bind.toolbar.setNavigationOnClickListener(v -> activity.navController.navigateUp());
|
||||||
|
bind.toolbar.setTitle(MusicUtil.getReadableString(podcastChannelPageViewModel.getPodcastChannel().getTitle()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initPodcastChannelInfo() {
|
private void initPodcastChannelInfo() {
|
||||||
String normalizePodcastChannelDescription = MusicUtil.forceReadableString(podcastChannelPageViewModel.getPodcastChannel().getDescription());
|
String normalizePodcastChannelDescription = MusicUtil.forceReadableString(podcastChannelPageViewModel.getPodcastChannel().getDescription());
|
||||||
|
|
||||||
if (bind != null)
|
if (bind != null) {
|
||||||
bind.podcastChannelDescriptionTextView.setVisibility(!normalizePodcastChannelDescription.trim().isEmpty() ? View.VISIBLE : View.GONE);
|
bind.podcastChannelDescriptionTextView.setVisibility(!normalizePodcastChannelDescription.trim().isEmpty() ? View.VISIBLE : View.GONE);
|
||||||
|
|
||||||
if (getContext() != null && bind != null) CustomGlideRequest.Builder
|
|
||||||
.from(requireContext(), podcastChannelPageViewModel.getPodcastChannel().getCoverArtId())
|
|
||||||
.build()
|
|
||||||
.into(bind.podcastChannelBackdropImageView);
|
|
||||||
|
|
||||||
if (bind != null)
|
|
||||||
bind.podcastChannelDescriptionTextView.setText(normalizePodcastChannelDescription);
|
bind.podcastChannelDescriptionTextView.setText(normalizePodcastChannelDescription);
|
||||||
|
bind.podcastEpisodesFilterImageView.setOnClickListener(view -> showPopupMenu(view, R.menu.filter_podcast_episode_popup_menu));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initPodcastChannelEpisodesView() {
|
private void initPodcastChannelEpisodesView() {
|
||||||
@@ -116,22 +113,21 @@ public class PodcastChannelPageFragment extends Fragment implements ClickCallbac
|
|||||||
bind.podcastEpisodesRecyclerView.setAdapter(podcastEpisodeAdapter);
|
bind.podcastEpisodesRecyclerView.setAdapter(podcastEpisodeAdapter);
|
||||||
podcastChannelPageViewModel.getPodcastChannelEpisodes().observe(getViewLifecycleOwner(), channels -> {
|
podcastChannelPageViewModel.getPodcastChannelEpisodes().observe(getViewLifecycleOwner(), channels -> {
|
||||||
if (channels == null) {
|
if (channels == null) {
|
||||||
if (bind != null)
|
if (bind != null) {
|
||||||
bind.podcastChannelPageEpisodesPlaceholder.placeholder.setVisibility(View.VISIBLE);
|
bind.podcastEpisodesRecyclerView.setVisibility(View.GONE);
|
||||||
if (bind != null) bind.podcastChannelPageEpisodesSector.setVisibility(View.GONE);
|
}
|
||||||
} else {
|
} else {
|
||||||
if (bind != null)
|
if (bind != null) {
|
||||||
bind.podcastChannelPageEpisodesPlaceholder.placeholder.setVisibility(View.GONE);
|
bind.podcastEpisodesRecyclerView.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
if (!channels.isEmpty() && channels.get(0) != null && channels.get(0).getEpisodes() != null) {
|
if (!channels.isEmpty() && channels.get(0) != null && channels.get(0).getEpisodes() != null) {
|
||||||
List<PodcastEpisode> availableEpisode = channels.get(0).getEpisodes().stream().filter(podcastEpisode -> Objects.equals(podcastEpisode.getStatus(), "completed")).collect(Collectors.toList());
|
List<PodcastEpisode> availableEpisode = channels.get(0).getEpisodes();
|
||||||
|
|
||||||
if (bind != null) {
|
if (bind != null && availableEpisode != null) {
|
||||||
bind.podcastEpisodesRecyclerView.setVisibility(availableEpisode.isEmpty() ? View.GONE : View.VISIBLE);
|
bind.podcastEpisodesRecyclerView.setVisibility(availableEpisode.isEmpty() ? View.GONE : View.VISIBLE);
|
||||||
bind.podcastEpisodesAvailabilityTextView.setVisibility(availableEpisode.isEmpty() ? View.VISIBLE : View.GONE);
|
podcastEpisodeAdapter.setItems(availableEpisode);
|
||||||
}
|
}
|
||||||
|
|
||||||
podcastEpisodeAdapter.setItems(availableEpisode);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -145,6 +141,25 @@ public class PodcastChannelPageFragment extends Fragment implements ClickCallbac
|
|||||||
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
|
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void showPopupMenu(View view, int menuResource) {
|
||||||
|
PopupMenu popup = new PopupMenu(requireContext(), view);
|
||||||
|
popup.getMenuInflater().inflate(menuResource, popup.getMenu());
|
||||||
|
|
||||||
|
popup.setOnMenuItemClickListener(menuItem -> {
|
||||||
|
if (menuItem.getItemId() == R.id.menu_podcast_filter_download) {
|
||||||
|
podcastEpisodeAdapter.sort(Constants.PODCAST_FILTER_BY_DOWNLOAD);
|
||||||
|
return true;
|
||||||
|
} else if (menuItem.getItemId() == R.id.menu_podcast_filter_all) {
|
||||||
|
podcastEpisodeAdapter.sort(Constants.PODCAST_FILTER_BY_ALL);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
popup.show();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPodcastEpisodeClick(Bundle bundle) {
|
public void onPodcastEpisodeClick(Bundle bundle) {
|
||||||
MediaManager.startPodcast(mediaBrowserListenableFuture, bundle.getParcelable(Constants.PODCAST_OBJECT));
|
MediaManager.startPodcast(mediaBrowserListenableFuture, bundle.getParcelable(Constants.PODCAST_OBJECT));
|
||||||
@@ -155,4 +170,14 @@ public class PodcastChannelPageFragment extends Fragment implements ClickCallbac
|
|||||||
public void onPodcastEpisodeLongClick(Bundle bundle) {
|
public void onPodcastEpisodeLongClick(Bundle bundle) {
|
||||||
Navigation.findNavController(requireView()).navigate(R.id.podcastEpisodeBottomSheetDialog, bundle);
|
Navigation.findNavController(requireView()).navigate(R.id.podcastEpisodeBottomSheetDialog, bundle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPodcastEpisodeAltClick(Bundle bundle) {
|
||||||
|
PodcastEpisode episode = bundle.getParcelable(Constants.PODCAST_OBJECT);
|
||||||
|
podcastChannelPageViewModel.requestPodcastEpisodeDownload(episode);
|
||||||
|
|
||||||
|
Snackbar.make(requireView(), R.string.podcast_episode_download_request_snackbar, Snackbar.LENGTH_SHORT)
|
||||||
|
.setAnchorView(activity.bind.bottomNavigation)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -91,7 +91,7 @@ public class AlbumBottomSheetDialog extends BottomSheetDialogFragment implements
|
|||||||
artistAlbum.setText(MusicUtil.getReadableString(albumBottomSheetViewModel.getAlbum().getArtist()));
|
artistAlbum.setText(MusicUtil.getReadableString(albumBottomSheetViewModel.getAlbum().getArtist()));
|
||||||
|
|
||||||
ToggleButton favoriteToggle = view.findViewById(R.id.button_favorite);
|
ToggleButton favoriteToggle = view.findViewById(R.id.button_favorite);
|
||||||
favoriteToggle.setChecked(Boolean.TRUE.equals(albumBottomSheetViewModel.getAlbum().getStarred()));
|
favoriteToggle.setChecked(albumBottomSheetViewModel.getAlbum().getStarred() != null);
|
||||||
favoriteToggle.setOnClickListener(v -> {
|
favoriteToggle.setOnClickListener(v -> {
|
||||||
albumBottomSheetViewModel.setFavorite();
|
albumBottomSheetViewModel.setFavorite();
|
||||||
dismissBottomSheet();
|
dismissBottomSheet();
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ public class ArtistBottomSheetDialog extends BottomSheetDialogFragment implement
|
|||||||
nameArtist.setSelected(true);
|
nameArtist.setSelected(true);
|
||||||
|
|
||||||
ToggleButton favoriteToggle = view.findViewById(R.id.button_favorite);
|
ToggleButton favoriteToggle = view.findViewById(R.id.button_favorite);
|
||||||
favoriteToggle.setChecked(Boolean.TRUE.equals(artistBottomSheetViewModel.getArtist().getStarred()));
|
favoriteToggle.setChecked(artistBottomSheetViewModel.getArtist().getStarred() != null);
|
||||||
favoriteToggle.setOnClickListener(v -> {
|
favoriteToggle.setOnClickListener(v -> {
|
||||||
artistBottomSheetViewModel.setFavorite();
|
artistBottomSheetViewModel.setFavorite();
|
||||||
dismissBottomSheet();
|
dismissBottomSheet();
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ public class SongBottomSheetDialog extends BottomSheetDialogFragment implements
|
|||||||
artistSong.setText(MusicUtil.getReadableString(songBottomSheetViewModel.getSong().getArtist()));
|
artistSong.setText(MusicUtil.getReadableString(songBottomSheetViewModel.getSong().getArtist()));
|
||||||
|
|
||||||
ToggleButton favoriteToggle = view.findViewById(R.id.button_favorite);
|
ToggleButton favoriteToggle = view.findViewById(R.id.button_favorite);
|
||||||
favoriteToggle.setChecked(Boolean.TRUE.equals(songBottomSheetViewModel.getSong().getStarred()));
|
favoriteToggle.setChecked(songBottomSheetViewModel.getSong().getStarred() != null);
|
||||||
favoriteToggle.setOnClickListener(v -> {
|
favoriteToggle.setOnClickListener(v -> {
|
||||||
songBottomSheetViewModel.setFavorite(requireContext());
|
songBottomSheetViewModel.setFavorite(requireContext());
|
||||||
dismissBottomSheet();
|
dismissBottomSheet();
|
||||||
|
|||||||
@@ -44,6 +44,9 @@ object Constants {
|
|||||||
const val PLAYLIST_ORDER_BY_NAME = "ORDER_BY_NAME"
|
const val PLAYLIST_ORDER_BY_NAME = "ORDER_BY_NAME"
|
||||||
const val PLAYLIST_ORDER_BY_RANDOM = "ORDER_BY_RANDOM"
|
const val PLAYLIST_ORDER_BY_RANDOM = "ORDER_BY_RANDOM"
|
||||||
|
|
||||||
|
const val PODCAST_FILTER_BY_DOWNLOAD = "PODCAST_FILTER_BY_DOWNLOAD"
|
||||||
|
const val PODCAST_FILTER_BY_ALL = "PODCAST_FILTER_BY_ALL"
|
||||||
|
|
||||||
const val MEDIA_TYPE_MUSIC = "music"
|
const val MEDIA_TYPE_MUSIC = "music"
|
||||||
const val MEDIA_TYPE_PODCAST = "podcast"
|
const val MEDIA_TYPE_PODCAST = "podcast"
|
||||||
const val MEDIA_TYPE_AUDIOBOOK = "audiobook"
|
const val MEDIA_TYPE_AUDIOBOOK = "audiobook"
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.cappielloantonio.tempo.util;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.net.ConnectivityManager;
|
||||||
|
import android.net.NetworkInfo;
|
||||||
|
|
||||||
|
import com.cappielloantonio.tempo.App;
|
||||||
|
|
||||||
|
public class NetworkUtil {
|
||||||
|
public static boolean isOffline() {
|
||||||
|
ConnectivityManager connectivityManager = (ConnectivityManager) App.getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||||
|
|
||||||
|
if (connectivityManager != null) {
|
||||||
|
NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
|
||||||
|
|
||||||
|
return networkInfo == null || !networkInfo.isConnected();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -105,7 +105,7 @@ object Preferences {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun askForOptimization(): Boolean? {
|
fun askForOptimization(): Boolean {
|
||||||
return App.getInstance().preferences.getBoolean(BATTERY_OPTIMIZATION, true)
|
return App.getInstance().preferences.getBoolean(BATTERY_OPTIMIZATION, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package com.cappielloantonio.tempo.util;
|
package com.cappielloantonio.tempo.util;
|
||||||
|
|
||||||
|
import androidx.annotation.OptIn;
|
||||||
import androidx.media3.common.Metadata;
|
import androidx.media3.common.Metadata;
|
||||||
import androidx.media3.common.Tracks;
|
import androidx.media3.common.Tracks;
|
||||||
|
import androidx.media3.common.util.UnstableApi;
|
||||||
import androidx.media3.exoplayer.ExoPlayer;
|
import androidx.media3.exoplayer.ExoPlayer;
|
||||||
|
|
||||||
import com.cappielloantonio.tempo.model.ReplayGain;
|
import com.cappielloantonio.tempo.model.ReplayGain;
|
||||||
@@ -10,6 +12,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@OptIn(markerClass = UnstableApi.class)
|
||||||
public class ReplayGainUtil {
|
public class ReplayGainUtil {
|
||||||
private static final String[] tags = {"REPLAYGAIN_TRACK_GAIN", "REPLAYGAIN_ALBUM_GAIN", "R128_TRACK_GAIN", "R128_ALBUM_GAIN"};
|
private static final String[] tags = {"REPLAYGAIN_TRACK_GAIN", "REPLAYGAIN_ALBUM_GAIN", "R128_TRACK_GAIN", "R128_ALBUM_GAIN"};
|
||||||
|
|
||||||
@@ -23,11 +26,15 @@ public class ReplayGainUtil {
|
|||||||
private static List<Metadata> getMetadata(Tracks tracks) {
|
private static List<Metadata> getMetadata(Tracks tracks) {
|
||||||
List<Metadata> metadata = new ArrayList<>();
|
List<Metadata> metadata = new ArrayList<>();
|
||||||
|
|
||||||
for (int i = 0; i < tracks.getGroups().size(); i++) {
|
if (tracks != null && !tracks.getGroups().isEmpty()) {
|
||||||
Tracks.Group group = tracks.getGroups().get(i);
|
for (int i = 0; i < tracks.getGroups().size(); i++) {
|
||||||
|
Tracks.Group group = tracks.getGroups().get(i);
|
||||||
|
|
||||||
for (int j = 0; j < group.getMediaTrackGroup().length; j++) {
|
if (group != null && group.getMediaTrackGroup() != null) {
|
||||||
metadata.add(group.getTrackFormat(j).metadata);
|
for (int j = 0; j < group.getMediaTrackGroup().length; j++) {
|
||||||
|
metadata.add(group.getTrackFormat(j).metadata);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,13 +44,19 @@ public class ReplayGainUtil {
|
|||||||
private static List<ReplayGain> getReplayGains(List<Metadata> metadata) {
|
private static List<ReplayGain> getReplayGains(List<Metadata> metadata) {
|
||||||
List<ReplayGain> gains = new ArrayList<>();
|
List<ReplayGain> gains = new ArrayList<>();
|
||||||
|
|
||||||
for (int i = 0; i < metadata.size(); i++) {
|
if (metadata != null) {
|
||||||
for (int j = 0; j < metadata.get(i).length(); j++) {
|
for (int i = 0; i < metadata.size(); i++) {
|
||||||
Metadata.Entry entry = metadata.get(i).get(j);
|
Metadata singleMetadata = metadata.get(i);
|
||||||
|
|
||||||
if (checkReplayGain(entry)) {
|
if (singleMetadata != null) {
|
||||||
ReplayGain replayGain = setReplayGains(entry);
|
for (int j = 0; j < singleMetadata.length(); j++) {
|
||||||
gains.add(replayGain);
|
Metadata.Entry entry = singleMetadata.get(j);
|
||||||
|
|
||||||
|
if (checkReplayGain(entry)) {
|
||||||
|
ReplayGain replayGain = setReplayGains(entry);
|
||||||
|
gains.add(replayGain);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,11 +7,14 @@ import androidx.lifecycle.AndroidViewModel;
|
|||||||
import androidx.lifecycle.LiveData;
|
import androidx.lifecycle.LiveData;
|
||||||
import androidx.lifecycle.MutableLiveData;
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
|
||||||
|
import com.cappielloantonio.tempo.interfaces.StarCallback;
|
||||||
import com.cappielloantonio.tempo.repository.AlbumRepository;
|
import com.cappielloantonio.tempo.repository.AlbumRepository;
|
||||||
import com.cappielloantonio.tempo.repository.ArtistRepository;
|
import com.cappielloantonio.tempo.repository.ArtistRepository;
|
||||||
|
import com.cappielloantonio.tempo.repository.FavoriteRepository;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
|
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||||
|
import com.cappielloantonio.tempo.util.NetworkUtil;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -19,6 +22,7 @@ import java.util.List;
|
|||||||
public class AlbumBottomSheetViewModel extends AndroidViewModel {
|
public class AlbumBottomSheetViewModel extends AndroidViewModel {
|
||||||
private final AlbumRepository albumRepository;
|
private final AlbumRepository albumRepository;
|
||||||
private final ArtistRepository artistRepository;
|
private final ArtistRepository artistRepository;
|
||||||
|
private final FavoriteRepository favoriteRepository;
|
||||||
|
|
||||||
private AlbumID3 album;
|
private AlbumID3 album;
|
||||||
|
|
||||||
@@ -27,6 +31,7 @@ public class AlbumBottomSheetViewModel extends AndroidViewModel {
|
|||||||
|
|
||||||
albumRepository = new AlbumRepository();
|
albumRepository = new AlbumRepository();
|
||||||
artistRepository = new ArtistRepository();
|
artistRepository = new ArtistRepository();
|
||||||
|
favoriteRepository = new FavoriteRepository();
|
||||||
}
|
}
|
||||||
|
|
||||||
public AlbumID3 getAlbum() {
|
public AlbumID3 getAlbum() {
|
||||||
@@ -47,11 +52,51 @@ public class AlbumBottomSheetViewModel extends AndroidViewModel {
|
|||||||
|
|
||||||
public void setFavorite() {
|
public void setFavorite() {
|
||||||
if (album.getStarred() != null) {
|
if (album.getStarred() != null) {
|
||||||
artistRepository.unstar(album.getId());
|
if (NetworkUtil.isOffline()) {
|
||||||
album.setStarred(null);
|
removeFavoriteOffline();
|
||||||
|
} else {
|
||||||
|
removeFavoriteOnline();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
artistRepository.star(album.getId());
|
if (NetworkUtil.isOffline()) {
|
||||||
album.setStarred(new Date());
|
setFavoriteOffline();
|
||||||
|
} else {
|
||||||
|
setFavoriteOnline();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void removeFavoriteOffline() {
|
||||||
|
favoriteRepository.starLater(null, album.getId(), null, false);
|
||||||
|
album.setStarred(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeFavoriteOnline() {
|
||||||
|
favoriteRepository.unstar(null, album.getId(), null, new StarCallback() {
|
||||||
|
@Override
|
||||||
|
public void onError() {
|
||||||
|
// album.setStarred(new Date());
|
||||||
|
favoriteRepository.starLater(null, album.getId(), null, false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
album.setStarred(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setFavoriteOffline() {
|
||||||
|
favoriteRepository.starLater(null, album.getId(), null, true);
|
||||||
|
album.setStarred(new Date());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setFavoriteOnline() {
|
||||||
|
favoriteRepository.star(null, album.getId(), null, new StarCallback() {
|
||||||
|
@Override
|
||||||
|
public void onError() {
|
||||||
|
// album.setStarred(null);
|
||||||
|
favoriteRepository.starLater(null, album.getId(), null, true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
album.setStarred(new Date());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,20 +5,25 @@ import android.app.Application;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.lifecycle.AndroidViewModel;
|
import androidx.lifecycle.AndroidViewModel;
|
||||||
|
|
||||||
import com.cappielloantonio.tempo.repository.AlbumRepository;
|
import com.cappielloantonio.tempo.interfaces.StarCallback;
|
||||||
|
import com.cappielloantonio.tempo.repository.ArtistRepository;
|
||||||
|
import com.cappielloantonio.tempo.repository.FavoriteRepository;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
||||||
|
import com.cappielloantonio.tempo.util.NetworkUtil;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
public class ArtistBottomSheetViewModel extends AndroidViewModel {
|
public class ArtistBottomSheetViewModel extends AndroidViewModel {
|
||||||
private final AlbumRepository albumRepository;
|
private final ArtistRepository artistRepository;
|
||||||
|
private final FavoriteRepository favoriteRepository;
|
||||||
|
|
||||||
private ArtistID3 artist;
|
private ArtistID3 artist;
|
||||||
|
|
||||||
public ArtistBottomSheetViewModel(@NonNull Application application) {
|
public ArtistBottomSheetViewModel(@NonNull Application application) {
|
||||||
super(application);
|
super(application);
|
||||||
|
|
||||||
albumRepository = new AlbumRepository();
|
artistRepository = new ArtistRepository();
|
||||||
|
favoriteRepository = new FavoriteRepository();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ArtistID3 getArtist() {
|
public ArtistID3 getArtist() {
|
||||||
@@ -31,11 +36,51 @@ public class ArtistBottomSheetViewModel extends AndroidViewModel {
|
|||||||
|
|
||||||
public void setFavorite() {
|
public void setFavorite() {
|
||||||
if (artist.getStarred() != null) {
|
if (artist.getStarred() != null) {
|
||||||
albumRepository.unstar(artist.getId());
|
if (NetworkUtil.isOffline()) {
|
||||||
artist.setStarred(null);
|
removeFavoriteOffline();
|
||||||
|
} else {
|
||||||
|
removeFavoriteOnline();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
albumRepository.star(artist.getId());
|
if (NetworkUtil.isOffline()) {
|
||||||
artist.setStarred(new Date());
|
setFavoriteOffline();
|
||||||
|
} else {
|
||||||
|
setFavoriteOnline();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void removeFavoriteOffline() {
|
||||||
|
favoriteRepository.starLater(null, null, artist.getId(), false);
|
||||||
|
artist.setStarred(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeFavoriteOnline() {
|
||||||
|
favoriteRepository.unstar(null, null, artist.getId(), new StarCallback() {
|
||||||
|
@Override
|
||||||
|
public void onError() {
|
||||||
|
// artist.setStarred(new Date());
|
||||||
|
favoriteRepository.starLater(null, null, artist.getId(), false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
artist.setStarred(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setFavoriteOffline() {
|
||||||
|
favoriteRepository.starLater(null, null, artist.getId(), true);
|
||||||
|
artist.setStarred(new Date());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setFavoriteOnline() {
|
||||||
|
favoriteRepository.star(null, null, artist.getId(), new StarCallback() {
|
||||||
|
@Override
|
||||||
|
public void onError() {
|
||||||
|
// artist.setStarred(null);
|
||||||
|
favoriteRepository.starLater(null, null, artist.getId(), true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
artist.setStarred(new Date());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,20 +8,24 @@ import androidx.lifecycle.LifecycleOwner;
|
|||||||
import androidx.lifecycle.LiveData;
|
import androidx.lifecycle.LiveData;
|
||||||
import androidx.lifecycle.MutableLiveData;
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
|
||||||
|
import com.cappielloantonio.tempo.interfaces.StarCallback;
|
||||||
import com.cappielloantonio.tempo.model.Chronology;
|
import com.cappielloantonio.tempo.model.Chronology;
|
||||||
|
import com.cappielloantonio.tempo.model.Favorite;
|
||||||
import com.cappielloantonio.tempo.repository.AlbumRepository;
|
import com.cappielloantonio.tempo.repository.AlbumRepository;
|
||||||
import com.cappielloantonio.tempo.repository.ArtistRepository;
|
import com.cappielloantonio.tempo.repository.ArtistRepository;
|
||||||
import com.cappielloantonio.tempo.repository.ChronologyRepository;
|
import com.cappielloantonio.tempo.repository.ChronologyRepository;
|
||||||
import com.cappielloantonio.tempo.repository.PodcastRepository;
|
import com.cappielloantonio.tempo.repository.FavoriteRepository;
|
||||||
import com.cappielloantonio.tempo.repository.SongRepository;
|
import com.cappielloantonio.tempo.repository.SongRepository;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
|
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||||
import com.cappielloantonio.tempo.util.Preferences;
|
import com.cappielloantonio.tempo.util.Preferences;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class HomeViewModel extends AndroidViewModel {
|
public class HomeViewModel extends AndroidViewModel {
|
||||||
@@ -31,6 +35,7 @@ public class HomeViewModel extends AndroidViewModel {
|
|||||||
private final AlbumRepository albumRepository;
|
private final AlbumRepository albumRepository;
|
||||||
private final ArtistRepository artistRepository;
|
private final ArtistRepository artistRepository;
|
||||||
private final ChronologyRepository chronologyRepository;
|
private final ChronologyRepository chronologyRepository;
|
||||||
|
private final FavoriteRepository favoriteRepository;
|
||||||
|
|
||||||
private final MutableLiveData<List<Child>> dicoverSongSample = new MutableLiveData<>(null);
|
private final MutableLiveData<List<Child>> dicoverSongSample = new MutableLiveData<>(null);
|
||||||
private final MutableLiveData<List<AlbumID3>> newReleasedAlbum = new MutableLiveData<>(null);
|
private final MutableLiveData<List<AlbumID3>> newReleasedAlbum = new MutableLiveData<>(null);
|
||||||
@@ -57,6 +62,9 @@ public class HomeViewModel extends AndroidViewModel {
|
|||||||
albumRepository = new AlbumRepository();
|
albumRepository = new AlbumRepository();
|
||||||
artistRepository = new ArtistRepository();
|
artistRepository = new ArtistRepository();
|
||||||
chronologyRepository = new ChronologyRepository();
|
chronologyRepository = new ChronologyRepository();
|
||||||
|
favoriteRepository = new FavoriteRepository();
|
||||||
|
|
||||||
|
setOfflineFavorite();
|
||||||
}
|
}
|
||||||
|
|
||||||
public LiveData<List<Child>> getDiscoverSongSample(LifecycleOwner owner) {
|
public LiveData<List<Child>> getDiscoverSongSample(LifecycleOwner owner) {
|
||||||
@@ -239,4 +247,109 @@ public class HomeViewModel extends AndroidViewModel {
|
|||||||
public void refreshRecentlyPlayedAlbumList(LifecycleOwner owner) {
|
public void refreshRecentlyPlayedAlbumList(LifecycleOwner owner) {
|
||||||
albumRepository.getAlbums("recent", 20, null, null).observe(owner, recentlyPlayedAlbumSample::postValue);
|
albumRepository.getAlbums("recent", 20, null, null).observe(owner, recentlyPlayedAlbumSample::postValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setOfflineFavorite() {
|
||||||
|
ArrayList<Favorite> favorites = getFavorites();
|
||||||
|
ArrayList<Favorite> favoritesToSave = getFavoritesToSave(favorites);
|
||||||
|
ArrayList<Favorite> favoritesToDelete = getFavoritesToDelete(favorites, favoritesToSave);
|
||||||
|
|
||||||
|
manageFavoriteToSave(favoritesToSave);
|
||||||
|
manageFavoriteToDelete(favoritesToDelete);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ArrayList<Favorite> getFavorites() {
|
||||||
|
return new ArrayList<>(favoriteRepository.getFavorites());
|
||||||
|
}
|
||||||
|
|
||||||
|
private ArrayList<Favorite> getFavoritesToSave(ArrayList<Favorite> favorites) {
|
||||||
|
HashMap<String, Favorite> filteredMap = new HashMap<>();
|
||||||
|
|
||||||
|
for (Favorite favorite : favorites) {
|
||||||
|
String key = favorite.toString();
|
||||||
|
|
||||||
|
if (!filteredMap.containsKey(key) || favorite.getTimestamp() > filteredMap.get(key).getTimestamp()) {
|
||||||
|
filteredMap.put(key, favorite);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ArrayList<>(filteredMap.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
private ArrayList<Favorite> getFavoritesToDelete(ArrayList<Favorite> favorites, ArrayList<Favorite> favoritesToSave) {
|
||||||
|
ArrayList<Favorite> favoritesToDelete = new ArrayList<>();
|
||||||
|
|
||||||
|
for (Favorite favorite : favorites) {
|
||||||
|
if (!favoritesToSave.contains(favorite)) {
|
||||||
|
favoritesToDelete.add(favorite);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return favoritesToDelete;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void manageFavoriteToSave(ArrayList<Favorite> favoritesToSave) {
|
||||||
|
for (Favorite favorite : favoritesToSave) {
|
||||||
|
if (favorite.getToStar()) {
|
||||||
|
favoriteToStar(favorite);
|
||||||
|
} else {
|
||||||
|
favoriteToUnstar(favorite);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void manageFavoriteToDelete(ArrayList<Favorite> favoritesToDelete) {
|
||||||
|
for (Favorite favorite : favoritesToDelete) {
|
||||||
|
favoriteRepository.delete(favorite);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void favoriteToStar(Favorite favorite) {
|
||||||
|
if (favorite.getSongId() != null) {
|
||||||
|
favoriteRepository.star(favorite.getSongId(), null, null, new StarCallback() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess() {
|
||||||
|
favoriteRepository.delete(favorite);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (favorite.getAlbumId() != null) {
|
||||||
|
favoriteRepository.star(null, favorite.getAlbumId(), null, new StarCallback() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess() {
|
||||||
|
favoriteRepository.delete(favorite);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (favorite.getArtistId() != null) {
|
||||||
|
favoriteRepository.star(null, null, favorite.getArtistId(), new StarCallback() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess() {
|
||||||
|
favoriteRepository.delete(favorite);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void favoriteToUnstar(Favorite favorite) {
|
||||||
|
if (favorite.getSongId() != null) {
|
||||||
|
favoriteRepository.unstar(favorite.getSongId(), null, null, new StarCallback() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess() {
|
||||||
|
favoriteRepository.delete(favorite);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (favorite.getAlbumId() != null) {
|
||||||
|
favoriteRepository.unstar(null, favorite.getAlbumId(), null, new StarCallback() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess() {
|
||||||
|
favoriteRepository.delete(favorite);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (favorite.getArtistId() != null) {
|
||||||
|
favoriteRepository.unstar(null, null, favorite.getArtistId(), new StarCallback() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess() {
|
||||||
|
favoriteRepository.delete(favorite);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,9 +11,11 @@ import androidx.lifecycle.LiveData;
|
|||||||
import androidx.lifecycle.MutableLiveData;
|
import androidx.lifecycle.MutableLiveData;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
|
||||||
|
import com.cappielloantonio.tempo.interfaces.StarCallback;
|
||||||
import com.cappielloantonio.tempo.model.Download;
|
import com.cappielloantonio.tempo.model.Download;
|
||||||
import com.cappielloantonio.tempo.model.Queue;
|
import com.cappielloantonio.tempo.model.Queue;
|
||||||
import com.cappielloantonio.tempo.repository.ArtistRepository;
|
import com.cappielloantonio.tempo.repository.ArtistRepository;
|
||||||
|
import com.cappielloantonio.tempo.repository.FavoriteRepository;
|
||||||
import com.cappielloantonio.tempo.repository.QueueRepository;
|
import com.cappielloantonio.tempo.repository.QueueRepository;
|
||||||
import com.cappielloantonio.tempo.repository.SongRepository;
|
import com.cappielloantonio.tempo.repository.SongRepository;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
||||||
@@ -22,6 +24,7 @@ import com.cappielloantonio.tempo.subsonic.models.PlayQueue;
|
|||||||
import com.cappielloantonio.tempo.util.Constants;
|
import com.cappielloantonio.tempo.util.Constants;
|
||||||
import com.cappielloantonio.tempo.util.DownloadUtil;
|
import com.cappielloantonio.tempo.util.DownloadUtil;
|
||||||
import com.cappielloantonio.tempo.util.MappingUtil;
|
import com.cappielloantonio.tempo.util.MappingUtil;
|
||||||
|
import com.cappielloantonio.tempo.util.NetworkUtil;
|
||||||
import com.cappielloantonio.tempo.util.Preferences;
|
import com.cappielloantonio.tempo.util.Preferences;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -36,6 +39,7 @@ public class PlayerBottomSheetViewModel extends AndroidViewModel {
|
|||||||
private final SongRepository songRepository;
|
private final SongRepository songRepository;
|
||||||
private final ArtistRepository artistRepository;
|
private final ArtistRepository artistRepository;
|
||||||
private final QueueRepository queueRepository;
|
private final QueueRepository queueRepository;
|
||||||
|
private final FavoriteRepository favoriteRepository;
|
||||||
|
|
||||||
private final MutableLiveData<String> lyricsLiveData = new MutableLiveData<>(null);
|
private final MutableLiveData<String> lyricsLiveData = new MutableLiveData<>(null);
|
||||||
|
|
||||||
@@ -50,6 +54,7 @@ public class PlayerBottomSheetViewModel extends AndroidViewModel {
|
|||||||
songRepository = new SongRepository();
|
songRepository = new SongRepository();
|
||||||
artistRepository = new ArtistRepository();
|
artistRepository = new ArtistRepository();
|
||||||
queueRepository = new QueueRepository();
|
queueRepository = new QueueRepository();
|
||||||
|
favoriteRepository = new FavoriteRepository();
|
||||||
}
|
}
|
||||||
|
|
||||||
public LiveData<List<Queue>> getQueueSong() {
|
public LiveData<List<Queue>> getQueueSong() {
|
||||||
@@ -59,22 +64,62 @@ public class PlayerBottomSheetViewModel extends AndroidViewModel {
|
|||||||
public void setFavorite(Context context, Child media) {
|
public void setFavorite(Context context, Child media) {
|
||||||
if (media != null) {
|
if (media != null) {
|
||||||
if (media.getStarred() != null) {
|
if (media.getStarred() != null) {
|
||||||
songRepository.unstar(media.getId());
|
if (NetworkUtil.isOffline()) {
|
||||||
media.setStarred(null);
|
removeFavoriteOffline(media);
|
||||||
|
} else {
|
||||||
|
removeFavoriteOnline(media);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
songRepository.star(media.getId());
|
if (NetworkUtil.isOffline()) {
|
||||||
media.setStarred(new Date());
|
setFavoriteOffline(media);
|
||||||
|
} else {
|
||||||
if (Preferences.isStarredSyncEnabled()) {
|
setFavoriteOnline(context, media);
|
||||||
DownloadUtil.getDownloadTracker(context).download(
|
|
||||||
MappingUtil.mapDownload(media),
|
|
||||||
new Download(media)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void removeFavoriteOffline(Child media) {
|
||||||
|
favoriteRepository.starLater(media.getId(), null, null, false);
|
||||||
|
media.setStarred(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeFavoriteOnline(Child media) {
|
||||||
|
favoriteRepository.unstar(media.getId(), null, null, new StarCallback() {
|
||||||
|
@Override
|
||||||
|
public void onError() {
|
||||||
|
// media.setStarred(new Date());
|
||||||
|
favoriteRepository.starLater(media.getId(), null, null, false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
media.setStarred(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setFavoriteOffline(Child media) {
|
||||||
|
favoriteRepository.starLater(media.getId(), null, null, true);
|
||||||
|
media.setStarred(new Date());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setFavoriteOnline(Context context, Child media) {
|
||||||
|
favoriteRepository.star(media.getId(), null, null, new StarCallback() {
|
||||||
|
@Override
|
||||||
|
public void onError() {
|
||||||
|
// media.setStarred(null);
|
||||||
|
favoriteRepository.starLater(media.getId(), null, null, true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
media.setStarred(new Date());
|
||||||
|
|
||||||
|
if (Preferences.isStarredSyncEnabled()) {
|
||||||
|
DownloadUtil.getDownloadTracker(context).download(
|
||||||
|
MappingUtil.mapDownload(media),
|
||||||
|
new Download(media)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public LiveData<String> getLiveLyrics() {
|
public LiveData<String> getLiveLyrics() {
|
||||||
return lyricsLiveData;
|
return lyricsLiveData;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import androidx.lifecycle.LiveData;
|
|||||||
|
|
||||||
import com.cappielloantonio.tempo.repository.PodcastRepository;
|
import com.cappielloantonio.tempo.repository.PodcastRepository;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.PodcastChannel;
|
import com.cappielloantonio.tempo.subsonic.models.PodcastChannel;
|
||||||
|
import com.cappielloantonio.tempo.subsonic.models.PodcastEpisode;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -33,4 +34,8 @@ public class PodcastChannelPageViewModel extends AndroidViewModel {
|
|||||||
public void setPodcastChannel(PodcastChannel podcastChannel) {
|
public void setPodcastChannel(PodcastChannel podcastChannel) {
|
||||||
this.podcastChannel = podcastChannel;
|
this.podcastChannel = podcastChannel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void requestPodcastEpisodeDownload(PodcastEpisode podcastEpisode) {
|
||||||
|
podcastRepository.downloadPodcastEpisode(podcastEpisode.getId());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,15 +10,18 @@ import androidx.lifecycle.LiveData;
|
|||||||
import androidx.lifecycle.MutableLiveData;
|
import androidx.lifecycle.MutableLiveData;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
|
||||||
|
import com.cappielloantonio.tempo.interfaces.StarCallback;
|
||||||
import com.cappielloantonio.tempo.model.Download;
|
import com.cappielloantonio.tempo.model.Download;
|
||||||
import com.cappielloantonio.tempo.repository.AlbumRepository;
|
import com.cappielloantonio.tempo.repository.AlbumRepository;
|
||||||
import com.cappielloantonio.tempo.repository.ArtistRepository;
|
import com.cappielloantonio.tempo.repository.ArtistRepository;
|
||||||
|
import com.cappielloantonio.tempo.repository.FavoriteRepository;
|
||||||
import com.cappielloantonio.tempo.repository.SongRepository;
|
import com.cappielloantonio.tempo.repository.SongRepository;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
|
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||||
import com.cappielloantonio.tempo.util.DownloadUtil;
|
import com.cappielloantonio.tempo.util.DownloadUtil;
|
||||||
import com.cappielloantonio.tempo.util.MappingUtil;
|
import com.cappielloantonio.tempo.util.MappingUtil;
|
||||||
|
import com.cappielloantonio.tempo.util.NetworkUtil;
|
||||||
import com.cappielloantonio.tempo.util.Preferences;
|
import com.cappielloantonio.tempo.util.Preferences;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -30,6 +33,7 @@ public class SongBottomSheetViewModel extends AndroidViewModel {
|
|||||||
private final SongRepository songRepository;
|
private final SongRepository songRepository;
|
||||||
private final AlbumRepository albumRepository;
|
private final AlbumRepository albumRepository;
|
||||||
private final ArtistRepository artistRepository;
|
private final ArtistRepository artistRepository;
|
||||||
|
private final FavoriteRepository favoriteRepository;
|
||||||
|
|
||||||
private Child song;
|
private Child song;
|
||||||
|
|
||||||
@@ -41,6 +45,7 @@ public class SongBottomSheetViewModel extends AndroidViewModel {
|
|||||||
songRepository = new SongRepository();
|
songRepository = new SongRepository();
|
||||||
albumRepository = new AlbumRepository();
|
albumRepository = new AlbumRepository();
|
||||||
artistRepository = new ArtistRepository();
|
artistRepository = new ArtistRepository();
|
||||||
|
favoriteRepository = new FavoriteRepository();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Child getSong() {
|
public Child getSong() {
|
||||||
@@ -53,18 +58,58 @@ public class SongBottomSheetViewModel extends AndroidViewModel {
|
|||||||
|
|
||||||
public void setFavorite(Context context) {
|
public void setFavorite(Context context) {
|
||||||
if (song.getStarred() != null) {
|
if (song.getStarred() != null) {
|
||||||
songRepository.unstar(song.getId());
|
if (NetworkUtil.isOffline()) {
|
||||||
song.setStarred(null);
|
removeFavoriteOffline(song);
|
||||||
} else {
|
} else {
|
||||||
songRepository.star(song.getId());
|
removeFavoriteOnline(song);
|
||||||
song.setStarred(new Date());
|
|
||||||
|
|
||||||
if (Preferences.isStarredSyncEnabled()) {
|
|
||||||
DownloadUtil.getDownloadTracker(context).download(
|
|
||||||
MappingUtil.mapDownload(song),
|
|
||||||
new Download(song)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if (NetworkUtil.isOffline()) {
|
||||||
|
setFavoriteOffline(song);
|
||||||
|
} else {
|
||||||
|
setFavoriteOnline(context, song);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeFavoriteOffline(Child media) {
|
||||||
|
favoriteRepository.starLater(media.getId(), null, null, false);
|
||||||
|
media.setStarred(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeFavoriteOnline(Child media) {
|
||||||
|
favoriteRepository.unstar(media.getId(), null, null, new StarCallback() {
|
||||||
|
@Override
|
||||||
|
public void onError() {
|
||||||
|
// media.setStarred(new Date());
|
||||||
|
favoriteRepository.starLater(media.getId(), null, null, false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
media.setStarred(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setFavoriteOffline(Child media) {
|
||||||
|
favoriteRepository.starLater(media.getId(), null, null, true);
|
||||||
|
media.setStarred(new Date());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setFavoriteOnline(Context context, Child media) {
|
||||||
|
favoriteRepository.star(media.getId(), null, null, new StarCallback() {
|
||||||
|
@Override
|
||||||
|
public void onError() {
|
||||||
|
// media.setStarred(null);
|
||||||
|
favoriteRepository.starLater(media.getId(), null, null, true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
media.setStarred(new Date());
|
||||||
|
|
||||||
|
if (Preferences.isStarredSyncEnabled()) {
|
||||||
|
DownloadUtil.getDownloadTracker(context).download(
|
||||||
|
MappingUtil.mapDownload(media),
|
||||||
|
new Download(media)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
15
app/src/main/res/drawable/fast_scrollbar_bubble.xml
Normal file
15
app/src/main/res/drawable/fast_scrollbar_bubble.xml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
|
||||||
|
<corners
|
||||||
|
android:bottomLeftRadius="44dp"
|
||||||
|
android:bottomRightRadius="0px"
|
||||||
|
android:topLeftRadius="44dp"
|
||||||
|
android:topRightRadius="44dp" />
|
||||||
|
|
||||||
|
<solid android:color="?attr/colorPrimary" />
|
||||||
|
<size
|
||||||
|
android:width="88dp"
|
||||||
|
android:height="88dp" />
|
||||||
|
</shape>
|
||||||
17
app/src/main/res/drawable/fast_scrollbar_handle.xml
Normal file
17
app/src/main/res/drawable/fast_scrollbar_handle.xml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:state_selected="true">
|
||||||
|
<shape android:shape="rectangle">
|
||||||
|
<corners android:radius="2dp" />
|
||||||
|
<solid android:color="?attr/colorPrimary" />
|
||||||
|
<size android:width="4dp" android:height="32dp" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<shape android:shape="rectangle">
|
||||||
|
<corners android:radius="2dp" />
|
||||||
|
<solid android:color="?attr/colorPrimaryContainer" />
|
||||||
|
<size android:width="4dp" android:height="32dp" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</selector>
|
||||||
9
app/src/main/res/drawable/ic_podcast_download.xml
Normal file
9
app/src/main/res/drawable/ic_podcast_download.xml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="960"
|
||||||
|
android:viewportHeight="960">
|
||||||
|
<path
|
||||||
|
android:fillColor="@color/titleTextColor"
|
||||||
|
android:pathData="M439,878Q363,870 297.5,835.5Q232,801 184,747.5Q136,694 108.5,625Q81,556 81,479Q81,324 183.5,210.5Q286,97 440,80L440,160Q319,177 240,267.5Q161,358 161,479Q161,600 240,690.5Q319,781 439,798L439,878ZM479,680L278,478L335,421L439,525L439,280L519,280L519,525L622,422L679,480L479,680ZM519,878L519,798Q562,792 601.5,775Q641,758 675,732L733,790Q686,827 632,849.5Q578,872 519,878ZM677,226Q642,200 602.5,183Q563,166 520,160L520,80Q579,86 633,108.5Q687,131 733,168L677,226ZM789,732L733,675Q759,641 775,601.5Q791,562 797,519L879,519Q871,578 849,632.5Q827,687 789,732ZM797,439Q791,396 775,356.5Q759,317 733,283L789,226Q827,271 850,325.5Q873,380 879,439L797,439Z"/>
|
||||||
|
</vector>
|
||||||
@@ -39,8 +39,7 @@
|
|||||||
android:paddingBottom="24dp"
|
android:paddingBottom="24dp"
|
||||||
android:text="@string/album_catalogue_title_expanded"
|
android:text="@string/album_catalogue_title_expanded"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent" />
|
||||||
app:layout_constraintStart_toEndOf="parent" />
|
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/album_list_sort_image_view"
|
android:id="@+id/album_list_sort_image_view"
|
||||||
|
|||||||
@@ -47,13 +47,30 @@
|
|||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:id="@+id/index_recycler_view"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:clipToPadding="false"
|
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||||
android:paddingBottom="@dimen/global_padding_bottom"
|
|
||||||
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/index_recycler_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:paddingBottom="@dimen/global_padding_bottom"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<com.cappielloantonio.tempo.helper.recyclerview.FastScrollbar
|
||||||
|
android:id="@+id/fast_scrollbar"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginBottom="@dimen/global_padding_bottom"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
|||||||
@@ -12,35 +12,67 @@
|
|||||||
app:layout_collapseMode="pin"
|
app:layout_collapseMode="pin"
|
||||||
app:navigationIcon="@drawable/ic_arrow_back" />
|
app:navigationIcon="@drawable/ic_arrow_back" />
|
||||||
|
|
||||||
<androidx.core.widget.NestedScrollView
|
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
android:id="@+id/fragment_playlist_page_nested_scroll_view"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<LinearLayout
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
|
android:id="@+id/app_bar_layout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content">
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:id="@+id/playlist_info_sector"
|
android:id="@+id/playlist_info_sector"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:clipChildren="false"
|
android:background="?attr/colorSurface"
|
||||||
android:paddingTop="8dp">
|
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/playlist_cover_image_view"
|
android:id="@+id/playlist_cover_image_view_top_left"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:layout_marginStart="64dp"
|
android:layout_marginStart="64dp"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
|
app:layout_constraintDimensionRatio="H,1:1"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/playlist_cover_image_view_top_right"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/playlist_cover_image_view_top_right"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
android:layout_marginEnd="64dp"
|
android:layout_marginEnd="64dp"
|
||||||
app:layout_constraintDimensionRatio="H,1:1"
|
app:layout_constraintDimensionRatio="H,1:1"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toEndOf="@id/playlist_cover_image_view_top_left"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/playlist_cover_image_view_bottom_left"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_marginStart="64dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
app:layout_constraintDimensionRatio="H,1:1"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/playlist_cover_image_view_bottom_right"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/playlist_cover_image_view_top_left" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/playlist_cover_image_view_bottom_right"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_marginEnd="64dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
app:layout_constraintDimensionRatio="H,1:1"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/playlist_cover_image_view_bottom_left"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/playlist_cover_image_view_bottom_left" />
|
||||||
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/playlist_name_label"
|
android:id="@+id/playlist_name_label"
|
||||||
style="@style/LabelExtraLarge"
|
style="@style/LabelExtraLarge"
|
||||||
@@ -55,7 +87,7 @@
|
|||||||
android:textAlignment="center"
|
android:textAlignment="center"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/playlist_cover_image_view" />
|
app:layout_constraintTop_toBottomOf="@+id/playlist_cover_image_view_bottom_left" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/playlist_song_count_label"
|
android:id="@+id/playlist_song_count_label"
|
||||||
@@ -159,15 +191,16 @@
|
|||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/playlist_page_button_layout" />
|
app:layout_constraintTop_toBottomOf="@+id/playlist_page_button_layout" />
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/song_recycler_view"
|
android:id="@+id/song_recycler_view"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="match_parent"
|
||||||
android:clipToPadding="false"
|
android:clipToPadding="false"
|
||||||
android:nestedScrollingEnabled="false"
|
android:paddingTop="8dp"
|
||||||
android:paddingTop="8dp"
|
android:paddingBottom="@dimen/global_padding_bottom"
|
||||||
android:paddingBottom="@dimen/global_padding_bottom" />
|
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
|
||||||
</LinearLayout>
|
|
||||||
</androidx.core.widget.NestedScrollView>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
@@ -1,74 +1,45 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:id="@+id/layout"
|
android:id="@+id/layout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
<com.google.android.material.appbar.MaterialToolbar
|
||||||
android:id="@+id/appbar"
|
android:id="@+id/toolbar"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="@dimen/appbar_header_height">
|
android:layout_height="?attr/actionBarSize"
|
||||||
|
app:layout_collapseMode="pin"
|
||||||
|
app:navigationIcon="@drawable/ic_arrow_back" />
|
||||||
|
|
||||||
<com.google.android.material.appbar.CollapsingToolbarLayout
|
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
android:id="@+id/collapsing_toolbar"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
app:contentScrim="?attr/colorSurface"
|
|
||||||
app:expandedTitleMarginStart="@dimen/activity_margin_content"
|
|
||||||
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/podcast_channel_backdrop_image_view"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:fitsSystemWindows="true"
|
|
||||||
android:scaleType="centerCrop"
|
|
||||||
app:layout_collapseMode="parallax" />
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="@dimen/appbar_header_height"
|
|
||||||
android:layout_gravity="top"
|
|
||||||
android:background="@drawable/gradient_backdrop_background_image" />
|
|
||||||
|
|
||||||
<com.google.android.material.appbar.MaterialToolbar
|
|
||||||
android:id="@+id/anim_toolbar"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="?attr/actionBarSize"
|
|
||||||
app:layout_collapseMode="pin" />
|
|
||||||
|
|
||||||
</com.google.android.material.appbar.CollapsingToolbarLayout>
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
|
||||||
|
|
||||||
<androidx.core.widget.NestedScrollView
|
|
||||||
android:id="@+id/fragment_artist_page_nested_scroll_view"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="match_parent">
|
||||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
|
||||||
|
|
||||||
<LinearLayout
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
|
android:id="@+id/app_bar_layout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content">
|
||||||
android:clipToPadding="false"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:paddingTop="18dp"
|
|
||||||
android:paddingBottom="@dimen/global_padding_bottom">
|
|
||||||
|
|
||||||
<LinearLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:id="@+id/podcast_channel_page_bio_sector"
|
android:id="@+id/podcast_channel_page_info_sector"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:background="?attr/colorSurface"
|
||||||
android:paddingBottom="22dp">
|
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
android:id="@+id/podcast_channel_description_label"
|
||||||
style="@style/TitleLarge"
|
style="@style/TitleLarge"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:paddingStart="16dp"
|
android:paddingStart="16dp"
|
||||||
android:paddingEnd="8dp"
|
android:paddingEnd="8dp"
|
||||||
android:text="@string/podcast_channel_page_title_description_section" />
|
android:text="@string/podcast_channel_page_title_description_section"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/podcast_channel_description_text_view"
|
android:id="@+id/podcast_channel_description_text_view"
|
||||||
@@ -77,63 +48,61 @@
|
|||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:paddingStart="16dp"
|
android:paddingStart="16dp"
|
||||||
android:paddingTop="8dp"
|
android:paddingTop="8dp"
|
||||||
android:paddingEnd="16dp" />
|
android:paddingEnd="16dp"
|
||||||
</LinearLayout>
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/podcast_channel_description_label" />
|
||||||
|
|
||||||
<include
|
<View
|
||||||
android:id="@+id/podcast_channel_page_description_placeholder"
|
android:id="@+id/upper_button_divider"
|
||||||
layout="@layout/item_placehoder_biography"
|
style="@style/Divider"
|
||||||
android:visibility="gone" />
|
android:layout_marginHorizontal="16dp"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
<View
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
android:id="@+id/upper_button_divider"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
style="@style/Divider"
|
app:layout_constraintTop_toBottomOf="@+id/podcast_channel_description_text_view" />
|
||||||
android:layout_marginHorizontal="16dp" />
|
|
||||||
|
|
||||||
<!-- Label and button -->
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/podcast_channel_page_episodes_sector"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:paddingBottom="22dp">
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
android:id="@+id/podcast_episodes_section_label"
|
||||||
style="@style/TitleLarge"
|
style="@style/TitleLarge"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:paddingStart="16dp"
|
|
||||||
android:paddingTop="16dp"
|
|
||||||
android:paddingEnd="8dp"
|
|
||||||
android:text="@string/podcast_channel_page_title_episode_section" />
|
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
|
||||||
android:id="@+id/podcast_episodes_recycler_view"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
android:layout_marginBottom="8dp"
|
|
||||||
android:clipToPadding="false"
|
|
||||||
android:nestedScrollingEnabled="false"
|
|
||||||
android:paddingTop="8dp" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/podcast_episodes_availability_text_view"
|
|
||||||
style="@style/TitleMedium"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:paddingTop="8dp"
|
|
||||||
android:paddingStart="16dp"
|
android:paddingStart="16dp"
|
||||||
android:paddingEnd="8dp"
|
android:paddingEnd="8dp"
|
||||||
android:text="@string/podcast_channel_page_title_no_episode_available"
|
android:paddingBottom="12dp"
|
||||||
android:visibility="gone" />
|
android:text="@string/podcast_channel_page_title_episode_section"
|
||||||
</LinearLayout>
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/upper_button_divider" />
|
||||||
|
|
||||||
<include
|
<Button
|
||||||
android:id="@+id/podcast_channel_page_episodes_placeholder"
|
android:id="@+id/podcast_episodes_filter_image_view"
|
||||||
layout="@layout/item_placeholder_horizontal"
|
style="@style/Widget.Material3.Button.TonalButton.Icon"
|
||||||
android:visibility="gone" />
|
android:layout_width="52dp"
|
||||||
|
android:layout_height="52dp"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:insetLeft="0dp"
|
||||||
|
android:insetTop="0dp"
|
||||||
|
android:insetRight="0dp"
|
||||||
|
android:insetBottom="0dp"
|
||||||
|
app:cornerRadius="30dp"
|
||||||
|
app:icon="@drawable/ic_filter_list"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/podcast_episodes_section_label"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/podcast_episodes_section_label" />
|
||||||
|
|
||||||
</LinearLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
</androidx.core.widget.NestedScrollView>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/podcast_episodes_recycler_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:paddingBottom="@dimen/global_padding_bottom"
|
||||||
|
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
||||||
|
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
</LinearLayout>
|
||||||
@@ -20,9 +20,9 @@
|
|||||||
style="@style/LabelMedium"
|
style="@style/LabelMedium"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:maxLines="5"
|
android:maxLines="5"
|
||||||
android:layout_marginStart="12dp"
|
|
||||||
android:text="@string/label_placeholder"
|
android:text="@string/label_placeholder"
|
||||||
app:layout_constraintBottom_toTopOf="@+id/podcast_subtitle_label"
|
app:layout_constraintBottom_toTopOf="@+id/podcast_subtitle_label"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
@@ -36,9 +36,9 @@
|
|||||||
style="@style/LabelSmall"
|
style="@style/LabelSmall"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:maxLines="2"
|
android:maxLines="2"
|
||||||
android:layout_marginStart="12dp"
|
|
||||||
android:text="@string/label_placeholder"
|
android:text="@string/label_placeholder"
|
||||||
app:layout_constraintBottom_toTopOf="@id/podcast_upper_divider"
|
app:layout_constraintBottom_toTopOf="@id/podcast_upper_divider"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
@@ -100,9 +100,21 @@
|
|||||||
style="@style/Widget.Material3.Button.IconButton"
|
style="@style/Widget.Material3.Button.IconButton"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:visibility="gone"
|
||||||
app:icon="@drawable/ic_more_vert"
|
app:icon="@drawable/ic_more_vert"
|
||||||
app:layout_constraintBottom_toBottomOf="@+id/podcast_play_button"
|
app:layout_constraintBottom_toBottomOf="@+id/podcast_play_button"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="@+id/podcast_play_button" />
|
app:layout_constraintTop_toTopOf="@+id/podcast_play_button" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/podcast_download_request_button"
|
||||||
|
style="@style/Widget.Material3.Button.IconButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:icon="@drawable/ic_podcast_download"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/podcast_play_button"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/podcast_play_button" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
android:layout_height="52dp"
|
android:layout_height="52dp"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:layout_margin="2dp"
|
android:layout_margin="2dp"
|
||||||
|
android:background="?attr/colorSurfaceVariant"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|||||||
26
app/src/main/res/layout/layout_fast_scrollbar.xml
Normal file
26
app/src/main/res/layout/layout_fast_scrollbar.xml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/fastscroller_bubble"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
android:background="@drawable/fast_scrollbar_bubble"
|
||||||
|
android:gravity="center"
|
||||||
|
android:textColor="?attr/colorOnPrimary"
|
||||||
|
android:textSize="48sp"
|
||||||
|
android:visibility="visible"
|
||||||
|
tools:text="A" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/fastscroller_handle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:src="@drawable/fast_scrollbar_handle" />
|
||||||
|
</merge>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item
|
||||||
|
android:id="@+id/menu_podcast_filter_download"
|
||||||
|
android:title="@string/menu_filter_download" />
|
||||||
|
<item
|
||||||
|
android:id="@+id/menu_podcast_filter_all"
|
||||||
|
android:title="@string/menu_filter_all" />
|
||||||
|
</menu>
|
||||||
@@ -134,6 +134,7 @@
|
|||||||
<string name="podcast_channel_editor_dialog_hint_rss_url">RSS Url</string>
|
<string name="podcast_channel_editor_dialog_hint_rss_url">RSS Url</string>
|
||||||
<string name="podcast_channel_editor_dialog_title">Podcast Channel</string>
|
<string name="podcast_channel_editor_dialog_title">Podcast Channel</string>
|
||||||
<string name="podcast_release_date_duration_formatter">%1$s • %2$s</string>
|
<string name="podcast_release_date_duration_formatter">%1$s • %2$s</string>
|
||||||
|
<string name="podcast_episode_download_request_snackbar">Your request has been sent to the server</string>
|
||||||
<string name="podcast_info_empty_subtitle">Once you add a channel, you\'ll find it here</string>
|
<string name="podcast_info_empty_subtitle">Once you add a channel, you\'ll find it here</string>
|
||||||
<string name="podcast_info_empty_title">No podcasts found!</string>
|
<string name="podcast_info_empty_title">No podcasts found!</string>
|
||||||
<string name="podcast_info_empty_button">Click to hide the section\nThe effects will be visible on restart</string>
|
<string name="podcast_info_empty_button">Click to hide the section\nThe effects will be visible on restart</string>
|
||||||
@@ -265,4 +266,6 @@
|
|||||||
<string name="menu_sort_name">Name</string>
|
<string name="menu_sort_name">Name</string>
|
||||||
<string name="menu_sort_random">Random</string>
|
<string name="menu_sort_random">Random</string>
|
||||||
<string name="description_empty_title">No description available</string>
|
<string name="description_empty_title">No description available</string>
|
||||||
|
<string name="menu_filter_download">Downloaded</string>
|
||||||
|
<string name="menu_filter_all">All</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -186,6 +186,17 @@ class MediaService : MediaLibraryService() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onPlaybackStateChanged(playbackState: Int) {
|
||||||
|
super.onPlaybackStateChanged(playbackState)
|
||||||
|
if (!player.hasNextMediaItem() &&
|
||||||
|
playbackState == Player.STATE_ENDED &&
|
||||||
|
player.mediaMetadata.extras?.getString("type") == Constants.MEDIA_TYPE_MUSIC
|
||||||
|
) {
|
||||||
|
MediaManager.scrobble(player.currentMediaItem)
|
||||||
|
MediaManager.saveChronology(player.currentMediaItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onPositionDiscontinuity(
|
override fun onPositionDiscontinuity(
|
||||||
oldPosition: Player.PositionInfo,
|
oldPosition: Player.PositionInfo,
|
||||||
newPosition: Player.PositionInfo,
|
newPosition: Player.PositionInfo,
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -259,6 +259,17 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onPlaybackStateChanged(playbackState: Int) {
|
||||||
|
super.onPlaybackStateChanged(playbackState)
|
||||||
|
if (!player.hasNextMediaItem() &&
|
||||||
|
playbackState == Player.STATE_ENDED &&
|
||||||
|
player.mediaMetadata.extras?.getString("type") == Constants.MEDIA_TYPE_MUSIC
|
||||||
|
) {
|
||||||
|
MediaManager.scrobble(player.currentMediaItem)
|
||||||
|
MediaManager.saveChronology(player.currentMediaItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onPositionDiscontinuity(
|
override fun onPositionDiscontinuity(
|
||||||
oldPosition: Player.PositionInfo,
|
oldPosition: Player.PositionInfo,
|
||||||
newPosition: Player.PositionInfo,
|
newPosition: Player.PositionInfo,
|
||||||
|
|||||||
Reference in New Issue
Block a user