mirror of
https://github.com/CappielloAntonio/tempo.git
synced 2026-01-30 14:22:05 +00:00
Compare commits
77 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 | ||
|
|
fcbe4377aa | ||
|
|
84db4060e6 | ||
|
|
b73a1c532b | ||
|
|
4fe27067e9 | ||
|
|
9c6981ed19 | ||
|
|
560ac2df68 | ||
|
|
260e6e9daa | ||
|
|
38a1368c76 | ||
|
|
9bf3139bd2 | ||
|
|
d389d1d62a | ||
|
|
e60c0e312c | ||
|
|
dc13beca49 | ||
|
|
6399488819 | ||
|
|
69c1b93d28 | ||
|
|
50c240793a | ||
|
|
80f7783b7d | ||
|
|
259c8b3599 | ||
|
|
ba1295440b | ||
|
|
9e6178037c | ||
|
|
47302815c1 | ||
|
|
c81bfe1457 | ||
|
|
f413b5d498 | ||
|
|
feae1b8bdd | ||
|
|
57bb51fb8d | ||
|
|
0f1364502a | ||
|
|
a508ec0af6 | ||
|
|
a9b264004c | ||
|
|
803b61430f | ||
|
|
25367512c0 | ||
|
|
e3720f4a50 | ||
|
|
1168cc58f4 | ||
|
|
c711b387bb | ||
|
|
a3fe0de233 | ||
|
|
922153837d | ||
|
|
960dd6c76a | ||
|
|
fcb7135c3d | ||
|
|
3102bff729 | ||
|
|
992b16cc1d | ||
|
|
2046872d79 | ||
|
|
7312ae20d1 | ||
|
|
e8f2385928 | ||
|
|
204f9ff4c7 | ||
|
|
17e64f3dd0 | ||
|
|
d09e7e7981 | ||
|
|
95e9d06e47 | ||
|
|
f147c762d5 | ||
|
|
200ce6555b | ||
|
|
b5e5537691 | ||
|
|
3b80725673 | ||
|
|
d7c5be834e | ||
|
|
4a5672fc77 | ||
|
|
34fa2f456e | ||
|
|
663285611b | ||
|
|
a62cc78e18 |
6
.github/workflows/github_release.yml
vendored
6
.github/workflows/github_release.yml
vendored
@@ -34,13 +34,13 @@ jobs:
|
||||
|
||||
- name: Build APK
|
||||
id: build
|
||||
run: bash ./gradlew assembleRelease
|
||||
run: bash ./gradlew assembleTempoRelease
|
||||
|
||||
- name: Sign APK
|
||||
id: sign_apk
|
||||
uses: r0adkll/sign-android-release@v1
|
||||
with:
|
||||
releaseDirectory: app/build/outputs/apk/release
|
||||
releaseDirectory: app/build/outputs/apk/tempo/release
|
||||
signingKeyBase64: ${{ secrets.KEYSTORE_BASE64 }}
|
||||
alias: ${{ secrets.KEY_ALIAS_GITHUB }}
|
||||
keyStorePassword: ${{ secrets.KEYSTORE_PASSWORD }}
|
||||
@@ -98,7 +98,7 @@ jobs:
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ${{steps.sign_apk.outputs.signedReleaseFile}}
|
||||
asset_name: app-release.apk
|
||||
asset_name: app-tempo-release.apk
|
||||
asset_content_type: application/zip
|
||||
|
||||
# - name: Upload AAB
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
<h1 align="center"> Tempo </h1>
|
||||
<br>
|
||||
<p align="center">
|
||||
<img alt="Tempo" title="Tempo" src="mockup/svg/horizontal_logo.svg" width="250">
|
||||
</p>
|
||||
|
||||
@@ -7,11 +7,8 @@ android {
|
||||
buildToolsVersion '33.0.0'
|
||||
|
||||
defaultConfig {
|
||||
applicationId 'com.cappielloantonio.tempo'
|
||||
minSdkVersion 24
|
||||
targetSdkVersion 34
|
||||
versionCode 5
|
||||
versionName '3.3.0'
|
||||
|
||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||
|
||||
@@ -25,6 +22,24 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
flavorDimensions "default"
|
||||
|
||||
productFlavors {
|
||||
tempo {
|
||||
dimension "default"
|
||||
applicationId 'com.cappielloantonio.tempo'
|
||||
versionCode 14
|
||||
versionName '3.4.7'
|
||||
}
|
||||
|
||||
notquitemy {
|
||||
dimension "default"
|
||||
applicationId "com.cappielloantonio.notquitemy.tempo"
|
||||
versionCode 1
|
||||
versionName "1.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
shrinkResources true
|
||||
@@ -54,51 +69,34 @@ dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
|
||||
// AndroidX
|
||||
implementation 'androidx.core:core-ktx:1.10.1'
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.2.0'
|
||||
implementation 'androidx.preference:preference-ktx:1.2.0'
|
||||
implementation 'androidx.navigation:navigation-fragment-ktx:2.6.0'
|
||||
implementation 'androidx.navigation:navigation-ui-ktx:2.6.0'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.3.0'
|
||||
implementation 'androidx.room:room-runtime:2.5.1'
|
||||
implementation 'androidx.cardview:cardview:1.0.0'
|
||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.3.1'
|
||||
implementation 'androidx.room:room-runtime:2.5.2'
|
||||
implementation 'androidx.core:core-splashscreen:1.0.1'
|
||||
|
||||
// Google GMS
|
||||
implementation 'com.google.android.gms:play-services-cast-framework:21.3.0'
|
||||
|
||||
// Android Material
|
||||
implementation 'com.google.android.material:material:1.9.0'
|
||||
|
||||
// SearchBar
|
||||
implementation 'com.paulrybitskyi.persistentsearchview:persistentsearchview:1.1.4'
|
||||
implementation 'com.arthurivanets.adapster:adapster:1.0.13'
|
||||
|
||||
// Glide
|
||||
implementation 'com.github.bumptech.glide:glide:4.15.1'
|
||||
implementation 'com.github.bumptech.glide:annotations:4.15.1'
|
||||
|
||||
// Media3
|
||||
implementation 'androidx.media3:media3-session:1.0.2'
|
||||
implementation 'androidx.media3:media3-common:1.0.2'
|
||||
implementation 'androidx.media3:media3-exoplayer:1.0.2'
|
||||
implementation 'androidx.media3:media3-ui:1.0.2'
|
||||
implementation 'androidx.media3:media3-cast:1.0.2'
|
||||
implementation 'androidx.media3:media3-session:1.1.0'
|
||||
implementation 'androidx.media3:media3-common:1.1.0'
|
||||
implementation 'androidx.media3:media3-exoplayer:1.1.0'
|
||||
implementation 'androidx.media3:media3-ui:1.1.0'
|
||||
tempoImplementation 'androidx.media3:media3-cast:1.1.0'
|
||||
|
||||
annotationProcessor 'com.github.bumptech.glide:compiler:4.15.1'
|
||||
annotationProcessor 'androidx.room:room-compiler:2.5.1'
|
||||
annotationProcessor 'androidx.room:room-compiler:2.5.2'
|
||||
|
||||
// Retrofit
|
||||
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
|
||||
implementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.6'
|
||||
implementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.11'
|
||||
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
|
||||
|
||||
// Crash Report
|
||||
// debugImplementation 'com.balsikandar.android:crashreporter:1.1.0'
|
||||
|
||||
// DB debug
|
||||
// debugImplementation 'com.amitshekhar.android:debug-db:1.0.6'
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
import androidx.room.AutoMigration;
|
||||
import androidx.room.Database;
|
||||
import androidx.room.Room;
|
||||
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.dao.ChronologyDao;
|
||||
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.RecentSearchDao;
|
||||
import com.cappielloantonio.tempo.database.dao.ServerDao;
|
||||
import com.cappielloantonio.tempo.model.Chronology;
|
||||
import com.cappielloantonio.tempo.model.Download;
|
||||
import com.cappielloantonio.tempo.model.Favorite;
|
||||
import com.cappielloantonio.tempo.model.Queue;
|
||||
import com.cappielloantonio.tempo.model.RecentSearch;
|
||||
import com.cappielloantonio.tempo.model.Server;
|
||||
|
||||
@Database(
|
||||
version = 1,
|
||||
entities = {Queue.class, Server.class, RecentSearch.class, Download.class, Chronology.class}
|
||||
// autoMigrations = {@AutoMigration(from = 61, to = 62)}
|
||||
version = 2,
|
||||
entities = {Queue.class, Server.class, RecentSearch.class, Download.class, Chronology.class, Favorite.class},
|
||||
autoMigrations = {@AutoMigration(from = 1, to = 2)}
|
||||
)
|
||||
@TypeConverters({DateConverters.class})
|
||||
public abstract class AppDatabase extends RoomDatabase {
|
||||
@@ -47,4 +50,6 @@ public abstract class AppDatabase extends RoomDatabase {
|
||||
public abstract DownloadDao downloadDao();
|
||||
|
||||
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();
|
||||
}
|
||||
@@ -12,8 +12,8 @@ import java.util.List;
|
||||
|
||||
@Dao
|
||||
public interface RecentSearchDao {
|
||||
@Query("SELECT * FROM recent_search GROUP BY search ORDER BY search DESC LIMIT :limit")
|
||||
List<String> getRecent(int limit);
|
||||
@Query("SELECT * FROM recent_search ORDER BY search DESC")
|
||||
List<String> getRecent();
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
void insert(RecentSearch search);
|
||||
|
||||
@@ -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 onServerLongClick(Bundle bundle) {}
|
||||
default void onPodcastEpisodeClick(Bundle bundle) {}
|
||||
default void onPodcastEpisodeAltClick(Bundle bundle) {}
|
||||
default void onPodcastEpisodeLongClick(Bundle bundle) {}
|
||||
default void onPodcastChannelClick(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")
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.cappielloantonio.tempo.model
|
||||
|
||||
import androidx.annotation.Keep
|
||||
|
||||
@Keep
|
||||
data class ReplayGain(
|
||||
var trackGain: Float = 0f,
|
||||
var albumGain: Float = 0f,
|
||||
)
|
||||
@@ -21,8 +21,6 @@ import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
|
||||
public class AlbumRepository {
|
||||
private static final String TAG = "AlbumRepository";
|
||||
|
||||
public MutableLiveData<List<AlbumID3>> getAlbums(String type, int size, Integer fromYear, Integer toYear) {
|
||||
MutableLiveData<List<AlbumID3>> listLiveAlbums = new MutableLiveData<>(new ArrayList<>());
|
||||
|
||||
@@ -78,40 +76,6 @@ public class AlbumRepository {
|
||||
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) {
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getMediaAnnotationClient()
|
||||
|
||||
@@ -135,9 +135,6 @@ public class ArtistRepository {
|
||||
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) {
|
||||
MutableLiveData<ArtistInfo2> artistFullInfo = new MutableLiveData<>(null);
|
||||
|
||||
@@ -161,40 +158,6 @@ public class ArtistRepository {
|
||||
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) {
|
||||
App.getSubsonicClientInstance(false)
|
||||
.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,9 +24,14 @@ public class GenreRepository {
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getGenres() != null) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse() != null && response.body().getSubsonicResponse().getGenres() != null) {
|
||||
List<Genre> genreList = response.body().getSubsonicResponse().getGenres().getGenres();
|
||||
|
||||
if (genreList == null || genreList.isEmpty()) {
|
||||
genres.setValue(Collections.emptyList());
|
||||
return;
|
||||
}
|
||||
|
||||
if (random) {
|
||||
Collections.shuffle(genreList);
|
||||
}
|
||||
|
||||
@@ -133,4 +133,21 @@ public class PodcastRepository {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void downloadPodcastEpisode(String episodeId) {
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getPodcastClient()
|
||||
.downloadPodcastEpisode(episodeId)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,8 +17,12 @@ public class ScanRepository {
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull retrofit2.Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getScanStatus() != null) {
|
||||
callback.onSuccess(response.body().getSubsonicResponse().getScanStatus().isScanning(), response.body().getSubsonicResponse().getScanStatus().getCount());
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse() != null) {
|
||||
if (response.body().getSubsonicResponse().getError() != null) {
|
||||
callback.onError(new Exception(response.body().getSubsonicResponse().getError().getMessage()));
|
||||
} else if (response.body().getSubsonicResponse().getScanStatus() != null) {
|
||||
callback.onSuccess(response.body().getSubsonicResponse().getScanStatus().isScanning(), response.body().getSubsonicResponse().getScanStatus().getCount());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,8 +40,12 @@ public class ScanRepository {
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull retrofit2.Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getScanStatus() != null) {
|
||||
callback.onSuccess(response.body().getSubsonicResponse().getScanStatus().isScanning(), response.body().getSubsonicResponse().getScanStatus().getCount());
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse() != null) {
|
||||
if (response.body().getSubsonicResponse().getError() != null) {
|
||||
callback.onError(new Exception(response.body().getSubsonicResponse().getError().getMessage()));
|
||||
} else if (response.body().getSubsonicResponse().getScanStatus() != null) {
|
||||
callback.onSuccess(response.body().getSubsonicResponse().getScanStatus().isScanning(), response.body().getSubsonicResponse().getScanStatus().getCount());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.cappielloantonio.tempo.repository;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
@@ -11,6 +13,7 @@ import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
||||
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
|
||||
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||
import com.cappielloantonio.tempo.subsonic.models.SearchResult2;
|
||||
import com.cappielloantonio.tempo.subsonic.models.SearchResult3;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -24,7 +27,28 @@ import retrofit2.Response;
|
||||
public class SearchingRepository {
|
||||
private final RecentSearchDao recentSearchDao = AppDatabase.getInstance().recentSearchDao();
|
||||
|
||||
public MutableLiveData<SearchResult3> search(String query) {
|
||||
public MutableLiveData<SearchResult2> search2(String query) {
|
||||
MutableLiveData<SearchResult2> result = new MutableLiveData<>();
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getSearchingClient()
|
||||
.search3(query, 20, 20, 20)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
result.setValue(response.body().getSubsonicResponse().getSearchResult2());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public MutableLiveData<SearchResult3> search3(String query) {
|
||||
MutableLiveData<SearchResult3> result = new MutableLiveData<>();
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
@@ -78,6 +102,8 @@ public class SearchingRepository {
|
||||
LinkedHashSet<String> hashSet = new LinkedHashSet<>(newSuggestions);
|
||||
ArrayList<String> suggestionsWithoutDuplicates = new ArrayList<>(hashSet);
|
||||
|
||||
Log.d("suggestionsWithoutDuplicates", suggestionsWithoutDuplicates.toString());
|
||||
|
||||
suggestions.setValue(suggestionsWithoutDuplicates);
|
||||
}
|
||||
}
|
||||
@@ -103,10 +129,10 @@ public class SearchingRepository {
|
||||
thread.start();
|
||||
}
|
||||
|
||||
public List<String> getRecentSearchSuggestion(int limit) {
|
||||
public List<String> getRecentSearchSuggestion() {
|
||||
List<String> recent = new ArrayList<>();
|
||||
|
||||
RecentThreadSafe suggestionsThread = new RecentThreadSafe(recentSearchDao, limit);
|
||||
RecentThreadSafe suggestionsThread = new RecentThreadSafe(recentSearchDao);
|
||||
Thread thread = new Thread(suggestionsThread);
|
||||
thread.start();
|
||||
|
||||
@@ -152,17 +178,15 @@ public class SearchingRepository {
|
||||
|
||||
private static class RecentThreadSafe implements Runnable {
|
||||
private final RecentSearchDao recentSearchDao;
|
||||
private final int limit;
|
||||
private List<String> recent = new ArrayList<>();
|
||||
|
||||
public RecentThreadSafe(RecentSearchDao recentSearchDao, int limit) {
|
||||
public RecentThreadSafe(RecentSearchDao recentSearchDao) {
|
||||
this.recentSearchDao = recentSearchDao;
|
||||
this.limit = limit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
recent = recentSearchDao.getRecent(limit);
|
||||
recent = recentSearchDao.getRecent();
|
||||
}
|
||||
|
||||
public List<String> getRecent() {
|
||||
|
||||
@@ -6,6 +6,8 @@ import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
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.models.Child;
|
||||
|
||||
@@ -86,7 +88,7 @@ public class SongRepository {
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
List<Child> songs = new ArrayList<>();
|
||||
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getRandomSongs() != null) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getRandomSongs() != null && response.body().getSubsonicResponse().getRandomSongs().getSongs() != null) {
|
||||
songs.addAll(response.body().getSubsonicResponse().getRandomSongs().getSongs());
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getMediaAnnotationClient()
|
||||
@@ -173,8 +141,6 @@ public class SongRepository {
|
||||
public MutableLiveData<List<Child>> getSongsByGenre(String id, int page) {
|
||||
MutableLiveData<List<Child>> songsByGenre = new MutableLiveData<>();
|
||||
|
||||
Log.d(TAG, "onScrolled PAGE: " + page);
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getAlbumSongListClient()
|
||||
.getSongsByGenre(id, 100, 100 * page)
|
||||
|
||||
@@ -22,7 +22,7 @@ public class SystemRepository {
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull retrofit2.Response<ApiResponse> response) {
|
||||
if (response.body() != null) {
|
||||
if (response.body().getSubsonicResponse().getStatus().equals(ResponseStatus.FAILED)) {
|
||||
callback.onError(new Exception(response.body().getSubsonicResponse().getError().getCode().getValue() + " - " + response.body().getSubsonicResponse().getError().getMessage()));
|
||||
callback.onError(new Exception(response.body().getSubsonicResponse().getError().getCode() + " - " + response.body().getSubsonicResponse().getError().getMessage()));
|
||||
} else if (response.body().getSubsonicResponse().getStatus().equals(ResponseStatus.OK)) {
|
||||
String password = response.raw().request().url().queryParameter("p");
|
||||
String token = response.raw().request().url().queryParameter("t");
|
||||
|
||||
@@ -16,6 +16,7 @@ class RetrofitClient(subsonic: Subsonic) {
|
||||
init {
|
||||
retrofit = Retrofit.Builder()
|
||||
.baseUrl(subsonic.url)
|
||||
.addConverterFactory(GsonConverterFactory.create(GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ss").create()))
|
||||
.addConverterFactory(GsonConverterFactory.create(GsonBuilder().setLenient().create()))
|
||||
.client(getOkHttpClient())
|
||||
.build()
|
||||
|
||||
@@ -48,4 +48,9 @@ public class PodcastClient {
|
||||
Log.d(TAG, "deletePodcastEpisode()");
|
||||
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")
|
||||
Call<ApiResponse> deletePodcastEpisode(@QueryMap Map<String, String> params, @Query("id") String id);
|
||||
|
||||
@GET("downloadPodcastEpisode")
|
||||
Call<ApiResponse> downloadPodcastEpisode(@QueryMap Map<String, String> params, @Query("id") String id);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,6 @@ import androidx.annotation.Keep
|
||||
|
||||
@Keep
|
||||
class Error {
|
||||
var code: ErrorCode? = null
|
||||
var code: Int? = null
|
||||
var message: String? = null
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package com.cappielloantonio.tempo.subsonic.models
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.annotation.Keep
|
||||
import androidx.room.ColumnInfo
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import java.util.*
|
||||
@@ -29,11 +28,9 @@ class PodcastEpisode : Parcelable {
|
||||
var transcodedContentType: String? = null
|
||||
var transcodedSuffix: String? = null
|
||||
var duration: Int? = null
|
||||
@ColumnInfo("bitrate")
|
||||
@SerializedName("bitRate")
|
||||
var bitrate: Int? = null
|
||||
var path: String? = null
|
||||
@ColumnInfo(name = "is_video")
|
||||
@SerializedName("isVideo")
|
||||
var isVideo: Boolean = false
|
||||
var userRating: Int? = null
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.cappielloantonio.tempo.ui.activity;
|
||||
import android.content.Context;
|
||||
import android.content.IntentFilter;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.view.View;
|
||||
@@ -141,6 +142,10 @@ public class MainActivity extends BaseActivity {
|
||||
handler.postDelayed(runnable, 100);
|
||||
}
|
||||
|
||||
public void expandBottomSheet() {
|
||||
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
|
||||
}
|
||||
|
||||
public void setBottomSheetDraggableState(Boolean isDraggable) {
|
||||
bottomSheetBehavior.setDraggable(isDraggable);
|
||||
}
|
||||
@@ -247,6 +252,7 @@ public class MainActivity extends BaseActivity {
|
||||
}
|
||||
|
||||
private void goToLogin() {
|
||||
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
|
||||
setBottomNavigationBarVisibility(false);
|
||||
setBottomSheetVisibility(false);
|
||||
|
||||
@@ -333,7 +339,9 @@ public class MainActivity extends BaseActivity {
|
||||
private void checkConnectionType() {
|
||||
if (Preferences.isWifiOnly()) {
|
||||
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();
|
||||
dialog.show(getSupportFragmentManager(), null);
|
||||
}
|
||||
|
||||
@@ -2,15 +2,12 @@ package com.cappielloantonio.tempo.ui.activity.base;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.PowerManager;
|
||||
import android.provider.Settings;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
@@ -19,11 +16,11 @@ import androidx.media3.exoplayer.offline.DownloadService;
|
||||
import androidx.media3.session.MediaBrowser;
|
||||
import androidx.media3.session.SessionToken;
|
||||
|
||||
import com.cappielloantonio.tempo.R;
|
||||
import com.cappielloantonio.tempo.service.DownloaderService;
|
||||
import com.cappielloantonio.tempo.service.MediaService;
|
||||
import com.cappielloantonio.tempo.util.UIUtil;
|
||||
import com.google.android.gms.cast.framework.CastContext;
|
||||
import com.cappielloantonio.tempo.ui.dialog.BatteryOptimizationDialog;
|
||||
import com.cappielloantonio.tempo.util.Flavors;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
import com.google.android.material.elevation.SurfaceColors;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
|
||||
@@ -36,8 +33,10 @@ public class BaseActivity extends AppCompatActivity {
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
initializeCastContext();
|
||||
Flavors.initializeCastContext(this);
|
||||
initializeDownloader();
|
||||
checkBatteryOptimization();
|
||||
checkPermission();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -47,13 +46,6 @@ public class BaseActivity extends AppCompatActivity {
|
||||
initializeBrowser();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
checkBatteryOptimization();
|
||||
checkPermission();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
releaseBrowser();
|
||||
@@ -61,7 +53,7 @@ public class BaseActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
private void checkBatteryOptimization() {
|
||||
if (detectBatteryOptimization()) {
|
||||
if (detectBatteryOptimization() && Preferences.askForOptimization()) {
|
||||
showBatteryOptimizationDialog();
|
||||
}
|
||||
}
|
||||
@@ -81,19 +73,8 @@ public class BaseActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
private void showBatteryOptimizationDialog() {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setMessage(R.string.activity_battery_optimizations_summary)
|
||||
.setTitle(R.string.activity_battery_optimizations_title)
|
||||
.setNegativeButton(R.string.activity_negative_button, null)
|
||||
.setPositiveButton(R.string.activity_neutral_button, (dialog, id) -> openPowerSettings())
|
||||
.show();
|
||||
}
|
||||
|
||||
private void openPowerSettings() {
|
||||
Intent intent = new Intent();
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
intent.setAction(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS);
|
||||
startActivity(intent);
|
||||
BatteryOptimizationDialog dialog = new BatteryOptimizationDialog();
|
||||
dialog.show(getSupportFragmentManager(), null);
|
||||
}
|
||||
|
||||
private void initializeBrowser() {
|
||||
@@ -116,10 +97,6 @@ public class BaseActivity extends AppCompatActivity {
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeCastContext() {
|
||||
if (UIUtil.isCastApiAvailable(this)) CastContext.getSharedInstance(this);
|
||||
}
|
||||
|
||||
private void setNavigationBarColor() {
|
||||
getWindow().setNavigationBarColor(SurfaceColors.getColorForElevation(this, 10));
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ public class MusicDirectoryAdapter extends RecyclerView.Adapter<MusicDirectoryAd
|
||||
Bundle bundle = new Bundle();
|
||||
|
||||
if (children.get(getBindingAdapterPosition()).isDir()) {
|
||||
bundle.putParcelable(Constants.MUSIC_DIRECTORY_OBJECT, children.get(getBindingAdapterPosition()));
|
||||
bundle.putString(Constants.MUSIC_DIRECTORY_ID, children.get(getBindingAdapterPosition()).getId());
|
||||
click.onMusicDirectoryClick(bundle);
|
||||
} else {
|
||||
bundle.putParcelableArrayList(Constants.TRACKS_OBJECT, new ArrayList<>(children));
|
||||
|
||||
@@ -9,16 +9,17 @@ import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
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.subsonic.models.Artist;
|
||||
import com.cappielloantonio.tempo.util.Constants;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
@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 List<Artist> artists;
|
||||
@@ -41,10 +42,10 @@ public class MusicIndexAdapter extends RecyclerView.Adapter<MusicIndexAdapter.Vi
|
||||
|
||||
holder.item.musicIndexTitleTextView.setText(artist.getName());
|
||||
|
||||
CustomGlideRequest.Builder
|
||||
/* CustomGlideRequest.Builder
|
||||
.from(holder.itemView.getContext(), artist.getName())
|
||||
.build()
|
||||
.into(holder.item.musicIndexCoverImageView);
|
||||
.into(holder.item.musicIndexCoverImageView); */
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -57,6 +58,11 @@ public class MusicIndexAdapter extends RecyclerView.Adapter<MusicIndexAdapter.Vi
|
||||
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 {
|
||||
ItemLibraryMusicIndexBinding item;
|
||||
|
||||
@@ -73,7 +79,7 @@ public class MusicIndexAdapter extends RecyclerView.Adapter<MusicIndexAdapter.Vi
|
||||
|
||||
public void onClick() {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelable(Constants.MUSIC_INDEX_OBJECT, artists.get(getBindingAdapterPosition()));
|
||||
bundle.putString(Constants.MUSIC_DIRECTORY_ID, artists.get(getBindingAdapterPosition()).getId());
|
||||
click.onMusicIndexClick(bundle);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,14 +128,14 @@ public class PodcastChannelCatalogueAdapter extends RecyclerView.Adapter<Podcast
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelable(Constants.PODCAST_CHANNEL_OBJECT, podcastChannels.get(getBindingAdapterPosition()));
|
||||
|
||||
click.onAlbumClick(bundle);
|
||||
click.onPodcastChannelClick(bundle);
|
||||
}
|
||||
|
||||
private boolean onLongClick() {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelable(Constants.PODCAST_CHANNEL_OBJECT, podcastChannels.get(getBindingAdapterPosition()));
|
||||
|
||||
click.onAlbumLongClick(bundle);
|
||||
click.onPodcastChannelLongClick(bundle);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.cappielloantonio.tempo.ui.adapter;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -18,11 +19,14 @@ import com.cappielloantonio.tempo.util.MusicUtil;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class PodcastEpisodeAdapter extends RecyclerView.Adapter<PodcastEpisodeAdapter.ViewHolder> {
|
||||
private final ClickCallback click;
|
||||
|
||||
private List<PodcastEpisode> podcastEpisodes;
|
||||
private List<PodcastEpisode> podcastEpisodesFull;
|
||||
|
||||
public PodcastEpisodeAdapter(ClickCallback click) {
|
||||
this.click = click;
|
||||
@@ -50,6 +54,10 @@ public class PodcastEpisodeAdapter extends RecyclerView.Adapter<PodcastEpisodeAd
|
||||
.from(holder.itemView.getContext(), podcastEpisode.getCoverArtId())
|
||||
.build()
|
||||
.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
|
||||
@@ -58,7 +66,8 @@ public class PodcastEpisodeAdapter extends RecyclerView.Adapter<PodcastEpisodeAd
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -85,22 +94,57 @@ public class PodcastEpisodeAdapter extends RecyclerView.Adapter<PodcastEpisodeAd
|
||||
|
||||
item.podcastPlayButton.setOnClickListener(v -> onClick());
|
||||
item.podcastMoreButton.setOnClickListener(v -> openMore());
|
||||
item.podcastDownloadRequestButton.setOnClickListener(v -> requestDownload());
|
||||
}
|
||||
|
||||
public void onClick() {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelable(Constants.PODCAST_OBJECT, podcastEpisodes.get(getBindingAdapterPosition()));
|
||||
PodcastEpisode podcastEpisode = 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() {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelable(Constants.PODCAST_OBJECT, podcastEpisodes.get(getBindingAdapterPosition()));
|
||||
PodcastEpisode podcastEpisode = 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();
|
||||
}
|
||||
}
|
||||
@@ -76,7 +76,7 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAd
|
||||
}
|
||||
|
||||
public void setItems(List<Child> songs) {
|
||||
this.songs = songs;
|
||||
this.songs = songs != null ? songs : Collections.emptyList();
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.cappielloantonio.tempo.ui.dialog;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.OptIn;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
|
||||
import com.cappielloantonio.tempo.R;
|
||||
import com.cappielloantonio.tempo.databinding.DialogBatteryOptimizationBinding;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
|
||||
@OptIn(markerClass = UnstableApi.class)
|
||||
public class BatteryOptimizationDialog extends DialogFragment {
|
||||
private static final String TAG = "BatteryOptimizationDialog";
|
||||
|
||||
private DialogBatteryOptimizationBinding bind;
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
bind = DialogBatteryOptimizationBinding.inflate(getLayoutInflater());
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
|
||||
builder.setView(bind.getRoot())
|
||||
.setTitle(R.string.activity_battery_optimizations_title)
|
||||
.setNeutralButton(R.string.battery_optimization_neutral_button, (dialog, id) -> Preferences.dontAskForOptimization())
|
||||
.setNegativeButton(R.string.battery_optimization_negative_button, null)
|
||||
.setPositiveButton(R.string.battery_optimization_positive_button, (dialog, id) -> openPowerSettings());
|
||||
|
||||
AlertDialog popup = builder.create();
|
||||
|
||||
popup.setCancelable(false);
|
||||
popup.setCanceledOnTouchOutside(false);
|
||||
|
||||
return popup;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
bind = null;
|
||||
}
|
||||
|
||||
private void openPowerSettings() {
|
||||
Intent intent = new Intent();
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
intent.setAction(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS);
|
||||
startActivity(intent);
|
||||
}
|
||||
}
|
||||
@@ -116,6 +116,11 @@ public class ServerSignupDialog extends DialogFragment {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!server.matches("^https?://(.*)")) {
|
||||
bind.serverTextView.setError(getString(R.string.error_server_prefix));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.session.MediaBrowser;
|
||||
import androidx.media3.session.SessionToken;
|
||||
import androidx.navigation.Navigation;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
|
||||
import com.cappielloantonio.tempo.R;
|
||||
@@ -20,17 +21,12 @@ import com.cappielloantonio.tempo.databinding.FragmentDirectoryBinding;
|
||||
import com.cappielloantonio.tempo.interfaces.ClickCallback;
|
||||
import com.cappielloantonio.tempo.service.MediaManager;
|
||||
import com.cappielloantonio.tempo.service.MediaService;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Artist;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
||||
import com.cappielloantonio.tempo.ui.adapter.MusicDirectoryAdapter;
|
||||
import com.cappielloantonio.tempo.util.Constants;
|
||||
import com.cappielloantonio.tempo.viewmodel.DirectoryViewModel;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Objects;
|
||||
|
||||
@UnstableApi
|
||||
public class DirectoryFragment extends Fragment implements ClickCallback {
|
||||
private static final String TAG = "DirectoryFragment";
|
||||
@@ -52,9 +48,7 @@ public class DirectoryFragment extends Fragment implements ClickCallback {
|
||||
directoryViewModel = new ViewModelProvider(requireActivity()).get(DirectoryViewModel.class);
|
||||
|
||||
initAppBar();
|
||||
initButtons();
|
||||
initDirectoryListView();
|
||||
init();
|
||||
|
||||
return view;
|
||||
}
|
||||
@@ -77,17 +71,6 @@ public class DirectoryFragment extends Fragment implements ClickCallback {
|
||||
bind = null;
|
||||
}
|
||||
|
||||
private void init() {
|
||||
Artist artist = getArguments().getParcelable(Constants.MUSIC_INDEX_OBJECT);
|
||||
|
||||
if (artist != null) {
|
||||
directoryViewModel.setMusicDirectoryId(artist.getId());
|
||||
directoryViewModel.setMusicDirectoryName(artist.getName());
|
||||
}
|
||||
|
||||
directoryViewModel.loadMusicDirectory(getViewLifecycleOwner());
|
||||
}
|
||||
|
||||
private void initAppBar() {
|
||||
activity.setSupportActionBar(bind.toolbar);
|
||||
|
||||
@@ -96,39 +79,10 @@ public class DirectoryFragment extends Fragment implements ClickCallback {
|
||||
activity.getSupportActionBar().setDisplayShowHomeEnabled(true);
|
||||
}
|
||||
|
||||
if (bind != null)
|
||||
if (bind != null) {
|
||||
bind.toolbar.setNavigationOnClickListener(v -> activity.navController.navigateUp());
|
||||
|
||||
if (bind != null)
|
||||
bind.appBarLayout.addOnOffsetChangedListener((appBarLayout, verticalOffset) -> {
|
||||
if ((bind.directoryInfoSector.getHeight() + verticalOffset) < (2 * ViewCompat.getMinimumHeight(bind.toolbar))) {
|
||||
directoryViewModel.getDirectory().observe(getViewLifecycleOwner(), directory -> {
|
||||
if (directory != null) {
|
||||
bind.toolbar.setTitle(directory.getName());
|
||||
}
|
||||
});
|
||||
} else {
|
||||
bind.toolbar.setTitle(R.string.empty_string);
|
||||
}
|
||||
});
|
||||
|
||||
directoryViewModel.getDirectory().observe(getViewLifecycleOwner(), directory -> {
|
||||
if (directory != null) {
|
||||
bind.directoryTitleLabel.setText(directory.getName());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void initButtons() {
|
||||
directoryViewModel.getDirectory().observe(getViewLifecycleOwner(), directory -> {
|
||||
if (directory != null && directory.getParentId() != null && !Objects.equals(directory.getParentId(), "-1")) {
|
||||
bind.directoryBackImageView.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
bind.directoryBackImageView.setVisibility(View.GONE);
|
||||
}
|
||||
});
|
||||
|
||||
bind.directoryBackImageView.setOnClickListener(v -> directoryViewModel.goBack());
|
||||
bind.directoryBackImageView.setOnClickListener(v -> activity.navController.navigateUp());
|
||||
}
|
||||
}
|
||||
|
||||
private void initDirectoryListView() {
|
||||
@@ -137,13 +91,18 @@ public class DirectoryFragment extends Fragment implements ClickCallback {
|
||||
|
||||
musicDirectoryAdapter = new MusicDirectoryAdapter(this);
|
||||
bind.directoryRecyclerView.setAdapter(musicDirectoryAdapter);
|
||||
directoryViewModel.loadMusicDirectory(getArguments().getString(Constants.MUSIC_DIRECTORY_ID)).observe(getViewLifecycleOwner(), directory -> {
|
||||
bind.appBarLayout.addOnOffsetChangedListener((appBarLayout, verticalOffset) -> {
|
||||
if ((bind.directoryInfoSector.getHeight() + verticalOffset) < (2 * ViewCompat.getMinimumHeight(bind.toolbar))) {
|
||||
bind.toolbar.setTitle(directory.getName());
|
||||
} else {
|
||||
bind.toolbar.setTitle(R.string.empty_string);
|
||||
}
|
||||
});
|
||||
|
||||
directoryViewModel.getDirectory().observe(getViewLifecycleOwner(), directory -> {
|
||||
if (directory != null) {
|
||||
musicDirectoryAdapter.setItems(directory.getChildren());
|
||||
} else {
|
||||
musicDirectoryAdapter.setItems(Collections.emptyList());
|
||||
}
|
||||
bind.directoryTitleLabel.setText(directory.getName());
|
||||
|
||||
musicDirectoryAdapter.setItems(directory.getChildren());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -162,11 +121,6 @@ public class DirectoryFragment extends Fragment implements ClickCallback {
|
||||
|
||||
@Override
|
||||
public void onMusicDirectoryClick(Bundle bundle) {
|
||||
Child child = bundle.getParcelable(Constants.MUSIC_DIRECTORY_OBJECT);
|
||||
|
||||
if (child != null) {
|
||||
directoryViewModel.setMusicDirectoryId(child.getId());
|
||||
directoryViewModel.setMusicDirectoryName(child.getTitle());
|
||||
}
|
||||
Navigation.findNavController(requireView()).navigate(R.id.directoryFragment, bundle);
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,6 @@ package com.cappielloantonio.tempo.ui.fragment;
|
||||
import android.content.ComponentName;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
@@ -28,7 +25,7 @@ import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
||||
import com.cappielloantonio.tempo.ui.adapter.DownloadHorizontalAdapter;
|
||||
import com.cappielloantonio.tempo.util.Constants;
|
||||
import com.cappielloantonio.tempo.viewmodel.DownloadViewModel;
|
||||
import com.google.android.gms.cast.framework.CastButtonFactory;
|
||||
import com.google.android.material.appbar.MaterialToolbar;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
|
||||
import java.util.Objects;
|
||||
@@ -43,18 +40,7 @@ public class DownloadFragment extends Fragment implements ClickCallback {
|
||||
|
||||
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
inflater.inflate(R.menu.main_page_menu, menu);
|
||||
CastButtonFactory.setUpMediaRouteButton(requireContext(), menu, R.id.media_route_menu_item);
|
||||
}
|
||||
private MaterialToolbar materialToolbar;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
@@ -97,22 +83,11 @@ public class DownloadFragment extends Fragment implements ClickCallback {
|
||||
bind = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||
if (item.getItemId() == R.id.action_search) {
|
||||
activity.navController.navigate(R.id.action_downloadFragment_to_searchFragment);
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.action_settings) {
|
||||
activity.navController.navigate(R.id.action_downloadFragment_to_settingsFragment);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void initAppBar() {
|
||||
activity.setSupportActionBar(bind.toolbar);
|
||||
Objects.requireNonNull(bind.toolbar.getOverflowIcon()).setTint(requireContext().getResources().getColor(R.color.titleTextColor, null));
|
||||
materialToolbar = bind.getRoot().findViewById(R.id.toolbar);
|
||||
|
||||
activity.setSupportActionBar(materialToolbar);
|
||||
Objects.requireNonNull(materialToolbar.getOverflowIcon()).setTint(requireContext().getResources().getColor(R.color.titleTextColor, null));
|
||||
}
|
||||
|
||||
private void initDownloadedSongView() {
|
||||
|
||||
@@ -2,9 +2,6 @@ package com.cappielloantonio.tempo.ui.fragment;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
@@ -18,7 +15,9 @@ import com.cappielloantonio.tempo.databinding.FragmentHomeBinding;
|
||||
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
||||
import com.cappielloantonio.tempo.ui.fragment.pager.HomePager;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
import com.google.android.gms.cast.framework.CastButtonFactory;
|
||||
import com.google.android.material.appbar.AppBarLayout;
|
||||
import com.google.android.material.appbar.MaterialToolbar;
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
import com.google.android.material.tabs.TabLayoutMediator;
|
||||
|
||||
import java.util.Objects;
|
||||
@@ -30,18 +29,9 @@ public class HomeFragment extends Fragment {
|
||||
private FragmentHomeBinding bind;
|
||||
private MainActivity activity;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
inflater.inflate(R.menu.main_page_menu, menu);
|
||||
CastButtonFactory.setUpMediaRouteButton(requireContext(), menu, R.id.media_route_menu_item);
|
||||
}
|
||||
private MaterialToolbar materialToolbar;
|
||||
private AppBarLayout appBarLayout;
|
||||
private TabLayout tabLayout;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
@@ -73,22 +63,18 @@ public class HomeFragment extends Fragment {
|
||||
bind = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||
if (item.getItemId() == R.id.action_search) {
|
||||
activity.navController.navigate(R.id.action_homeFragment_to_searchFragment);
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.action_settings) {
|
||||
activity.navController.navigate(R.id.action_homeFragment_to_settingsFragment);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void initAppBar() {
|
||||
activity.setSupportActionBar(bind.toolbar);
|
||||
Objects.requireNonNull(bind.toolbar.getOverflowIcon()).setTint(requireContext().getResources().getColor(R.color.titleTextColor, null));
|
||||
appBarLayout = bind.getRoot().findViewById(R.id.toolbar_fragment);
|
||||
materialToolbar = bind.getRoot().findViewById(R.id.toolbar);
|
||||
|
||||
activity.setSupportActionBar(materialToolbar);
|
||||
Objects.requireNonNull(materialToolbar.getOverflowIcon()).setTint(requireContext().getResources().getColor(R.color.titleTextColor, null));
|
||||
|
||||
tabLayout = new TabLayout(requireContext());
|
||||
tabLayout.setTabGravity(TabLayout.GRAVITY_FILL);
|
||||
tabLayout.setTabMode(TabLayout.MODE_FIXED);
|
||||
|
||||
appBarLayout.addView(tabLayout);
|
||||
}
|
||||
|
||||
private void initHomePager() {
|
||||
@@ -106,13 +92,13 @@ public class HomeFragment extends Fragment {
|
||||
bind.homeViewPager.setOffscreenPageLimit(3);
|
||||
bind.homeViewPager.setUserInputEnabled(false);
|
||||
|
||||
new TabLayoutMediator(bind.homeTabLayout, bind.homeViewPager,
|
||||
new TabLayoutMediator(tabLayout, bind.homeViewPager,
|
||||
(tab, position) -> {
|
||||
tab.setText(pager.getPageTitle(position));
|
||||
// tab.setIcon(pager.getPageIcon(position));
|
||||
}
|
||||
).attach();
|
||||
|
||||
bind.homeTabLayout.setVisibility(Preferences.isPodcastSectionVisible() || Preferences.isRadioSectionVisible() ? View.VISIBLE : View.GONE);
|
||||
tabLayout.setVisibility(Preferences.isPodcastSectionVisible() || Preferences.isRadioSectionVisible() ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -138,6 +139,15 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||
return true;
|
||||
});
|
||||
|
||||
bind.discoveryTextViewClickable.setOnClickListener(v -> {
|
||||
homeViewModel.getRandomShuffleSample().observe(getViewLifecycleOwner(), songs -> {
|
||||
if (songs.size() > 0) {
|
||||
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
|
||||
activity.setBottomSheetInPeek(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
bind.similarTracksTextViewRefreshable.setOnLongClickListener(v -> {
|
||||
homeViewModel.refreshSimilarSongSample(getViewLifecycleOwner());
|
||||
return true;
|
||||
|
||||
@@ -96,6 +96,9 @@ public class IndexFragment extends Fragment implements ClickCallback {
|
||||
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
|
||||
|
||||
@@ -2,9 +2,6 @@ package com.cappielloantonio.tempo.ui.fragment;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
@@ -29,8 +26,9 @@ import com.cappielloantonio.tempo.ui.adapter.MusicFolderAdapter;
|
||||
import com.cappielloantonio.tempo.ui.adapter.PlaylistHorizontalAdapter;
|
||||
import com.cappielloantonio.tempo.ui.dialog.PlaylistEditorDialog;
|
||||
import com.cappielloantonio.tempo.util.Constants;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
import com.cappielloantonio.tempo.viewmodel.LibraryViewModel;
|
||||
import com.google.android.gms.cast.framework.CastButtonFactory;
|
||||
import com.google.android.material.appbar.MaterialToolbar;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@@ -46,21 +44,9 @@ public class LibraryFragment extends Fragment implements ClickCallback {
|
||||
private AlbumAdapter albumAdapter;
|
||||
private ArtistAdapter artistAdapter;
|
||||
private GenreAdapter genreAdapter;
|
||||
|
||||
private PlaylistHorizontalAdapter playlistHorizontalAdapter;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
inflater.inflate(R.menu.main_page_menu, menu);
|
||||
CastButtonFactory.setUpMediaRouteButton(requireContext(), menu, R.id.media_route_menu_item);
|
||||
}
|
||||
private MaterialToolbar materialToolbar;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
@@ -100,19 +86,6 @@ public class LibraryFragment extends Fragment implements ClickCallback {
|
||||
bind = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||
if (item.getItemId() == R.id.action_search) {
|
||||
activity.navController.navigate(R.id.action_libraryFragment_to_searchFragment);
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.action_settings) {
|
||||
activity.navController.navigate(R.id.action_libraryFragment_to_settingsFragment);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void init() {
|
||||
bind.albumCatalogueTextViewClickable.setOnClickListener(v -> activity.navController.navigate(R.id.action_libraryFragment_to_albumCatalogueFragment));
|
||||
bind.artistCatalogueTextViewClickable.setOnClickListener(v -> activity.navController.navigate(R.id.action_libraryFragment_to_artistCatalogueFragment));
|
||||
@@ -142,11 +115,18 @@ public class LibraryFragment extends Fragment implements ClickCallback {
|
||||
}
|
||||
|
||||
private void initAppBar() {
|
||||
activity.setSupportActionBar(bind.toolbar);
|
||||
Objects.requireNonNull(bind.toolbar.getOverflowIcon()).setTint(requireContext().getResources().getColor(R.color.titleTextColor, null));
|
||||
materialToolbar = bind.getRoot().findViewById(R.id.toolbar);
|
||||
|
||||
activity.setSupportActionBar(materialToolbar);
|
||||
Objects.requireNonNull(materialToolbar.getOverflowIcon()).setTint(requireContext().getResources().getColor(R.color.titleTextColor, null));
|
||||
}
|
||||
|
||||
private void initMusicFolderView() {
|
||||
if (!Preferences.isMusicDirectorySectionVisible()) {
|
||||
bind.libraryMusicFolderSector.setVisibility(View.GONE);
|
||||
return;
|
||||
}
|
||||
|
||||
bind.musicFolderRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
|
||||
bind.musicFolderRecyclerView.setHasFixedSize(true);
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
||||
import com.cappielloantonio.tempo.service.MediaManager;
|
||||
import com.cappielloantonio.tempo.service.MediaService;
|
||||
import com.cappielloantonio.tempo.subsonic.models.PlayQueue;
|
||||
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
||||
import com.cappielloantonio.tempo.ui.fragment.pager.PlayerControllerVerticalPager;
|
||||
import com.cappielloantonio.tempo.util.Constants;
|
||||
import com.cappielloantonio.tempo.util.MusicUtil;
|
||||
@@ -57,6 +58,7 @@ public class PlayerBottomSheetFragment extends Fragment {
|
||||
playerBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(PlayerBottomSheetViewModel.class);
|
||||
|
||||
customizeBottomSheetBackground();
|
||||
customizeBottomSheetAction();
|
||||
initViewPager();
|
||||
setHeaderBookmarksButton();
|
||||
|
||||
@@ -87,6 +89,10 @@ public class PlayerBottomSheetFragment extends Fragment {
|
||||
bind.playerHeaderLayout.getRoot().setBackgroundColor(SurfaceColors.getColorForElevation(requireContext(), 2));
|
||||
}
|
||||
|
||||
private void customizeBottomSheetAction() {
|
||||
bind.playerHeaderLayout.getRoot().setOnClickListener(view -> ((MainActivity) requireActivity()).expandBottomSheet());
|
||||
}
|
||||
|
||||
private void initViewPager() {
|
||||
bind.playerBodyLayout.playerBodyBottomSheetViewPager.setOrientation(ViewPager2.ORIENTATION_VERTICAL);
|
||||
bind.playerBodyLayout.playerBodyBottomSheetViewPager.setAdapter(new PlayerControllerVerticalPager(this));
|
||||
|
||||
@@ -8,6 +8,7 @@ import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import android.widget.ToggleButton;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -51,6 +52,7 @@ public class PlayerControllerFragment extends Fragment {
|
||||
private Chip playerMediaExtension;
|
||||
private TextView playerMediaBitrate;
|
||||
private ImageView playerMediaTranscodingIcon;
|
||||
private ImageView playerMediaTranscodingPriorityIcon;
|
||||
private Chip playerMediaTranscodedExtension;
|
||||
private TextView playerMediaTranscodedBitrate;
|
||||
|
||||
@@ -71,6 +73,7 @@ public class PlayerControllerFragment extends Fragment {
|
||||
initCoverLyricsSlideView();
|
||||
initMediaListenable();
|
||||
initArtistLabelButton();
|
||||
initTranscodingInfo();
|
||||
|
||||
return view;
|
||||
}
|
||||
@@ -104,6 +107,7 @@ public class PlayerControllerFragment extends Fragment {
|
||||
playerMediaExtension = bind.getRoot().findViewById(R.id.player_media_extension);
|
||||
playerMediaBitrate = bind.getRoot().findViewById(R.id.player_media_bitrate);
|
||||
playerMediaTranscodingIcon = bind.getRoot().findViewById(R.id.player_media_transcoding_audio);
|
||||
playerMediaTranscodingPriorityIcon = bind.getRoot().findViewById(R.id.player_media_server_transcode_priority);
|
||||
playerMediaTranscodedExtension = bind.getRoot().findViewById(R.id.player_media_transcoded_extension);
|
||||
playerMediaTranscodedBitrate = bind.getRoot().findViewById(R.id.player_media_transcoded_bitrate);
|
||||
}
|
||||
@@ -166,7 +170,6 @@ public class PlayerControllerFragment extends Fragment {
|
||||
playerMediaBitrate.setVisibility(View.GONE);
|
||||
} else {
|
||||
playerMediaBitrate.setVisibility(View.VISIBLE);
|
||||
|
||||
playerMediaBitrate.setText(bitrate);
|
||||
}
|
||||
}
|
||||
@@ -177,19 +180,28 @@ public class PlayerControllerFragment extends Fragment {
|
||||
: "Original";
|
||||
|
||||
if (transcodingExtension.equals("raw") && transcodingBitrate.equals("Original")) {
|
||||
playerMediaTranscodingPriorityIcon.setVisibility(View.GONE);
|
||||
playerMediaTranscodingIcon.setVisibility(View.GONE);
|
||||
playerMediaTranscodedBitrate.setVisibility(View.GONE);
|
||||
playerMediaTranscodedExtension.setVisibility(View.GONE);
|
||||
} else {
|
||||
playerMediaTranscodingPriorityIcon.setVisibility(View.GONE);
|
||||
playerMediaTranscodingIcon.setVisibility(View.VISIBLE);
|
||||
playerMediaTranscodedBitrate.setVisibility(View.VISIBLE);
|
||||
playerMediaTranscodedExtension.setVisibility(View.VISIBLE);
|
||||
|
||||
playerMediaTranscodedExtension.setText(transcodingExtension);
|
||||
playerMediaTranscodedBitrate.setText(transcodingBitrate);
|
||||
}
|
||||
|
||||
if (mediaMetadata.extras != null && mediaMetadata.extras.getString("uri", "").contains(Constants.DOWNLOAD_URI)) {
|
||||
playerMediaTranscodingPriorityIcon.setVisibility(View.GONE);
|
||||
playerMediaTranscodingIcon.setVisibility(View.GONE);
|
||||
playerMediaTranscodedBitrate.setVisibility(View.GONE);
|
||||
playerMediaTranscodedExtension.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (Preferences.isServerPrioritized() && mediaMetadata.extras != null && !mediaMetadata.extras.getString("uri", "").contains(Constants.DOWNLOAD_URI)) {
|
||||
playerMediaTranscodingPriorityIcon.setVisibility(View.VISIBLE);
|
||||
playerMediaTranscodingIcon.setVisibility(View.GONE);
|
||||
playerMediaTranscodedBitrate.setVisibility(View.GONE);
|
||||
playerMediaTranscodedExtension.setVisibility(View.GONE);
|
||||
@@ -293,6 +305,13 @@ public class PlayerControllerFragment extends Fragment {
|
||||
});
|
||||
}
|
||||
|
||||
private void initTranscodingInfo() {
|
||||
playerMediaTranscodingPriorityIcon.setOnLongClickListener(view -> {
|
||||
Toast.makeText(requireActivity(), R.string.settings_audio_transcode_priority_toast, Toast.LENGTH_SHORT).show();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void initPlaybackSpeedButton(MediaBrowser mediaBrowser) {
|
||||
playbackSpeedButton.setOnClickListener(view -> {
|
||||
float currentSpeed = Preferences.getPlaybackSpeed();
|
||||
|
||||
@@ -19,6 +19,7 @@ import androidx.media3.session.SessionToken;
|
||||
import androidx.navigation.Navigation;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
|
||||
import com.bumptech.glide.load.resource.bitmap.GranularRoundedCorners;
|
||||
import com.cappielloantonio.tempo.R;
|
||||
import com.cappielloantonio.tempo.databinding.FragmentPlaylistPageBinding;
|
||||
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
||||
@@ -146,13 +147,13 @@ public class PlaylistPageFragment extends Fragment implements ClickCallback {
|
||||
playlistPageViewModel.getPlaylistSongLiveList().observe(getViewLifecycleOwner(), songs -> {
|
||||
if (bind != null) {
|
||||
bind.playlistPagePlayButton.setOnClickListener(v -> {
|
||||
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
|
||||
MediaManager.startQueue(mediaBrowserListenableFuture, songs.subList(0, Math.min(100, songs.size())), 0);
|
||||
activity.setBottomSheetInPeek(true);
|
||||
});
|
||||
|
||||
bind.playlistPageShuffleButton.setOnClickListener(v -> {
|
||||
Collections.shuffle(songs);
|
||||
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
|
||||
MediaManager.startQueue(mediaBrowserListenableFuture, songs.subList(0, Math.min(100, songs.size())), 0);
|
||||
activity.setBottomSheetInPeek(true);
|
||||
});
|
||||
}
|
||||
@@ -160,10 +161,39 @@ public class PlaylistPageFragment extends Fragment implements ClickCallback {
|
||||
}
|
||||
|
||||
private void initBackCover() {
|
||||
CustomGlideRequest.Builder
|
||||
.from(requireContext(), playlistPageViewModel.getPlaylist().getCoverArtId())
|
||||
.build()
|
||||
.into(bind.playlistCoverImageView);
|
||||
playlistPageViewModel.getPlaylistSongLiveList().observe(requireActivity(), songs -> {
|
||||
if (bind != null) {
|
||||
Collections.shuffle(songs);
|
||||
|
||||
// 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() {
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.PopupMenu;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.Fragment;
|
||||
@@ -17,7 +18,6 @@ import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
|
||||
import com.cappielloantonio.tempo.R;
|
||||
import com.cappielloantonio.tempo.databinding.FragmentPodcastChannelPageBinding;
|
||||
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
||||
import com.cappielloantonio.tempo.interfaces.ClickCallback;
|
||||
import com.cappielloantonio.tempo.service.MediaManager;
|
||||
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.UIUtil;
|
||||
import com.cappielloantonio.tempo.viewmodel.PodcastChannelPageViewModel;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@UnstableApi
|
||||
public class PodcastChannelPageFragment extends Fragment implements ClickCallback {
|
||||
@@ -84,28 +83,26 @@ public class PodcastChannelPageFragment extends Fragment implements ClickCallbac
|
||||
}
|
||||
|
||||
private void initAppBar() {
|
||||
activity.setSupportActionBar(bind.animToolbar);
|
||||
if (activity.getSupportActionBar() != null)
|
||||
activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
activity.setSupportActionBar(bind.toolbar);
|
||||
|
||||
bind.collapsingToolbar.setTitle(MusicUtil.getReadableString(podcastChannelPageViewModel.getPodcastChannel().getTitle()));
|
||||
bind.animToolbar.setNavigationOnClickListener(v -> activity.navController.navigateUp());
|
||||
bind.collapsingToolbar.setExpandedTitleColor(getResources().getColor(R.color.white, null));
|
||||
if (activity.getSupportActionBar() != null) {
|
||||
activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
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() {
|
||||
String normalizePodcastChannelDescription = MusicUtil.forceReadableString(podcastChannelPageViewModel.getPodcastChannel().getDescription());
|
||||
|
||||
if (bind != null)
|
||||
if (bind != null) {
|
||||
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.podcastEpisodesFilterImageView.setOnClickListener(view -> showPopupMenu(view, R.menu.filter_podcast_episode_popup_menu));
|
||||
}
|
||||
}
|
||||
|
||||
private void initPodcastChannelEpisodesView() {
|
||||
@@ -116,22 +113,21 @@ public class PodcastChannelPageFragment extends Fragment implements ClickCallbac
|
||||
bind.podcastEpisodesRecyclerView.setAdapter(podcastEpisodeAdapter);
|
||||
podcastChannelPageViewModel.getPodcastChannelEpisodes().observe(getViewLifecycleOwner(), channels -> {
|
||||
if (channels == null) {
|
||||
if (bind != null)
|
||||
bind.podcastChannelPageEpisodesPlaceholder.placeholder.setVisibility(View.VISIBLE);
|
||||
if (bind != null) bind.podcastChannelPageEpisodesSector.setVisibility(View.GONE);
|
||||
if (bind != null) {
|
||||
bind.podcastEpisodesRecyclerView.setVisibility(View.GONE);
|
||||
}
|
||||
} else {
|
||||
if (bind != null)
|
||||
bind.podcastChannelPageEpisodesPlaceholder.placeholder.setVisibility(View.GONE);
|
||||
if (bind != null) {
|
||||
bind.podcastEpisodesRecyclerView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
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.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);
|
||||
}
|
||||
|
||||
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
|
||||
public void onPodcastEpisodeClick(Bundle bundle) {
|
||||
MediaManager.startPodcast(mediaBrowserListenableFuture, bundle.getParcelable(Constants.PODCAST_OBJECT));
|
||||
@@ -155,4 +170,14 @@ public class PodcastChannelPageFragment extends Fragment implements ClickCallbac
|
||||
public void onPodcastEpisodeLongClick(Bundle bundle) {
|
||||
Navigation.findNavController(requireView()).navigate(R.id.podcastEpisodeBottomSheetDialog, bundle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPodcastEpisodeAltClick(Bundle bundle) {
|
||||
PodcastEpisode episode = bundle.getParcelable(Constants.PODCAST_OBJECT);
|
||||
podcastChannelPageViewModel.requestPodcastEpisodeDownload(episode);
|
||||
|
||||
Snackbar.make(requireView(), R.string.podcast_episode_download_request_snackbar, Snackbar.LENGTH_SHORT)
|
||||
.setAnchorView(activity.bind.bottomNavigation)
|
||||
.show();
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,15 @@ package com.cappielloantonio.tempo.ui.fragment;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -28,17 +34,15 @@ import com.cappielloantonio.tempo.ui.adapter.AlbumAdapter;
|
||||
import com.cappielloantonio.tempo.ui.adapter.ArtistAdapter;
|
||||
import com.cappielloantonio.tempo.ui.adapter.SongHorizontalAdapter;
|
||||
import com.cappielloantonio.tempo.util.Constants;
|
||||
import com.cappielloantonio.tempo.util.MusicUtil;
|
||||
import com.cappielloantonio.tempo.viewmodel.SearchViewModel;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.paulrybitskyi.persistentsearchview.adapters.model.SuggestionItem;
|
||||
import com.paulrybitskyi.persistentsearchview.listeners.OnSuggestionChangeListener;
|
||||
import com.paulrybitskyi.persistentsearchview.utils.SuggestionCreationUtil;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
@UnstableApi
|
||||
public class SearchFragment extends Fragment implements ClickCallback {
|
||||
private static final String TAG = "SearchFragment";
|
||||
|
||||
private FragmentSearchBinding bind;
|
||||
private MainActivity activity;
|
||||
private SearchViewModel searchViewModel;
|
||||
@@ -60,6 +64,7 @@ public class SearchFragment extends Fragment implements ClickCallback {
|
||||
|
||||
initSearchResultView();
|
||||
initSearchView();
|
||||
inputFocus();
|
||||
|
||||
return view;
|
||||
}
|
||||
@@ -70,12 +75,6 @@ public class SearchFragment extends Fragment implements ClickCallback {
|
||||
initializeMediaBrowser();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
inputFocus();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
releaseMediaBrowser();
|
||||
@@ -118,69 +117,100 @@ public class SearchFragment extends Fragment implements ClickCallback {
|
||||
}
|
||||
|
||||
private void initSearchView() {
|
||||
if (isQueryValid(searchViewModel.getQuery())) {
|
||||
search(searchViewModel.getQuery());
|
||||
}
|
||||
setRecentSuggestions();
|
||||
|
||||
bind.persistentSearchView.setInputQuery(searchViewModel.getQuery());
|
||||
setSuggestions();
|
||||
bind.searchView
|
||||
.getEditText()
|
||||
.setOnEditorActionListener((textView, actionId, keyEvent) -> {
|
||||
String query = bind.searchView.getText().toString();
|
||||
|
||||
bind.persistentSearchView.setOnSearchQueryChangeListener((searchView, oldQuery, newQuery) -> {
|
||||
if (!newQuery.trim().equals("") && newQuery.trim().length() > 1) {
|
||||
searchViewModel.getSearchSuggestion(newQuery).observe(getViewLifecycleOwner(), suggestions -> searchView.setSuggestions(SuggestionCreationUtil.asRegularSearchSuggestions(MusicUtil.getReadableStrings(suggestions)), false));
|
||||
} else {
|
||||
setSuggestions();
|
||||
}
|
||||
});
|
||||
if (isQueryValid(query)) {
|
||||
search(query);
|
||||
return true;
|
||||
}
|
||||
|
||||
bind.persistentSearchView.setOnSuggestionChangeListener(new OnSuggestionChangeListener() {
|
||||
@Override
|
||||
public void onSuggestionPicked(SuggestionItem suggestion) {
|
||||
search(suggestion.getItemModel().getText());
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
@Override
|
||||
public void onSuggestionRemoved(SuggestionItem suggestion) {
|
||||
}
|
||||
});
|
||||
bind.searchView
|
||||
.getEditText()
|
||||
.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence charSequence, int start, int count, int after) {
|
||||
|
||||
bind.persistentSearchView.setOnSearchConfirmedListener((searchView, query) -> {
|
||||
if (isQueryValid(query)) {
|
||||
searchView.collapse();
|
||||
search(query);
|
||||
} else {
|
||||
Toast.makeText(requireContext(), getString(R.string.search_info_minimum_characters), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bind.persistentSearchView.setOnSuggestionChangeListener(new OnSuggestionChangeListener() {
|
||||
@Override
|
||||
public void onSuggestionPicked(SuggestionItem suggestion) {
|
||||
search(suggestion.getItemModel().getText());
|
||||
}
|
||||
@Override
|
||||
public void onTextChanged(CharSequence charSequence, int start, int before, int count) {
|
||||
if (start + count > 1) {
|
||||
setSearchSuggestions(charSequence.toString());
|
||||
} else {
|
||||
setRecentSuggestions();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuggestionRemoved(SuggestionItem suggestion) {
|
||||
searchViewModel.deleteRecentSearch(suggestion.getItemModel().getText());
|
||||
}
|
||||
});
|
||||
@Override
|
||||
public void afterTextChanged(Editable editable) {
|
||||
|
||||
bind.persistentSearchView.setOnClearInputBtnClickListener(v -> searchViewModel.setQuery(""));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setSuggestions() {
|
||||
bind.persistentSearchView.setSuggestions(SuggestionCreationUtil.asRecentSearchSuggestions(searchViewModel.getRecentSearchSuggestion()), false);
|
||||
public void setRecentSuggestions() {
|
||||
bind.searchViewSuggestionContainer.removeAllViews();
|
||||
|
||||
for (String suggestion : searchViewModel.getRecentSearchSuggestion()) {
|
||||
View view = LayoutInflater.from(bind.searchViewSuggestionContainer.getContext()).inflate(R.layout.item_search_suggestion, bind.searchViewSuggestionContainer, false);
|
||||
|
||||
ImageView leadingImageView = view.findViewById(R.id.search_suggestion_icon);
|
||||
TextView titleView = view.findViewById(R.id.search_suggestion_title);
|
||||
ImageView tailingImageView = view.findViewById(R.id.search_suggestion_delete_icon);
|
||||
|
||||
leadingImageView.setImageDrawable(getResources().getDrawable(R.drawable.ic_history, null));
|
||||
titleView.setText(suggestion);
|
||||
|
||||
view.setOnClickListener(v -> search(suggestion));
|
||||
|
||||
tailingImageView.setOnClickListener(v -> {
|
||||
searchViewModel.deleteRecentSearch(suggestion);
|
||||
setRecentSuggestions();
|
||||
});
|
||||
|
||||
bind.searchViewSuggestionContainer.addView(view);
|
||||
}
|
||||
}
|
||||
|
||||
public void setSearchSuggestions(String query) {
|
||||
searchViewModel.getSearchSuggestion(query).observe(getViewLifecycleOwner(), suggestions -> {
|
||||
bind.searchViewSuggestionContainer.removeAllViews();
|
||||
|
||||
for (String suggestion : suggestions) {
|
||||
View view = LayoutInflater.from(bind.searchViewSuggestionContainer.getContext()).inflate(R.layout.item_search_suggestion, bind.searchViewSuggestionContainer, false);
|
||||
|
||||
ImageView leadingImageView = view.findViewById(R.id.search_suggestion_icon);
|
||||
TextView titleView = view.findViewById(R.id.search_suggestion_title);
|
||||
ImageView tailingImageView = view.findViewById(R.id.search_suggestion_delete_icon);
|
||||
|
||||
leadingImageView.setImageDrawable(getResources().getDrawable(R.drawable.ic_search, null));
|
||||
titleView.setText(suggestion);
|
||||
tailingImageView.setVisibility(View.GONE);
|
||||
|
||||
view.setOnClickListener(v -> search(suggestion));
|
||||
|
||||
bind.searchViewSuggestionContainer.addView(view);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void search(String query) {
|
||||
searchViewModel.setQuery(query);
|
||||
|
||||
bind.persistentSearchView.setInputQuery(query);
|
||||
bind.searchBar.setText(query);
|
||||
bind.searchView.hide();
|
||||
performSearch(query);
|
||||
}
|
||||
|
||||
private void performSearch(String query) {
|
||||
searchViewModel.search(query).observe(getViewLifecycleOwner(), result -> {
|
||||
searchViewModel.search3(query).observe(getViewLifecycleOwner(), result -> {
|
||||
if (bind != null) {
|
||||
if (result.getArtists() != null) {
|
||||
bind.searchArtistSector.setVisibility(!result.getArtists().isEmpty() ? View.VISIBLE : View.GONE);
|
||||
@@ -212,11 +242,12 @@ public class SearchFragment extends Fragment implements ClickCallback {
|
||||
}
|
||||
|
||||
private boolean isQueryValid(String query) {
|
||||
Log.d(TAG, "isQueryValid()");
|
||||
return !query.equals("") && query.trim().length() > 2;
|
||||
}
|
||||
|
||||
private void inputFocus() {
|
||||
bind.persistentSearchView.expand();
|
||||
bind.searchView.show();
|
||||
}
|
||||
|
||||
private void initializeMediaBrowser() {
|
||||
|
||||
@@ -15,8 +15,10 @@ import androidx.annotation.OptIn;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.preference.ListPreference;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceFragmentCompat;
|
||||
|
||||
import com.cappielloantonio.tempo.BuildConfig;
|
||||
import com.cappielloantonio.tempo.R;
|
||||
import com.cappielloantonio.tempo.helper.ThemeHelper;
|
||||
import com.cappielloantonio.tempo.interfaces.ScanCallback;
|
||||
@@ -69,6 +71,10 @@ public class SettingsFragment extends PreferenceFragmentCompat {
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
checkEqualizer();
|
||||
|
||||
findPreference("version").setSummary(BuildConfig.VERSION_NAME);
|
||||
|
||||
findPreference("logout").setOnPreferenceClickListener(preference -> {
|
||||
activity.quit();
|
||||
return true;
|
||||
@@ -90,12 +96,6 @@ public class SettingsFragment extends PreferenceFragmentCompat {
|
||||
return true;
|
||||
});
|
||||
|
||||
findPreference("equalizer").setOnPreferenceClickListener(preference -> {
|
||||
Intent intent = new Intent(AudioEffect.ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL);
|
||||
someActivityResultLauncher.launch(intent);
|
||||
return true;
|
||||
});
|
||||
|
||||
findPreference("sync_starred_tracks_for_offline_use").setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
if (newValue instanceof Boolean) {
|
||||
if ((Boolean) newValue) {
|
||||
@@ -127,6 +127,23 @@ public class SettingsFragment extends PreferenceFragmentCompat {
|
||||
}
|
||||
}
|
||||
|
||||
private void checkEqualizer() {
|
||||
Preference equalizer = findPreference("equalizer");
|
||||
|
||||
if (equalizer == null) return;
|
||||
|
||||
Intent intent = new Intent(AudioEffect.ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL);
|
||||
|
||||
if ((intent.resolveActivity(requireActivity().getPackageManager()) != null)) {
|
||||
equalizer.setOnPreferenceClickListener(preference -> {
|
||||
someActivityResultLauncher.launch(intent);
|
||||
return true;
|
||||
});
|
||||
} else {
|
||||
equalizer.setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void getScanStatus() {
|
||||
settingViewModel.getScanStatus(new ScanCallback() {
|
||||
@Override
|
||||
|
||||
@@ -91,7 +91,7 @@ public class AlbumBottomSheetDialog extends BottomSheetDialogFragment implements
|
||||
artistAlbum.setText(MusicUtil.getReadableString(albumBottomSheetViewModel.getAlbum().getArtist()));
|
||||
|
||||
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 -> {
|
||||
albumBottomSheetViewModel.setFavorite();
|
||||
dismissBottomSheet();
|
||||
|
||||
@@ -79,7 +79,7 @@ public class ArtistBottomSheetDialog extends BottomSheetDialogFragment implement
|
||||
nameArtist.setSelected(true);
|
||||
|
||||
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 -> {
|
||||
artistBottomSheetViewModel.setFavorite();
|
||||
dismissBottomSheet();
|
||||
|
||||
@@ -87,7 +87,7 @@ public class SongBottomSheetDialog extends BottomSheetDialogFragment implements
|
||||
artistSong.setText(MusicUtil.getReadableString(songBottomSheetViewModel.getSong().getArtist()));
|
||||
|
||||
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 -> {
|
||||
songBottomSheetViewModel.setFavorite(requireContext());
|
||||
dismissBottomSheet();
|
||||
|
||||
@@ -17,6 +17,7 @@ object Constants {
|
||||
const val MUSIC_FOLDER_OBJECT = "MUSIC_FOLDER_OBJECT"
|
||||
const val MUSIC_DIRECTORY_OBJECT = "MUSIC_DIRECTORY_OBJECT"
|
||||
const val MUSIC_INDEX_OBJECT = "MUSIC_DIRECTORY_OBJECT"
|
||||
const val MUSIC_DIRECTORY_ID = "MUSIC_DIRECTORY_ID"
|
||||
|
||||
const val ALBUM_RECENTLY_PLAYED = "ALBUM_RECENTLY_PLAYED"
|
||||
const val ALBUM_MOST_PLAYED = "ALBUM_MOST_PLAYED"
|
||||
@@ -43,6 +44,9 @@ object Constants {
|
||||
const val PLAYLIST_ORDER_BY_NAME = "ORDER_BY_NAME"
|
||||
const val PLAYLIST_ORDER_BY_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_PODCAST = "podcast"
|
||||
const val MEDIA_TYPE_AUDIOBOOK = "audiobook"
|
||||
|
||||
@@ -41,8 +41,11 @@ public class MusicUtil {
|
||||
if (params.containsKey("c") && params.get("c") != null)
|
||||
uri.append("&c=").append(params.get("c"));
|
||||
|
||||
uri.append("&maxBitRate=").append(getBitratePreference());
|
||||
uri.append("&format=").append(getTranscodingFormatPreference());
|
||||
if (!Preferences.isServerPrioritized())
|
||||
uri.append("&maxBitRate=").append(getBitratePreference());
|
||||
if (!Preferences.isServerPrioritized())
|
||||
uri.append("&format=").append(getTranscodingFormatPreference());
|
||||
|
||||
uri.append("&id=").append(id);
|
||||
|
||||
Log.d(TAG, "getStreamUri: " + uri);
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.cappielloantonio.tempo.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
|
||||
import com.cappielloantonio.tempo.App;
|
||||
|
||||
public class NetworkUtil {
|
||||
public static boolean isOffline() {
|
||||
ConnectivityManager connectivityManager = (ConnectivityManager) App.getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
|
||||
if (connectivityManager != null) {
|
||||
NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
|
||||
|
||||
return networkInfo == null || !networkInfo.isConnected();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ object Preferences {
|
||||
private const val TOKEN = "token"
|
||||
private const val SALT = "salt"
|
||||
private const val LOW_SECURITY = "low_security"
|
||||
private const val BATTERY_OPTIMIZATION = "battery_optimization"
|
||||
private const val SERVER_ID = "server_id"
|
||||
private const val PLAYBACK_SPEED = "playback_speed"
|
||||
private const val SKIP_SILENCE = "skip_silence"
|
||||
@@ -29,6 +30,9 @@ object Preferences {
|
||||
private const val ROUNDED_CORNER_SIZE = "rounded_corner_size"
|
||||
private const val PODCAST_SECTION_VISIBILITY = "podcast_section_visibility"
|
||||
private const val RADIO_SECTION_VISIBILITY = "radio_section_visibility"
|
||||
private const val MUSIC_DIRECTORY_SECTION_VISIBILITY = "music_directory_section_visibility"
|
||||
private const val REPLAY_GAIN_MODE = "replay_gain_mode"
|
||||
private const val AUDIO_TRANSCODE_PRIORITY = "audio_transcode_priority"
|
||||
|
||||
@JvmStatic
|
||||
fun getServer(): String? {
|
||||
@@ -100,6 +104,16 @@ object Preferences {
|
||||
App.getInstance().preferences.edit().putString(SERVER_ID, serverId).apply()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun askForOptimization(): Boolean {
|
||||
return App.getInstance().preferences.getBoolean(BATTERY_OPTIMIZATION, true)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun dontAskForOptimization() {
|
||||
App.getInstance().preferences.edit().putBoolean(BATTERY_OPTIMIZATION, false).apply()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getPlaybackSpeed(): Float {
|
||||
return App.getInstance().preferences.getFloat(PLAYBACK_SPEED, 1f)
|
||||
@@ -229,4 +243,19 @@ object Preferences {
|
||||
fun setRadioSectionHidden() {
|
||||
App.getInstance().preferences.edit().putBoolean(RADIO_SECTION_VISIBILITY, false).apply()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun isMusicDirectorySectionVisible(): Boolean {
|
||||
return App.getInstance().preferences.getBoolean(MUSIC_DIRECTORY_SECTION_VISIBILITY, true)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getReplayGainMode(): String? {
|
||||
return App.getInstance().preferences.getString(REPLAY_GAIN_MODE, "disabled")
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun isServerPrioritized(): Boolean {
|
||||
return App.getInstance().preferences.getBoolean(AUDIO_TRANSCODE_PRIORITY, false)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package com.cappielloantonio.tempo.util;
|
||||
|
||||
import androidx.annotation.OptIn;
|
||||
import androidx.media3.common.Metadata;
|
||||
import androidx.media3.common.Tracks;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.exoplayer.ExoPlayer;
|
||||
|
||||
import com.cappielloantonio.tempo.model.ReplayGain;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
@OptIn(markerClass = UnstableApi.class)
|
||||
public class ReplayGainUtil {
|
||||
private static final String[] tags = {"REPLAYGAIN_TRACK_GAIN", "REPLAYGAIN_ALBUM_GAIN", "R128_TRACK_GAIN", "R128_ALBUM_GAIN"};
|
||||
|
||||
public static void setReplayGain(ExoPlayer player, Tracks tracks) {
|
||||
List<Metadata> metadata = getMetadata(tracks);
|
||||
List<ReplayGain> gains = getReplayGains(metadata);
|
||||
|
||||
applyReplayGain(player, gains);
|
||||
}
|
||||
|
||||
private static List<Metadata> getMetadata(Tracks tracks) {
|
||||
List<Metadata> metadata = new ArrayList<>();
|
||||
|
||||
if (tracks != null && !tracks.getGroups().isEmpty()) {
|
||||
for (int i = 0; i < tracks.getGroups().size(); i++) {
|
||||
Tracks.Group group = tracks.getGroups().get(i);
|
||||
|
||||
if (group != null && group.getMediaTrackGroup() != null) {
|
||||
for (int j = 0; j < group.getMediaTrackGroup().length; j++) {
|
||||
metadata.add(group.getTrackFormat(j).metadata);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return metadata;
|
||||
}
|
||||
|
||||
private static List<ReplayGain> getReplayGains(List<Metadata> metadata) {
|
||||
List<ReplayGain> gains = new ArrayList<>();
|
||||
|
||||
if (metadata != null) {
|
||||
for (int i = 0; i < metadata.size(); i++) {
|
||||
Metadata singleMetadata = metadata.get(i);
|
||||
|
||||
if (singleMetadata != null) {
|
||||
for (int j = 0; j < singleMetadata.length(); j++) {
|
||||
Metadata.Entry entry = singleMetadata.get(j);
|
||||
|
||||
if (checkReplayGain(entry)) {
|
||||
ReplayGain replayGain = setReplayGains(entry);
|
||||
gains.add(replayGain);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return gains;
|
||||
}
|
||||
|
||||
private static boolean checkReplayGain(Metadata.Entry entry) {
|
||||
for (String tag : tags) {
|
||||
if (entry.toString().contains(tag)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static ReplayGain setReplayGains(Metadata.Entry entry) {
|
||||
ReplayGain replayGain = new ReplayGain();
|
||||
|
||||
if (entry.toString().contains(tags[0])) {
|
||||
replayGain.setTrackGain(parseReplayGainTag(entry));
|
||||
}
|
||||
|
||||
if (entry.toString().contains(tags[1])) {
|
||||
replayGain.setAlbumGain(parseReplayGainTag(entry));
|
||||
}
|
||||
|
||||
if (entry.toString().contains(tags[2])) {
|
||||
replayGain.setTrackGain(parseReplayGainTag(entry) / 256f);
|
||||
}
|
||||
|
||||
if (entry.toString().contains(tags[3])) {
|
||||
replayGain.setAlbumGain(parseReplayGainTag(entry) / 256f);
|
||||
}
|
||||
|
||||
return replayGain;
|
||||
}
|
||||
|
||||
private static Float parseReplayGainTag(Metadata.Entry entry) {
|
||||
try {
|
||||
return Float.parseFloat(entry.toString().replaceAll("[^\\d.]", ""));
|
||||
} catch (NumberFormatException exception) {
|
||||
return 0f;
|
||||
}
|
||||
}
|
||||
|
||||
private static void applyReplayGain(ExoPlayer player, List<ReplayGain> gains) {
|
||||
if (Objects.equals(Preferences.getReplayGainMode(), "disabled") || gains.size() == 0) {
|
||||
setReplayGain(player, 0f);
|
||||
} else if (Objects.equals(Preferences.getReplayGainMode(), "track")) {
|
||||
setReplayGain(player, gains.get(0).getTrackGain() != 0f ? gains.get(0).getTrackGain() : gains.get(0).getAlbumGain());
|
||||
} else if (Objects.equals(Preferences.getReplayGainMode(), "album")) {
|
||||
setReplayGain(player, gains.get(0).getAlbumGain() != 0f ? gains.get(0).getAlbumGain() : gains.get(0).getTrackGain());
|
||||
}
|
||||
}
|
||||
|
||||
private static void setReplayGain(ExoPlayer player, float gain) {
|
||||
player.setVolume((float) Math.pow(10f, gain / 20f));
|
||||
}
|
||||
}
|
||||
@@ -7,9 +7,6 @@ import android.graphics.drawable.InsetDrawable;
|
||||
|
||||
import androidx.recyclerview.widget.DividerItemDecoration;
|
||||
|
||||
import com.google.android.gms.common.ConnectionResult;
|
||||
import com.google.android.gms.common.GoogleApiAvailability;
|
||||
|
||||
public class UIUtil {
|
||||
public static int getSpanCount(int itemCount, int maxSpan) {
|
||||
int itemSize = itemCount == 0 ? 1 : itemCount;
|
||||
@@ -21,10 +18,6 @@ public class UIUtil {
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isCastApiAvailable(Context context) {
|
||||
return GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context) == ConnectionResult.SUCCESS;
|
||||
}
|
||||
|
||||
public static DividerItemDecoration getDividerItemDecoration(Context context) {
|
||||
int[] ATTRS = new int[]{android.R.attr.listDivider};
|
||||
|
||||
|
||||
@@ -7,11 +7,14 @@ import androidx.lifecycle.AndroidViewModel;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import com.cappielloantonio.tempo.interfaces.StarCallback;
|
||||
import com.cappielloantonio.tempo.repository.AlbumRepository;
|
||||
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.ArtistID3;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||
import com.cappielloantonio.tempo.util.NetworkUtil;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
@@ -19,6 +22,7 @@ import java.util.List;
|
||||
public class AlbumBottomSheetViewModel extends AndroidViewModel {
|
||||
private final AlbumRepository albumRepository;
|
||||
private final ArtistRepository artistRepository;
|
||||
private final FavoriteRepository favoriteRepository;
|
||||
|
||||
private AlbumID3 album;
|
||||
|
||||
@@ -27,6 +31,7 @@ public class AlbumBottomSheetViewModel extends AndroidViewModel {
|
||||
|
||||
albumRepository = new AlbumRepository();
|
||||
artistRepository = new ArtistRepository();
|
||||
favoriteRepository = new FavoriteRepository();
|
||||
}
|
||||
|
||||
public AlbumID3 getAlbum() {
|
||||
@@ -47,11 +52,51 @@ public class AlbumBottomSheetViewModel extends AndroidViewModel {
|
||||
|
||||
public void setFavorite() {
|
||||
if (album.getStarred() != null) {
|
||||
artistRepository.unstar(album.getId());
|
||||
album.setStarred(null);
|
||||
if (NetworkUtil.isOffline()) {
|
||||
removeFavoriteOffline();
|
||||
} else {
|
||||
removeFavoriteOnline();
|
||||
}
|
||||
} else {
|
||||
artistRepository.star(album.getId());
|
||||
album.setStarred(new Date());
|
||||
if (NetworkUtil.isOffline()) {
|
||||
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.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.util.NetworkUtil;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public class ArtistBottomSheetViewModel extends AndroidViewModel {
|
||||
private final AlbumRepository albumRepository;
|
||||
private final ArtistRepository artistRepository;
|
||||
private final FavoriteRepository favoriteRepository;
|
||||
|
||||
private ArtistID3 artist;
|
||||
|
||||
public ArtistBottomSheetViewModel(@NonNull Application application) {
|
||||
super(application);
|
||||
|
||||
albumRepository = new AlbumRepository();
|
||||
artistRepository = new ArtistRepository();
|
||||
favoriteRepository = new FavoriteRepository();
|
||||
}
|
||||
|
||||
public ArtistID3 getArtist() {
|
||||
@@ -31,11 +36,51 @@ public class ArtistBottomSheetViewModel extends AndroidViewModel {
|
||||
|
||||
public void setFavorite() {
|
||||
if (artist.getStarred() != null) {
|
||||
albumRepository.unstar(artist.getId());
|
||||
artist.setStarred(null);
|
||||
if (NetworkUtil.isOffline()) {
|
||||
removeFavoriteOffline();
|
||||
} else {
|
||||
removeFavoriteOnline();
|
||||
}
|
||||
} else {
|
||||
albumRepository.star(artist.getId());
|
||||
artist.setStarred(new Date());
|
||||
if (NetworkUtil.isOffline()) {
|
||||
setFavoriteOffline();
|
||||
} else {
|
||||
setFavoriteOnline();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void removeFavoriteOffline() {
|
||||
favoriteRepository.starLater(null, null, artist.getId(), false);
|
||||
artist.setStarred(null);
|
||||
}
|
||||
|
||||
private void removeFavoriteOnline() {
|
||||
favoriteRepository.unstar(null, null, artist.getId(), new StarCallback() {
|
||||
@Override
|
||||
public void onError() {
|
||||
// artist.setStarred(new Date());
|
||||
favoriteRepository.starLater(null, null, artist.getId(), false);
|
||||
}
|
||||
});
|
||||
|
||||
artist.setStarred(null);
|
||||
}
|
||||
|
||||
private void setFavoriteOffline() {
|
||||
favoriteRepository.starLater(null, null, artist.getId(), true);
|
||||
artist.setStarred(new Date());
|
||||
}
|
||||
|
||||
private void setFavoriteOnline() {
|
||||
favoriteRepository.star(null, null, artist.getId(), new StarCallback() {
|
||||
@Override
|
||||
public void onError() {
|
||||
// artist.setStarred(null);
|
||||
favoriteRepository.starLater(null, null, artist.getId(), true);
|
||||
}
|
||||
});
|
||||
|
||||
artist.setStarred(new Date());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,7 @@ import android.app.Application;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.AndroidViewModel;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import com.cappielloantonio.tempo.repository.DirectoryRepository;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Directory;
|
||||
@@ -14,34 +12,13 @@ import com.cappielloantonio.tempo.subsonic.models.Directory;
|
||||
public class DirectoryViewModel extends AndroidViewModel {
|
||||
private final DirectoryRepository directoryRepository;
|
||||
|
||||
private MutableLiveData<String> id = new MutableLiveData<>(null);
|
||||
private MutableLiveData<String> name = new MutableLiveData<>(null);
|
||||
|
||||
private MutableLiveData<Directory> directory = new MutableLiveData<>(null);
|
||||
|
||||
public DirectoryViewModel(@NonNull Application application) {
|
||||
super(application);
|
||||
|
||||
directoryRepository = new DirectoryRepository();
|
||||
}
|
||||
|
||||
public LiveData<Directory> getDirectory() {
|
||||
return directory;
|
||||
}
|
||||
|
||||
public void setMusicDirectoryId(String id) {
|
||||
this.id.setValue(id);
|
||||
}
|
||||
|
||||
public void setMusicDirectoryName(String name) {
|
||||
this.name.setValue(name);
|
||||
}
|
||||
|
||||
public void loadMusicDirectory(LifecycleOwner owner) {
|
||||
this.id.observe(owner, id -> directoryRepository.getMusicDirectory(id).observe(owner, directory -> this.directory.setValue(directory)));
|
||||
}
|
||||
|
||||
public void goBack() {
|
||||
this.id.setValue(this.directory.getValue().getParentId());
|
||||
public LiveData<Directory> loadMusicDirectory(String id) {
|
||||
return directoryRepository.getMusicDirectory(id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,20 +8,24 @@ import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import com.cappielloantonio.tempo.interfaces.StarCallback;
|
||||
import com.cappielloantonio.tempo.model.Chronology;
|
||||
import com.cappielloantonio.tempo.model.Favorite;
|
||||
import com.cappielloantonio.tempo.repository.AlbumRepository;
|
||||
import com.cappielloantonio.tempo.repository.ArtistRepository;
|
||||
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.subsonic.models.AlbumID3;
|
||||
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
public class HomeViewModel extends AndroidViewModel {
|
||||
@@ -31,6 +35,7 @@ public class HomeViewModel extends AndroidViewModel {
|
||||
private final AlbumRepository albumRepository;
|
||||
private final ArtistRepository artistRepository;
|
||||
private final ChronologyRepository chronologyRepository;
|
||||
private final FavoriteRepository favoriteRepository;
|
||||
|
||||
private final MutableLiveData<List<Child>> dicoverSongSample = new MutableLiveData<>(null);
|
||||
private final MutableLiveData<List<AlbumID3>> newReleasedAlbum = new MutableLiveData<>(null);
|
||||
@@ -57,6 +62,9 @@ public class HomeViewModel extends AndroidViewModel {
|
||||
albumRepository = new AlbumRepository();
|
||||
artistRepository = new ArtistRepository();
|
||||
chronologyRepository = new ChronologyRepository();
|
||||
favoriteRepository = new FavoriteRepository();
|
||||
|
||||
setOfflineFavorite();
|
||||
}
|
||||
|
||||
public LiveData<List<Child>> getDiscoverSongSample(LifecycleOwner owner) {
|
||||
@@ -67,6 +75,10 @@ public class HomeViewModel extends AndroidViewModel {
|
||||
return dicoverSongSample;
|
||||
}
|
||||
|
||||
public LiveData<List<Child>> getRandomShuffleSample() {
|
||||
return songRepository.getRandomSample(100, null, null);
|
||||
}
|
||||
|
||||
public LiveData<List<Chronology>> getGridSongSample(LifecycleOwner owner) {
|
||||
String server = Preferences.getServerId();
|
||||
chronologyRepository.getLastWeek(server).observe(owner, thisGridTopSong::postValue);
|
||||
@@ -235,4 +247,109 @@ public class HomeViewModel extends AndroidViewModel {
|
||||
public void refreshRecentlyPlayedAlbumList(LifecycleOwner owner) {
|
||||
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.media3.common.util.UnstableApi;
|
||||
|
||||
import com.cappielloantonio.tempo.interfaces.StarCallback;
|
||||
import com.cappielloantonio.tempo.model.Download;
|
||||
import com.cappielloantonio.tempo.model.Queue;
|
||||
import com.cappielloantonio.tempo.repository.ArtistRepository;
|
||||
import com.cappielloantonio.tempo.repository.FavoriteRepository;
|
||||
import com.cappielloantonio.tempo.repository.QueueRepository;
|
||||
import com.cappielloantonio.tempo.repository.SongRepository;
|
||||
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.DownloadUtil;
|
||||
import com.cappielloantonio.tempo.util.MappingUtil;
|
||||
import com.cappielloantonio.tempo.util.NetworkUtil;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
|
||||
import java.util.Collections;
|
||||
@@ -36,6 +39,7 @@ public class PlayerBottomSheetViewModel extends AndroidViewModel {
|
||||
private final SongRepository songRepository;
|
||||
private final ArtistRepository artistRepository;
|
||||
private final QueueRepository queueRepository;
|
||||
private final FavoriteRepository favoriteRepository;
|
||||
|
||||
private final MutableLiveData<String> lyricsLiveData = new MutableLiveData<>(null);
|
||||
|
||||
@@ -50,6 +54,7 @@ public class PlayerBottomSheetViewModel extends AndroidViewModel {
|
||||
songRepository = new SongRepository();
|
||||
artistRepository = new ArtistRepository();
|
||||
queueRepository = new QueueRepository();
|
||||
favoriteRepository = new FavoriteRepository();
|
||||
}
|
||||
|
||||
public LiveData<List<Queue>> getQueueSong() {
|
||||
@@ -59,22 +64,62 @@ public class PlayerBottomSheetViewModel extends AndroidViewModel {
|
||||
public void setFavorite(Context context, Child media) {
|
||||
if (media != null) {
|
||||
if (media.getStarred() != null) {
|
||||
songRepository.unstar(media.getId());
|
||||
media.setStarred(null);
|
||||
if (NetworkUtil.isOffline()) {
|
||||
removeFavoriteOffline(media);
|
||||
} else {
|
||||
removeFavoriteOnline(media);
|
||||
}
|
||||
} else {
|
||||
songRepository.star(media.getId());
|
||||
media.setStarred(new Date());
|
||||
|
||||
if (Preferences.isStarredSyncEnabled()) {
|
||||
DownloadUtil.getDownloadTracker(context).download(
|
||||
MappingUtil.mapDownload(media),
|
||||
new Download(media)
|
||||
);
|
||||
if (NetworkUtil.isOffline()) {
|
||||
setFavoriteOffline(media);
|
||||
} else {
|
||||
setFavoriteOnline(context, 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() {
|
||||
return lyricsLiveData;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import androidx.lifecycle.LiveData;
|
||||
|
||||
import com.cappielloantonio.tempo.repository.PodcastRepository;
|
||||
import com.cappielloantonio.tempo.subsonic.models.PodcastChannel;
|
||||
import com.cappielloantonio.tempo.subsonic.models.PodcastEpisode;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -33,4 +34,8 @@ public class PodcastChannelPageViewModel extends AndroidViewModel {
|
||||
public void setPodcastChannel(PodcastChannel podcastChannel) {
|
||||
this.podcastChannel = podcastChannel;
|
||||
}
|
||||
|
||||
public void requestPodcastEpisodeDownload(PodcastEpisode podcastEpisode) {
|
||||
podcastRepository.downloadPodcastEpisode(podcastEpisode.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import androidx.lifecycle.LiveData;
|
||||
|
||||
import com.cappielloantonio.tempo.model.RecentSearch;
|
||||
import com.cappielloantonio.tempo.repository.SearchingRepository;
|
||||
import com.cappielloantonio.tempo.subsonic.models.SearchResult2;
|
||||
import com.cappielloantonio.tempo.subsonic.models.SearchResult3;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -38,8 +39,12 @@ public class SearchViewModel extends AndroidViewModel {
|
||||
}
|
||||
}
|
||||
|
||||
public LiveData<SearchResult3> search(String title) {
|
||||
return searchingRepository.search(title);
|
||||
public LiveData<SearchResult2> search2(String title) {
|
||||
return searchingRepository.search2(title);
|
||||
}
|
||||
|
||||
public LiveData<SearchResult3> search3(String title) {
|
||||
return searchingRepository.search3(title);
|
||||
}
|
||||
|
||||
public void insertNewSearch(String search) {
|
||||
@@ -56,7 +61,7 @@ public class SearchViewModel extends AndroidViewModel {
|
||||
|
||||
public List<String> getRecentSearchSuggestion() {
|
||||
ArrayList<String> suggestions = new ArrayList<>();
|
||||
suggestions.addAll(searchingRepository.getRecentSearchSuggestion(5));
|
||||
suggestions.addAll(searchingRepository.getRecentSearchSuggestion());
|
||||
|
||||
return suggestions;
|
||||
}
|
||||
|
||||
@@ -10,15 +10,18 @@ import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
|
||||
import com.cappielloantonio.tempo.interfaces.StarCallback;
|
||||
import com.cappielloantonio.tempo.model.Download;
|
||||
import com.cappielloantonio.tempo.repository.AlbumRepository;
|
||||
import com.cappielloantonio.tempo.repository.ArtistRepository;
|
||||
import com.cappielloantonio.tempo.repository.FavoriteRepository;
|
||||
import com.cappielloantonio.tempo.repository.SongRepository;
|
||||
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
|
||||
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||
import com.cappielloantonio.tempo.util.DownloadUtil;
|
||||
import com.cappielloantonio.tempo.util.MappingUtil;
|
||||
import com.cappielloantonio.tempo.util.NetworkUtil;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
|
||||
import java.util.Collections;
|
||||
@@ -30,6 +33,7 @@ public class SongBottomSheetViewModel extends AndroidViewModel {
|
||||
private final SongRepository songRepository;
|
||||
private final AlbumRepository albumRepository;
|
||||
private final ArtistRepository artistRepository;
|
||||
private final FavoriteRepository favoriteRepository;
|
||||
|
||||
private Child song;
|
||||
|
||||
@@ -41,6 +45,7 @@ public class SongBottomSheetViewModel extends AndroidViewModel {
|
||||
songRepository = new SongRepository();
|
||||
albumRepository = new AlbumRepository();
|
||||
artistRepository = new ArtistRepository();
|
||||
favoriteRepository = new FavoriteRepository();
|
||||
}
|
||||
|
||||
public Child getSong() {
|
||||
@@ -53,18 +58,58 @@ public class SongBottomSheetViewModel extends AndroidViewModel {
|
||||
|
||||
public void setFavorite(Context context) {
|
||||
if (song.getStarred() != null) {
|
||||
songRepository.unstar(song.getId());
|
||||
song.setStarred(null);
|
||||
} else {
|
||||
songRepository.star(song.getId());
|
||||
song.setStarred(new Date());
|
||||
|
||||
if (Preferences.isStarredSyncEnabled()) {
|
||||
DownloadUtil.getDownloadTracker(context).download(
|
||||
MappingUtil.mapDownload(song),
|
||||
new Download(song)
|
||||
);
|
||||
if (NetworkUtil.isOffline()) {
|
||||
removeFavoriteOffline(song);
|
||||
} else {
|
||||
removeFavoriteOnline(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>
|
||||
@@ -1,10 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z" />
|
||||
</vector>
|
||||
android:fillColor="@color/titleTextColor"
|
||||
android:pathData="M256,760L200,704L424,480L200,256L256,200L480,424L704,200L760,256L536,480L760,704L704,760L480,536L256,760Z"/>
|
||||
</vector>
|
||||
9
app/src/main/res/drawable/ic_history.xml
Normal file
9
app/src/main/res/drawable/ic_history.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="@color/titleTextColor"
|
||||
android:pathData="M480,840Q342,840 239.5,748.5Q137,657 122,520L204,520Q218,624 296.5,692Q375,760 480,760Q597,760 678.5,678.5Q760,597 760,480Q760,363 678.5,281.5Q597,200 480,200Q411,200 351,232Q291,264 250,320L360,320L360,400L120,400L120,160L200,160L200,254Q251,190 324.5,155Q398,120 480,120Q555,120 620.5,148.5Q686,177 734.5,225.5Q783,274 811.5,339.5Q840,405 840,480Q840,555 811.5,620.5Q783,686 734.5,734.5Q686,783 620.5,811.5Q555,840 480,840ZM592,648L440,496L440,280L520,280L520,464L648,592L592,648Z"/>
|
||||
</vector>
|
||||
9
app/src/main/res/drawable/ic_podcast_download.xml
Normal file
9
app/src/main/res/drawable/ic_podcast_download.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="@color/titleTextColor"
|
||||
android:pathData="M439,878Q363,870 297.5,835.5Q232,801 184,747.5Q136,694 108.5,625Q81,556 81,479Q81,324 183.5,210.5Q286,97 440,80L440,160Q319,177 240,267.5Q161,358 161,479Q161,600 240,690.5Q319,781 439,798L439,878ZM479,680L278,478L335,421L439,525L439,280L519,280L519,525L622,422L679,480L479,680ZM519,878L519,798Q562,792 601.5,775Q641,758 675,732L733,790Q686,827 632,849.5Q578,872 519,878ZM677,226Q642,200 602.5,183Q563,166 520,160L520,80Q579,86 633,108.5Q687,131 733,168L677,226ZM789,732L733,675Q759,641 775,601.5Q791,562 797,519L879,519Q871,578 849,632.5Q827,687 789,732ZM797,439Q791,396 775,356.5Q759,317 733,283L789,226Q827,271 850,325.5Q873,380 879,439L797,439Z"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="@color/titleTextColor"
|
||||
android:pathData="M480,796.92Q455.25,796.92 437.63,779.29Q420,761.67 420,736.92Q420,712.17 437.63,694.55Q455.25,676.92 480,676.92Q504.75,676.92 522.37,694.55Q540,712.17 540,736.92Q540,761.67 522.37,779.29Q504.75,796.92 480,796.92ZM425.39,600.77L425.39,143.08L534.61,143.08L534.61,600.77L425.39,600.77Z"/>
|
||||
</vector>
|
||||
25
app/src/main/res/layout/dialog_battery_optimization.xml
Normal file
25
app/src/main/res/layout/dialog_battery_optimization.xml
Normal file
@@ -0,0 +1,25 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/battery_optimization_primary"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="24dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:text="@string/activity_battery_optimizations_summary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/battery_optimization_secondary"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="24dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:autoLink="web"
|
||||
android:text="@string/activity_battery_optimizations_conclusion" />
|
||||
</LinearLayout>
|
||||
@@ -39,8 +39,7 @@
|
||||
android:paddingBottom="24dp"
|
||||
android:text="@string/album_catalogue_title_expanded"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="parent" />
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/album_list_sort_image_view"
|
||||
|
||||
@@ -188,6 +188,7 @@
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:nestedScrollingEnabled="false"
|
||||
android:clipToPadding="false"
|
||||
android:paddingTop="8dp" />
|
||||
</LinearLayout>
|
||||
|
||||
@@ -4,40 +4,11 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/appbar"
|
||||
<fragment
|
||||
android:id="@+id/toolbar_fragment"
|
||||
android:name="com.cappielloantonio.tempo.ui.fragment.ToolbarFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/colorSurface"
|
||||
app:layout_scrollFlags="scroll|enterAlways|snap">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="28dp"
|
||||
android:layout_height="28dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:background="@drawable/ic_toolbar_tempo" />
|
||||
|
||||
<TextView
|
||||
style="@style/HeadlineMedium"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:text="@string/app_name" />
|
||||
</LinearLayout>
|
||||
</com.google.android.material.appbar.MaterialToolbar>
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/loading_progress_bar"
|
||||
@@ -121,6 +92,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipToPadding="false"
|
||||
android:nestedScrollingEnabled="false"
|
||||
android:paddingBottom="8dp" />
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
@@ -6,47 +6,11 @@
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".ui.fragment.HomeFragment">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/appbar"
|
||||
<fragment
|
||||
android:id="@+id/toolbar_fragment"
|
||||
android:name="com.cappielloantonio.tempo.ui.fragment.ToolbarFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/colorSurface"
|
||||
app:layout_scrollFlags="scroll|enterAlways|snap">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="28dp"
|
||||
android:layout_height="28dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:background="@drawable/ic_toolbar_tempo" />
|
||||
|
||||
<TextView
|
||||
style="@style/HeadlineMedium"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:text="@string/app_name" />
|
||||
</LinearLayout>
|
||||
</com.google.android.material.appbar.MaterialToolbar>
|
||||
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
android:id="@+id/homeTabLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:tabGravity="fill"
|
||||
app:tabMode="fixed" />
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<androidx.viewpager2.widget.ViewPager2
|
||||
android:id="@+id/homeViewPager"
|
||||
|
||||
@@ -113,16 +113,35 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/discovery_text_view_refreshable"
|
||||
style="@style/TitleLarge"
|
||||
<!-- Label and button -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:text="@string/home_title_discovery" />
|
||||
android:paddingEnd="8dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/discovery_text_view_refreshable"
|
||||
style="@style/TitleLarge"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:text="@string/home_title_discovery" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/discovery_text_view_clickable"
|
||||
style="@style/TitleMedium"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:text="@string/home_title_discovery_shuffle_all_button" />
|
||||
</LinearLayout>
|
||||
|
||||
<!-- slideview -->
|
||||
<androidx.viewpager2.widget.ViewPager2
|
||||
|
||||
@@ -47,13 +47,30 @@
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/index_recycler_view"
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
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" />
|
||||
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>
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
@@ -4,40 +4,11 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/appbar"
|
||||
<fragment
|
||||
android:id="@+id/toolbar_fragment"
|
||||
android:name="com.cappielloantonio.tempo.ui.fragment.ToolbarFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/colorSurface"
|
||||
app:layout_scrollFlags="scroll|enterAlways|snap">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="28dp"
|
||||
android:layout_height="28dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:background="@drawable/ic_toolbar_tempo" />
|
||||
|
||||
<TextView
|
||||
style="@style/HeadlineMedium"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:text="@string/app_name" />
|
||||
</LinearLayout>
|
||||
</com.google.android.material.appbar.MaterialToolbar>
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:id="@+id/fragment_library_nested_scroll_view"
|
||||
@@ -75,6 +46,7 @@
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:nestedScrollingEnabled="false"
|
||||
android:clipToPadding="false"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingTop="8dp"
|
||||
@@ -308,6 +280,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:nestedScrollingEnabled="false"
|
||||
android:clipToPadding="false"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp" />
|
||||
|
||||
@@ -12,35 +12,67 @@
|
||||
app:layout_collapseMode="pin"
|
||||
app:navigationIcon="@drawable/ic_arrow_back" />
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:id="@+id/fragment_playlist_page_nested_scroll_view"
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
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_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/playlist_info_sector"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipChildren="false"
|
||||
android:paddingTop="8dp">
|
||||
android:background="?attr/colorSurface"
|
||||
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/playlist_cover_image_view"
|
||||
android:id="@+id/playlist_cover_image_view_top_left"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="64dp"
|
||||
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"
|
||||
app:layout_constraintDimensionRatio="H,1:1"
|
||||
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" />
|
||||
|
||||
<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
|
||||
android:id="@+id/playlist_name_label"
|
||||
style="@style/LabelExtraLarge"
|
||||
@@ -55,7 +87,7 @@
|
||||
android:textAlignment="center"
|
||||
app:layout_constraintEnd_toEndOf="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
|
||||
android:id="@+id/playlist_song_count_label"
|
||||
@@ -159,15 +191,16 @@
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/playlist_page_button_layout" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/song_recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipToPadding="false"
|
||||
android:nestedScrollingEnabled="false"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="@dimen/global_padding_bottom" />
|
||||
</LinearLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/song_recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="@dimen/global_padding_bottom"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
</LinearLayout>
|
||||
@@ -1,74 +1,45 @@
|
||||
<?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"
|
||||
android:id="@+id/layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/appbar"
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
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
|
||||
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"
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||
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_height="wrap_content"
|
||||
android:clipToPadding="false"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="18dp"
|
||||
android:paddingBottom="@dimen/global_padding_bottom">
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/podcast_channel_page_bio_sector"
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/podcast_channel_page_info_sector"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="22dp">
|
||||
android:background="?attr/colorSurface"
|
||||
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/podcast_channel_description_label"
|
||||
style="@style/TitleLarge"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="16dp"
|
||||
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
|
||||
android:id="@+id/podcast_channel_description_text_view"
|
||||
@@ -77,63 +48,61 @@
|
||||
android:layout_height="match_parent"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingEnd="16dp" />
|
||||
</LinearLayout>
|
||||
android:paddingEnd="16dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/podcast_channel_description_label" />
|
||||
|
||||
<include
|
||||
android:id="@+id/podcast_channel_page_description_placeholder"
|
||||
layout="@layout/item_placehoder_biography"
|
||||
android:visibility="gone" />
|
||||
|
||||
<View
|
||||
android:id="@+id/upper_button_divider"
|
||||
style="@style/Divider"
|
||||
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">
|
||||
<View
|
||||
android:id="@+id/upper_button_divider"
|
||||
style="@style/Divider"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/podcast_channel_description_text_view" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/podcast_episodes_section_label"
|
||||
style="@style/TitleLarge"
|
||||
android:layout_width="match_parent"
|
||||
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_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:paddingEnd="8dp"
|
||||
android:text="@string/podcast_channel_page_title_no_episode_available"
|
||||
android:visibility="gone" />
|
||||
</LinearLayout>
|
||||
android:paddingBottom="12dp"
|
||||
android:text="@string/podcast_channel_page_title_episode_section"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/upper_button_divider" />
|
||||
|
||||
<include
|
||||
android:id="@+id/podcast_channel_page_episodes_placeholder"
|
||||
layout="@layout/item_placeholder_horizontal"
|
||||
android:visibility="gone" />
|
||||
<Button
|
||||
android:id="@+id/podcast_episodes_filter_image_view"
|
||||
style="@style/Widget.Material3.Button.TonalButton.Icon"
|
||||
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.core.widget.NestedScrollView>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<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>
|
||||
@@ -1,54 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.paulrybitskyi.persistentsearchview.PersistentSearchView
|
||||
android:id="@+id/persistentSearchView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
app:areSuggestionsDisabled="false"
|
||||
app:cardBackgroundColor="?attr/colorSurface"
|
||||
app:cardCornerRadius="0dp"
|
||||
app:cardElevation="0dp"
|
||||
app:clearInputButtonDrawable="@drawable/ic_close"
|
||||
app:dividerColor="@color/dividerColor"
|
||||
app:isClearInputButtonEnabled="true"
|
||||
app:isDismissableOnTouchOutside="true"
|
||||
app:isProgressBarEnabled="true"
|
||||
app:isVoiceInputButtonEnabled="false"
|
||||
app:leftButtonDrawable="@drawable/ic_search"
|
||||
app:progressBarColor="?attr/colorOnSurface"
|
||||
app:queryInputBarIconColor="@color/searchPlaceholderColor"
|
||||
app:queryInputCursorColor="?attr/colorOnSurface"
|
||||
app:queryInputHint="@string/search_hint"
|
||||
app:queryInputHintColor="@color/searchPlaceholderColor"
|
||||
app:queryInputTextColor="@color/searchPlaceholderColor"
|
||||
app:shouldDimBehind="false"
|
||||
app:suggestionRecentSearchIconColor="@color/searchColor"
|
||||
app:suggestionSearchSuggestionIconColor="@color/searchColor"
|
||||
app:suggestionSelectedTextColor="?attr/colorPrimary"
|
||||
app:suggestionTextColor="@color/searchColor"
|
||||
app:suggestionIconColor="@color/searchColor" />
|
||||
|
||||
<View
|
||||
android:id="@+id/search_bar_divider"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0.75dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:layout_below="@+id/persistentSearchView"
|
||||
android:background="?attr/colorSurfaceVariant" />
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:id="@+id/search_result_nested_scroll_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/search_bar_divider">
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="@string/searchbar_scrolling_view_behavior">
|
||||
|
||||
<!-- Search result -->
|
||||
<LinearLayout
|
||||
@@ -56,6 +16,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="64dp"
|
||||
android:paddingBottom="@dimen/global_padding_bottom">
|
||||
|
||||
<!-- Artist -->
|
||||
@@ -150,4 +111,39 @@
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
</RelativeLayout>
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.search.SearchBar
|
||||
android:id="@+id/search_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/search_hint">
|
||||
|
||||
</com.google.android.material.search.SearchBar>
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<com.google.android.material.search.SearchView
|
||||
android:id="@+id/search_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:hint="@string/search_hint"
|
||||
app:layout_anchor="@id/search_bar">
|
||||
|
||||
<!-- Content goes here (ScrollView, RecyclerView, etc.). -->
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scrollbars="none"
|
||||
android:paddingBottom="@dimen/global_padding_bottom">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/search_view_suggestion_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"/>
|
||||
</ScrollView>
|
||||
</com.google.android.material.search.SearchView>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
36
app/src/main/res/layout/fragment_toolbar.xml
Normal file
36
app/src/main/res/layout/fragment_toolbar.xml
Normal file
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.google.android.material.appbar.AppBarLayout xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/appbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/colorSurface"
|
||||
app:layout_scrollFlags="scroll|enterAlways|snap">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="28dp"
|
||||
android:layout_height="28dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:background="@drawable/ic_toolbar_tempo" />
|
||||
|
||||
<TextView
|
||||
style="@style/HeadlineMedium"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:text="@string/app_name" />
|
||||
</LinearLayout>
|
||||
</com.google.android.material.appbar.MaterialToolbar>
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
@@ -41,6 +41,13 @@
|
||||
android:src="@drawable/ic_transcode"
|
||||
android:visibility="gone" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/player_media_server_transcode_priority"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="20dp"
|
||||
android:src="@drawable/ic_server_transcode_priority"
|
||||
android:visibility="gone" />
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/player_media_transcoded_extension"
|
||||
style="@style/Widget.Material3.Chip.Suggestion"
|
||||
|
||||
@@ -20,9 +20,9 @@
|
||||
style="@style/LabelMedium"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="5"
|
||||
android:layout_marginStart="12dp"
|
||||
android:text="@string/label_placeholder"
|
||||
app:layout_constraintBottom_toTopOf="@+id/podcast_subtitle_label"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
@@ -36,9 +36,9 @@
|
||||
style="@style/LabelSmall"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="2"
|
||||
android:layout_marginStart="12dp"
|
||||
android:text="@string/label_placeholder"
|
||||
app:layout_constraintBottom_toTopOf="@id/podcast_upper_divider"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
@@ -100,9 +100,21 @@
|
||||
style="@style/Widget.Material3.Button.IconButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:icon="@drawable/ic_more_vert"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/podcast_play_button"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
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>
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
android:layout_height="52dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_margin="2dp"
|
||||
android:background="?attr/colorSurfaceVariant"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
48
app/src/main/res/layout/item_search_suggestion.xml
Normal file
48
app/src/main/res/layout/item_search_suggestion.xml
Normal file
@@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:paddingBottom="16dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/search_suggestion_icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@drawable/ic_history"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/search_suggestion_title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginHorizontal="12dp"
|
||||
android:text="@string/label_placeholder"
|
||||
android:textAppearance="?attr/textAppearanceBody2"
|
||||
app:layout_constraintBottom_toBottomOf="@id/search_suggestion_icon"
|
||||
app:layout_constraintEnd_toStartOf="@id/search_suggestion_delete_icon"
|
||||
app:layout_constraintStart_toEndOf="@id/search_suggestion_icon"
|
||||
app:layout_constraintTop_toTopOf="@id/search_suggestion_icon" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/search_suggestion_delete_icon"
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:background="?selectableItemBackgroundBorderless"
|
||||
android:src="@drawable/ic_close"
|
||||
app:layout_constraintBottom_toBottomOf="@id/search_suggestion_icon"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/search_suggestion_title"
|
||||
app:layout_constraintTop_toTopOf="@id/search_suggestion_icon"
|
||||
tools:ignore="ContentDescription" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
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>
|
||||
36
app/src/main/res/layout/layout_toolbar.xml
Normal file
36
app/src/main/res/layout/layout_toolbar.xml
Normal file
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.google.android.material.appbar.AppBarLayout xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/appbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/colorSurface"
|
||||
app:layout_scrollFlags="scroll|enterAlways|snap">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="28dp"
|
||||
android:layout_height="28dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:background="@drawable/ic_toolbar_tempo" />
|
||||
|
||||
<TextView
|
||||
style="@style/HeadlineMedium"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:text="@string/app_name" />
|
||||
</LinearLayout>
|
||||
</com.google.android.material.appbar.MaterialToolbar>
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
@@ -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>
|
||||
@@ -286,13 +286,16 @@
|
||||
android:id="@+id/directoryFragment"
|
||||
android:name="com.cappielloantonio.tempo.ui.fragment.DirectoryFragment"
|
||||
android:label="DirectoryFragment"
|
||||
tools:layout="@layout/fragment_directory" />
|
||||
tools:layout="@layout/fragment_directory">
|
||||
<action
|
||||
android:id="@+id/action_directoryFragment_to_directoryFragment"
|
||||
app:destination="@id/directoryFragment" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/indexFragment"
|
||||
android:name="com.cappielloantonio.tempo.ui.fragment.IndexFragment"
|
||||
android:label="IndexFragment"
|
||||
tools:layout="@layout/fragment_index">
|
||||
|
||||
<action
|
||||
android:id="@+id/action_indexFragment_to_directoryFragment"
|
||||
app:destination="@id/directoryFragment" />
|
||||
|
||||
@@ -141,4 +141,15 @@
|
||||
<item>12</item>
|
||||
<item>6</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="replay_gain_titles">
|
||||
<item>Disabled</item>
|
||||
<item>Track preferred</item>
|
||||
<item>Album preferred</item>
|
||||
</string-array>
|
||||
<string-array name="replay_gain_values">
|
||||
<item>disabled</item>
|
||||
<item>track</item>
|
||||
<item>album</item>
|
||||
</string-array>
|
||||
</resources>
|
||||
@@ -1,9 +1,11 @@
|
||||
<resources>
|
||||
<string name="activity_battery_optimizations_summary">Please disable battery optimizations for media playback while the screen is off.</string>
|
||||
<string name="activity_battery_optimizations_conclusion">If in trouble visit https://dontkillmyapp.com. It provides detailed instructions on how to disable any power-saving features that may affect app\'s performance.</string>
|
||||
<string name="activity_battery_optimizations_title">Battery Optimizations</string>
|
||||
<string name="activity_info_offline_mode">Offline mode</string>
|
||||
<string name="activity_negative_button">Ignore</string>
|
||||
<string name="activity_neutral_button">Disable</string>
|
||||
<string name="battery_optimization_negative_button">Ignore</string>
|
||||
<string name="battery_optimization_positive_button">Disable</string>
|
||||
<string name="battery_optimization_neutral_button">Don\'t ask again</string>
|
||||
<string name="album_bottom_sheet_add_to_queue">Add to queue</string>
|
||||
<string name="album_bottom_sheet_download_all">Download all</string>
|
||||
<string name="album_bottom_sheet_go_to_artist">Go to artist</string>
|
||||
@@ -52,6 +54,7 @@
|
||||
<string name="download_title_section">Downloads</string>
|
||||
<string name="empty_string" />
|
||||
<string name="error_required">Required</string>
|
||||
<string name="error_server_prefix">http or https prefix required</string>
|
||||
<string name="exo_download_notification_channel_name">Downloads</string>
|
||||
<string name="filter_info_selection">Select two or more filters</string>
|
||||
<string name="filter_title">Filter</string>
|
||||
@@ -74,6 +77,7 @@
|
||||
<string name="home_title_discovery">Discovery</string>
|
||||
<string name="home_title_recently_added">Recently added</string>
|
||||
<string name="home_title_recently_added_see_all_button">See all</string>
|
||||
<string name="home_title_discovery_shuffle_all_button">Shuffle all</string>
|
||||
<string name="home_title_starred_albums">★ Starred albums</string>
|
||||
<string name="home_title_starred_albums_see_all_button">See all</string>
|
||||
<string name="home_title_starred_artists">★ Starred artists</string>
|
||||
@@ -105,6 +109,7 @@
|
||||
<string name="menu_search_button">Search</string>
|
||||
<string name="menu_settings_button">Settings</string>
|
||||
<string name="player_playback_speed">%1$.2fx</string>
|
||||
<string name="player_server_priority">Server Priority</string>
|
||||
<string name="playlist_catalogue_title">Playlist Catalogue</string>
|
||||
<string name="playlist_catalogue_title_expanded">Browse Playlists</string>
|
||||
<string name="playlist_chooser_dialog_empty">No playlists created</string>
|
||||
@@ -129,6 +134,7 @@
|
||||
<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_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_title">No podcasts found!</string>
|
||||
<string name="podcast_info_empty_button">Click to hide the section\nThe effects will be visible on restart</string>
|
||||
@@ -166,10 +172,13 @@
|
||||
<string name="server_unreachable_dialog_summary">The requested server is unavailable. If you choose to continue this dialog will not appear for the next hour.</string>
|
||||
<string name="settings_about_summary">Tempo is an open source and lightweight music client for Subsonic, designed and built natively for Android.</string>
|
||||
<string name="settings_about_title">About</string>
|
||||
<string name="settings_audio_transcode_priority_summary">If enabled, Tempo will not force stream the track with the transcode settings below.</string>
|
||||
<string name="settings_audio_transcode_priority_title">Prioritize server transcode settings</string>
|
||||
<string name="settings_audio_transcode_priority_toast">Priority on transcoding of track given to server</string>
|
||||
<string name="settings_audio_transcode_format_mobile">Transcode format in mobile</string>
|
||||
<string name="settings_audio_transcode_format_wifi">Transcode format in Wi-Fi</string>
|
||||
<string name="settings_covers_cache">Size of artwork cache</string>
|
||||
<string name="settings_data_saving_mode_summary">In order to reduce data consumption, avoid downloading covers</string>
|
||||
<string name="settings_data_saving_mode_summary">In order to reduce data consumption, avoid downloading covers.</string>
|
||||
<string name="settings_data_saving_mode_title">Limit mobile data usage</string>
|
||||
<string name="settings_equalizer_summary">Adjust audio settings</string>
|
||||
<string name="settings_equalizer_title">Equalizer</string>
|
||||
@@ -181,6 +190,8 @@
|
||||
<string name="settings_max_bitrate_wifi">Bitrate in Wi-Fi</string>
|
||||
<string name="settings_max_bitrate_mobile">Bitrate in mobile</string>
|
||||
<string name="settings_media_cache">Size of media file cache</string>
|
||||
<string name="settings_music_directory">Show music directories</string>
|
||||
<string name="settings_music_directory_summary">If enabled, show the music directory section. Please note that for folder navigation to work properly, the server must support this feature.</string>
|
||||
<string name="settings_queue_syncing_title">Sync play queue for this user</string>
|
||||
<string name="settings_queue_syncing_countdown">Sync timer</string>
|
||||
<string name="settings_queue_syncing_summary">If enabled, the user will have the ability to save their play queue and will have the ability to load state when opening the application.</string>
|
||||
@@ -188,11 +199,13 @@
|
||||
<string name="settings_podcast_summary">If enabled, show the podcast section.</string>
|
||||
<string name="settings_radio">Show radio</string>
|
||||
<string name="settings_radio_summary">If enabled, show the radio section.</string>
|
||||
<string name="settings_replay_gain">Set replay gain mode</string>
|
||||
<string name="settings_rounded_corner">Rounded corners</string>
|
||||
<string name="settings_rounded_corner_summary">If enabled, sets a curvature angle for all rendered covers. The changes will take effect on restart.</string>
|
||||
<string name="settings_rounded_corner_size">Corners size</string>
|
||||
<string name="settings_rounded_corner_size_summary">Sets the magnitude of the curvature angle.</string>
|
||||
<string name="settings_scan_title">Scan library</string>
|
||||
<string name="settings_summary_replay_gain">Replay gain is a feature that allows you to adjust the volume level of audio tracks for a consistent listening experience. This setting is only effective if the track contains the necessary metadata.</string>
|
||||
<string name="settings_summary_syncing">Returns the state of the play queue for this user. This includes the tracks in the play queue, the currently playing track, and the position within this track. The server must support this feature.</string>
|
||||
<string name="settings_summary_transcoding">Priority given to the transcoding mode. If set to \"Direct play\" the bitrate of the file will not be changed.</string>
|
||||
<string name="settings_sync_starred_tracks_for_offline_use_summary">If enabled, starred tracks will be downloaded for offline use.</string>
|
||||
@@ -200,6 +213,7 @@
|
||||
<string name="settings_theme">Theme</string>
|
||||
<string name="settings_title_data">Data</string>
|
||||
<string name="settings_title_general">General</string>
|
||||
<string name="settings_title_replay_gain">Replay Gain</string>
|
||||
<string name="settings_title_syncing">Syncing</string>
|
||||
<string name="settings_title_transcoding">Transcoding</string>
|
||||
<string name="settings_title_ui">UI</string>
|
||||
@@ -233,7 +247,7 @@
|
||||
<string name="starred_sync_dialog_title">Sync starred tracks</string>
|
||||
<string name="undraw_url">https://undraw.co/</string>
|
||||
<string name="undraw_page">unDraw</string>
|
||||
<string name="undraw_thanks">A special thanks goes to unDraw without whose illustrations we could not have made this application more beautiful</string>
|
||||
<string name="undraw_thanks">A special thanks goes to unDraw without whose illustrations we could not have made this application more beautiful.</string>
|
||||
<string name="home_title_radio_station">Radio stations</string>
|
||||
<string name="home_title_new_releases">New releases</string>
|
||||
<string name="home_title_best_of">Best of</string>
|
||||
@@ -252,4 +266,6 @@
|
||||
<string name="menu_sort_name">Name</string>
|
||||
<string name="menu_sort_random">Random</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>
|
||||
@@ -51,6 +51,12 @@
|
||||
android:defaultValue="true"
|
||||
android:summary="@string/settings_radio_summary"
|
||||
android:key="radio_section_visibility" />
|
||||
|
||||
<SwitchPreference
|
||||
android:title="@string/settings_music_directory"
|
||||
android:defaultValue="true"
|
||||
android:summary="@string/settings_music_directory_summary"
|
||||
android:key="music_directory_section_visibility" />
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory app:title="@string/settings_title_data">
|
||||
@@ -96,6 +102,12 @@
|
||||
app:selectable="false"
|
||||
app:summary="@string/settings_summary_transcoding" />
|
||||
|
||||
<SwitchPreference
|
||||
android:title="@string/settings_audio_transcode_priority_title"
|
||||
android:defaultValue="false"
|
||||
android:summary="@string/settings_audio_transcode_priority_summary"
|
||||
android:key="audio_transcode_priority" />
|
||||
|
||||
<ListPreference
|
||||
app:defaultValue="raw"
|
||||
app:dialogTitle="@string/settings_audio_transcode_format_wifi"
|
||||
@@ -133,6 +145,22 @@
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory app:title="@string/settings_title_replay_gain">
|
||||
<Preference
|
||||
app:selectable="false"
|
||||
app:summary="@string/settings_summary_replay_gain" />
|
||||
|
||||
<ListPreference
|
||||
app:defaultValue="disabled"
|
||||
app:dialogTitle="@string/settings_replay_gain"
|
||||
app:entries="@array/replay_gain_titles"
|
||||
app:entryValues="@array/replay_gain_values"
|
||||
app:key="replay_gain_mode"
|
||||
app:title="@string/settings_replay_gain"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory app:title="@string/settings_title_syncing">
|
||||
<Preference
|
||||
app:selectable="false"
|
||||
@@ -141,7 +169,7 @@
|
||||
<SwitchPreference
|
||||
android:title="@string/settings_queue_syncing_title"
|
||||
android:defaultValue="false"
|
||||
android:summary="@string/settings_sync_starred_tracks_for_offline_use_summary"
|
||||
android:summary="@string/settings_queue_syncing_summary"
|
||||
android:key="queue_syncing" />
|
||||
|
||||
<ListPreference
|
||||
@@ -160,6 +188,7 @@
|
||||
app:summary="@string/settings_about_summary" />
|
||||
|
||||
<Preference
|
||||
android:key="version"
|
||||
app:summary="@string/settings_version_summary"
|
||||
app:title="@string/settings_version_title" />
|
||||
|
||||
|
||||
@@ -0,0 +1,253 @@
|
||||
package com.cappielloantonio.tempo.service
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.PendingIntent.FLAG_IMMUTABLE
|
||||
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
|
||||
import android.app.TaskStackBuilder
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.media3.common.*
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.exoplayer.ExoPlayer
|
||||
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
|
||||
import androidx.media3.exoplayer.source.TrackGroupArray
|
||||
import androidx.media3.exoplayer.trackselection.TrackSelectionArray
|
||||
import androidx.media3.session.*
|
||||
import androidx.media3.session.MediaSession.ControllerInfo
|
||||
import com.cappielloantonio.tempo.R
|
||||
import com.cappielloantonio.tempo.ui.activity.MainActivity
|
||||
import com.cappielloantonio.tempo.util.Constants
|
||||
import com.cappielloantonio.tempo.util.DownloadUtil
|
||||
import com.cappielloantonio.tempo.util.ReplayGainUtil
|
||||
import com.google.common.collect.ImmutableList
|
||||
import com.google.common.util.concurrent.Futures
|
||||
import com.google.common.util.concurrent.ListenableFuture
|
||||
|
||||
|
||||
@UnstableApi
|
||||
class MediaService : MediaLibraryService() {
|
||||
private val librarySessionCallback = CustomMediaLibrarySessionCallback()
|
||||
|
||||
private lateinit var player: ExoPlayer
|
||||
private lateinit var mediaLibrarySession: MediaLibrarySession
|
||||
private lateinit var customCommands: List<CommandButton>
|
||||
|
||||
private var customLayout = ImmutableList.of<CommandButton>()
|
||||
|
||||
companion object {
|
||||
private const val CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON =
|
||||
"android.media3.session.demo.SHUFFLE_ON"
|
||||
private const val CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF =
|
||||
"android.media3.session.demo.SHUFFLE_OFF"
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
initializeCustomCommands()
|
||||
initializePlayer()
|
||||
initializeMediaLibrarySession()
|
||||
initializePlayerListener()
|
||||
|
||||
setPlayer(player)
|
||||
}
|
||||
|
||||
override fun onGetSession(controllerInfo: ControllerInfo): MediaLibrarySession {
|
||||
return mediaLibrarySession
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
releasePlayer()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
private inner class CustomMediaLibrarySessionCallback : MediaLibrarySession.Callback {
|
||||
|
||||
override fun onConnect(
|
||||
session: MediaSession,
|
||||
controller: ControllerInfo
|
||||
): MediaSession.ConnectionResult {
|
||||
val connectionResult = super.onConnect(session, controller)
|
||||
val availableSessionCommands = connectionResult.availableSessionCommands.buildUpon()
|
||||
|
||||
customCommands.forEach { commandButton ->
|
||||
// TODO: Aggiungere i comandi personalizzati
|
||||
// commandButton.sessionCommand?.let { availableSessionCommands.add(it) }
|
||||
}
|
||||
|
||||
return MediaSession.ConnectionResult.accept(
|
||||
availableSessionCommands.build(),
|
||||
connectionResult.availablePlayerCommands
|
||||
)
|
||||
}
|
||||
|
||||
override fun onPostConnect(session: MediaSession, controller: ControllerInfo) {
|
||||
if (!customLayout.isEmpty() && controller.controllerVersion != 0) {
|
||||
ignoreFuture(mediaLibrarySession.setCustomLayout(controller, customLayout))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCustomCommand(
|
||||
session: MediaSession,
|
||||
controller: ControllerInfo,
|
||||
customCommand: SessionCommand,
|
||||
args: Bundle
|
||||
): ListenableFuture<SessionResult> {
|
||||
if (CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON == customCommand.customAction) {
|
||||
player.shuffleModeEnabled = true
|
||||
customLayout = ImmutableList.of(customCommands[1])
|
||||
session.setCustomLayout(customLayout)
|
||||
} else if (CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF == customCommand.customAction) {
|
||||
player.shuffleModeEnabled = false
|
||||
customLayout = ImmutableList.of(customCommands[0])
|
||||
session.setCustomLayout(customLayout)
|
||||
}
|
||||
|
||||
return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
|
||||
}
|
||||
|
||||
override fun onAddMediaItems(
|
||||
mediaSession: MediaSession,
|
||||
controller: ControllerInfo,
|
||||
mediaItems: List<MediaItem>
|
||||
): ListenableFuture<List<MediaItem>> {
|
||||
val updatedMediaItems = mediaItems.map {
|
||||
it.buildUpon()
|
||||
.setUri(it.requestMetadata.mediaUri)
|
||||
.setMediaMetadata(it.mediaMetadata)
|
||||
.setMimeType(MimeTypes.BASE_TYPE_AUDIO)
|
||||
.build()
|
||||
}
|
||||
return Futures.immediateFuture(updatedMediaItems)
|
||||
}
|
||||
}
|
||||
|
||||
private fun initializeCustomCommands() {
|
||||
customCommands =
|
||||
listOf(
|
||||
getShuffleCommandButton(
|
||||
SessionCommand(CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON, Bundle.EMPTY)
|
||||
),
|
||||
getShuffleCommandButton(
|
||||
SessionCommand(CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF, Bundle.EMPTY)
|
||||
)
|
||||
)
|
||||
|
||||
customLayout = ImmutableList.of(customCommands[0])
|
||||
}
|
||||
|
||||
private fun initializePlayer() {
|
||||
player = ExoPlayer.Builder(this)
|
||||
.setRenderersFactory(getRenderersFactory())
|
||||
.setMediaSourceFactory(getMediaSourceFactory())
|
||||
.setAudioAttributes(AudioAttributes.DEFAULT, true)
|
||||
.setHandleAudioBecomingNoisy(true)
|
||||
.setWakeMode(C.WAKE_MODE_NETWORK)
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun initializeMediaLibrarySession() {
|
||||
val sessionActivityPendingIntent =
|
||||
TaskStackBuilder.create(this).run {
|
||||
addNextIntent(Intent(this@MediaService, MainActivity::class.java))
|
||||
getPendingIntent(0, FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT)
|
||||
}
|
||||
|
||||
mediaLibrarySession =
|
||||
MediaLibrarySession.Builder(this, player, librarySessionCallback)
|
||||
.setSessionActivity(sessionActivityPendingIntent)
|
||||
.build()
|
||||
|
||||
if (!customLayout.isEmpty()) {
|
||||
mediaLibrarySession.setCustomLayout(customLayout)
|
||||
}
|
||||
}
|
||||
|
||||
private fun initializePlayerListener() {
|
||||
player.addListener(object : Player.Listener {
|
||||
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
|
||||
if (mediaItem == null) return
|
||||
|
||||
if (reason == Player.MEDIA_ITEM_TRANSITION_REASON_SEEK || reason == Player.MEDIA_ITEM_TRANSITION_REASON_AUTO) {
|
||||
MediaManager.setLastPlayedTimestamp(mediaItem)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTracksChanged(tracks: Tracks) {
|
||||
ReplayGainUtil.setReplayGain(player, tracks)
|
||||
}
|
||||
|
||||
override fun onIsPlayingChanged(isPlaying: Boolean) {
|
||||
if (!isPlaying) {
|
||||
MediaManager.setPlayingPausedTimestamp(
|
||||
player.currentMediaItem,
|
||||
player.currentPosition
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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(
|
||||
oldPosition: Player.PositionInfo,
|
||||
newPosition: Player.PositionInfo,
|
||||
reason: Int
|
||||
) {
|
||||
super.onPositionDiscontinuity(oldPosition, newPosition, reason)
|
||||
|
||||
if (reason == Player.DISCONTINUITY_REASON_AUTO_TRANSITION) {
|
||||
if (oldPosition.mediaItem?.mediaMetadata?.extras?.getString("type") == Constants.MEDIA_TYPE_MUSIC) {
|
||||
MediaManager.scrobble(oldPosition.mediaItem)
|
||||
MediaManager.saveChronology(oldPosition.mediaItem)
|
||||
}
|
||||
|
||||
if (newPosition.mediaItem?.mediaMetadata?.extras?.getString("type") == Constants.MEDIA_TYPE_MUSIC) {
|
||||
MediaManager.setLastPlayedTimestamp(newPosition.mediaItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun setPlayer(player: Player) {
|
||||
mediaLibrarySession.player = player
|
||||
}
|
||||
|
||||
private fun releasePlayer() {
|
||||
player.release()
|
||||
mediaLibrarySession.release()
|
||||
}
|
||||
|
||||
@SuppressLint("PrivateResource")
|
||||
private fun getShuffleCommandButton(sessionCommand: SessionCommand): CommandButton {
|
||||
val isOn = sessionCommand.customAction == CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON
|
||||
return CommandButton.Builder()
|
||||
.setDisplayName(
|
||||
getString(
|
||||
if (isOn) R.string.exo_controls_shuffle_on_description
|
||||
else R.string.exo_controls_shuffle_off_description
|
||||
)
|
||||
)
|
||||
.setSessionCommand(sessionCommand)
|
||||
.setIconResId(if (isOn) R.drawable.exo_icon_shuffle_off else R.drawable.exo_icon_shuffle_on)
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun ignoreFuture(customLayout: ListenableFuture<SessionResult>) {
|
||||
/* Do nothing. */
|
||||
}
|
||||
|
||||
private fun getRenderersFactory() = DownloadUtil.buildRenderersFactory(this, false)
|
||||
|
||||
private fun getMediaSourceFactory() =
|
||||
DefaultMediaSourceFactory(this).setDataSourceFactory(DownloadUtil.getDataSourceFactory(this))
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.cappielloantonio.tempo.ui.fragment;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
|
||||
import com.cappielloantonio.tempo.R;
|
||||
import com.cappielloantonio.tempo.databinding.FragmentToolbarBinding;
|
||||
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
||||
|
||||
@UnstableApi
|
||||
public class ToolbarFragment extends Fragment {
|
||||
private static final String TAG = "ToolbarFragment";
|
||||
|
||||
private FragmentToolbarBinding bind;
|
||||
private MainActivity activity;
|
||||
|
||||
public ToolbarFragment() {
|
||||
// Required empty public constructor
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
inflater.inflate(R.menu.main_page_menu, menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
activity = (MainActivity) getActivity();
|
||||
|
||||
bind = FragmentToolbarBinding.inflate(inflater, container, false);
|
||||
View view = bind.getRoot();
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||
if (item.getItemId() == R.id.action_search) {
|
||||
activity.navController.navigate(R.id.searchFragment);
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.action_settings) {
|
||||
activity.navController.navigate(R.id.settingsFragment);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.cappielloantonio.tempo.util;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
public class Flavors {
|
||||
public static void initializeCastContext(Context context) {
|
||||
|
||||
}
|
||||
}
|
||||
216
app/src/notquitemy/res/drawable/ic_splash_logo.xml
Normal file
216
app/src/notquitemy/res/drawable/ic_splash_logo.xml
Normal file
File diff suppressed because one or more lines are too long
201
app/src/notquitemy/res/drawable/ic_toolbar_tempo.xml
Normal file
201
app/src/notquitemy/res/drawable/ic_toolbar_tempo.xml
Normal file
File diff suppressed because one or more lines are too long
15
app/src/notquitemy/res/menu/main_page_menu.xml
Normal file
15
app/src/notquitemy/res/menu/main_page_menu.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item
|
||||
android:id="@+id/action_search"
|
||||
android:icon="@drawable/ic_search"
|
||||
android:title="@string/menu_search_button"
|
||||
app:showAsAction="always" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_settings"
|
||||
android:icon="@drawable/ic_settings"
|
||||
android:title="@string/menu_settings_button"
|
||||
app:showAsAction="never" />
|
||||
</menu>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user