mirror of
https://github.com/CappielloAntonio/tempo.git
synced 2026-01-31 14:43:36 +00:00
Compare commits
141 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c19db362d9 | ||
|
|
6a505eea4e | ||
|
|
99be2764d0 | ||
|
|
b656ad9e7f | ||
|
|
13d8bbf877 | ||
|
|
68527b2c91 | ||
|
|
347c074368 | ||
|
|
2a74f51f8d | ||
|
|
1a75369591 | ||
|
|
8f6e775ca9 | ||
|
|
bad0fa6c23 | ||
|
|
d4caa6f209 | ||
|
|
723bdf9771 | ||
|
|
49fbd85bb4 | ||
|
|
f040fbf0cf | ||
|
|
efb2213ab7 | ||
|
|
742ac6b17d | ||
|
|
ae7761cb96 | ||
|
|
c977982d64 | ||
|
|
28fc3dca36 | ||
|
|
f1cf65a371 | ||
|
|
beb1d29e8f | ||
|
|
1eda7cef9e | ||
|
|
1af92ad949 | ||
|
|
3fc9b35fe4 | ||
|
|
56b48dbd4d | ||
|
|
1cb371dc5a | ||
|
|
499001a269 | ||
|
|
4c9e47379d | ||
|
|
a0dbb5c81f | ||
|
|
dab53c6bbf | ||
|
|
c0a665c00a | ||
|
|
41b5c57240 | ||
|
|
1d65a79c20 | ||
|
|
efb6e72636 | ||
|
|
af83ffd608 | ||
|
|
0201077bc4 | ||
|
|
91d91d3024 | ||
|
|
9784a2b6c5 | ||
|
|
87a3301912 | ||
|
|
295795edc9 | ||
|
|
6120ab66ba | ||
|
|
7bea180c58 | ||
|
|
a29cee488e | ||
|
|
74b4b04693 | ||
|
|
b8b9c80bdc | ||
|
|
a50fc74117 | ||
|
|
c1af438a3a | ||
|
|
80b251cddc | ||
|
|
7d9a48818e | ||
|
|
ca3da0839b | ||
|
|
cf463d8fa1 | ||
|
|
635fdc4c5c | ||
|
|
3e913931c4 | ||
|
|
46420da038 | ||
|
|
d5b7619dd1 | ||
|
|
89b39123da | ||
|
|
5a43137984 | ||
|
|
761b07450f | ||
|
|
a0b67a06f4 | ||
|
|
59d7adb66d | ||
|
|
0c2f0d23cd | ||
|
|
dc201e6c8f | ||
|
|
9bf3399371 | ||
|
|
28565b691a | ||
|
|
7c6faf66c1 | ||
|
|
b160274859 | ||
|
|
3a1ced65d5 | ||
|
|
afeb72803d | ||
|
|
600c28c2a3 | ||
|
|
ec799fff96 | ||
|
|
60da08cdbc | ||
|
|
bdda3743db | ||
|
|
59fcf11ae3 | ||
|
|
77c80b7695 | ||
|
|
13f168f78c | ||
|
|
324eed7e02 | ||
|
|
4a99c7e9b1 | ||
|
|
01e5917642 | ||
|
|
ae00f4279e | ||
|
|
642c69eb96 | ||
|
|
ac69361735 | ||
|
|
e24efc4948 | ||
|
|
f8ad18ed5a | ||
|
|
17345372a2 | ||
|
|
db76494525 | ||
|
|
e87eda2757 | ||
|
|
06e2729aca | ||
|
|
37ffb88d67 | ||
|
|
3d4437151a | ||
|
|
af1961b185 | ||
|
|
c983e33522 | ||
|
|
b18daec708 | ||
|
|
6d20995e70 | ||
|
|
14cacd1bbc | ||
|
|
9d7acdb892 | ||
|
|
05325913f7 | ||
|
|
5733dca68a | ||
|
|
838d4496e5 | ||
|
|
4967363116 | ||
|
|
fc61308be5 | ||
|
|
5c4a292542 | ||
|
|
aa68c2c3d8 | ||
|
|
9c0ebca66f | ||
|
|
888f177597 | ||
|
|
c69fbcfa68 | ||
|
|
f044db142c | ||
|
|
9af7bc3ac8 | ||
|
|
cd87fcde26 | ||
|
|
9e1a6c804f | ||
|
|
4c15f6eb01 | ||
|
|
4ad2722e81 | ||
|
|
b267b904cc | ||
|
|
1ebe9ff8ba | ||
|
|
623a4956a5 | ||
|
|
6572b846c8 | ||
|
|
fe3ba9fb89 | ||
|
|
c4b9db303a | ||
|
|
aed52fdbf8 | ||
|
|
f9573b3eab | ||
|
|
7a8880ee68 | ||
|
|
68aae32d06 | ||
|
|
4b07f37378 | ||
|
|
4d573c6b9d | ||
|
|
10dcb2380c | ||
|
|
fcbe4377aa | ||
|
|
84db4060e6 | ||
|
|
b73a1c532b | ||
|
|
4fe27067e9 | ||
|
|
9c6981ed19 | ||
|
|
560ac2df68 | ||
|
|
260e6e9daa | ||
|
|
38a1368c76 | ||
|
|
9bf3139bd2 | ||
|
|
d389d1d62a | ||
|
|
e60c0e312c | ||
|
|
dc13beca49 | ||
|
|
6399488819 | ||
|
|
69c1b93d28 | ||
|
|
50c240793a | ||
|
|
80f7783b7d |
22
.github/workflows/github_release.yml
vendored
22
.github/workflows/github_release.yml
vendored
@@ -6,19 +6,15 @@ on:
|
|||||||
- '[0-9]+.[0-9]+.[0-9]+'
|
- '[0-9]+.[0-9]+.[0-9]+'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
setup:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Setup JDK 8
|
|
||||||
uses: actions/setup-java@v3
|
|
||||||
with:
|
|
||||||
distribution: 'temurin'
|
|
||||||
java-version: 8
|
|
||||||
|
|
||||||
build:
|
build:
|
||||||
needs: setup
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
- name: Setup JDK 17
|
||||||
|
uses: actions/setup-java@v2
|
||||||
|
with:
|
||||||
|
distribution: 'zulu'
|
||||||
|
java-version: '17'
|
||||||
|
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Cache Gradle and wrapper
|
- name: Cache Gradle and wrapper
|
||||||
@@ -34,13 +30,13 @@ jobs:
|
|||||||
|
|
||||||
- name: Build APK
|
- name: Build APK
|
||||||
id: build
|
id: build
|
||||||
run: bash ./gradlew assembleRelease
|
run: bash ./gradlew assembleTempoRelease
|
||||||
|
|
||||||
- name: Sign APK
|
- name: Sign APK
|
||||||
id: sign_apk
|
id: sign_apk
|
||||||
uses: r0adkll/sign-android-release@v1
|
uses: r0adkll/sign-android-release@v1
|
||||||
with:
|
with:
|
||||||
releaseDirectory: app/build/outputs/apk/release
|
releaseDirectory: app/build/outputs/apk/tempo/release
|
||||||
signingKeyBase64: ${{ secrets.KEYSTORE_BASE64 }}
|
signingKeyBase64: ${{ secrets.KEYSTORE_BASE64 }}
|
||||||
alias: ${{ secrets.KEY_ALIAS_GITHUB }}
|
alias: ${{ secrets.KEY_ALIAS_GITHUB }}
|
||||||
keyStorePassword: ${{ secrets.KEYSTORE_PASSWORD }}
|
keyStorePassword: ${{ secrets.KEYSTORE_PASSWORD }}
|
||||||
@@ -98,7 +94,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
asset_path: ${{steps.sign_apk.outputs.signedReleaseFile}}
|
asset_path: ${{steps.sign_apk.outputs.signedReleaseFile}}
|
||||||
asset_name: app-release.apk
|
asset_name: app-tempo-release.apk
|
||||||
asset_content_type: application/zip
|
asset_content_type: application/zip
|
||||||
|
|
||||||
# - name: Upload AAB
|
# - name: Upload AAB
|
||||||
|
|||||||
2
.idea/gradle.xml
generated
2
.idea/gradle.xml
generated
@@ -7,7 +7,7 @@
|
|||||||
<option name="testRunner" value="GRADLE" />
|
<option name="testRunner" value="GRADLE" />
|
||||||
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
||||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||||
<option name="gradleJvm" value="11" />
|
<option name="gradleJvm" value="17" />
|
||||||
<option name="modules">
|
<option name="modules">
|
||||||
<set>
|
<set>
|
||||||
<option value="$PROJECT_DIR$" />
|
<option value="$PROJECT_DIR$" />
|
||||||
|
|||||||
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@@ -191,7 +191,7 @@
|
|||||||
</map>
|
</map>
|
||||||
</option>
|
</option>
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="17" project-jdk-type="JavaSDK">
|
||||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectType">
|
<component name="ProjectType">
|
||||||
|
|||||||
@@ -33,12 +33,8 @@ Tempo does not rely on magic algorithms to decide what you should listen to. Ins
|
|||||||
<img src="mockup/feat/6_screenshot.png" width=200>
|
<img src="mockup/feat/6_screenshot.png" width=200>
|
||||||
<img src="mockup/feat/7_screenshot.png" width=200>
|
<img src="mockup/feat/7_screenshot.png" width=200>
|
||||||
<img src="mockup/feat/8_screenshot.png" width=200>
|
<img src="mockup/feat/8_screenshot.png" width=200>
|
||||||
<img src="mockup/feat/9_screenshot.png" width=200>
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
## Disclaimer
|
|
||||||
Tempo is currently under active development and is in alpha state. This means that the app may contain stability issues, bugs, or incomplete features. While we strive to provide a smooth and reliable experience, please be aware that using Tempo in its current state may not be as stable as a fully released version. I appreciate your understanding and patience as I work towards improving the app.
|
|
||||||
|
|
||||||
## Sponsors
|
## Sponsors
|
||||||
Tempo is an open-source project developed and maintained solely by me. I would like to express my heartfelt thanks to all the users who have shown their love and support for Tempo. Your contributions and encouragement mean a lot to me, and they help drive the development and improvement of the app.
|
Tempo is an open-source project developed and maintained solely by me. I would like to express my heartfelt thanks to all the users who have shown their love and support for Tempo. Your contributions and encouragement mean a lot to me, and they help drive the development and improvement of the app.
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ apply plugin: 'kotlin-android'
|
|||||||
apply plugin: 'kotlin-parcelize'
|
apply plugin: 'kotlin-parcelize'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 34
|
compileSdk 34
|
||||||
buildToolsVersion '33.0.0'
|
buildToolsVersion "34.0.0"
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 24
|
minSdkVersion 24
|
||||||
@@ -28,8 +28,8 @@ android {
|
|||||||
tempo {
|
tempo {
|
||||||
dimension "default"
|
dimension "default"
|
||||||
applicationId 'com.cappielloantonio.tempo'
|
applicationId 'com.cappielloantonio.tempo'
|
||||||
versionCode 11
|
versionCode 20
|
||||||
versionName '3.4.4'
|
versionName '3.5.5'
|
||||||
}
|
}
|
||||||
|
|
||||||
notquitemy {
|
notquitemy {
|
||||||
@@ -71,28 +71,29 @@ dependencies {
|
|||||||
// AndroidX
|
// AndroidX
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||||
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.2.0'
|
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.2.0'
|
||||||
implementation 'androidx.preference:preference-ktx:1.2.0'
|
implementation 'androidx.preference:preference-ktx:1.2.1'
|
||||||
implementation 'androidx.navigation:navigation-fragment-ktx:2.6.0'
|
implementation 'androidx.navigation:navigation-fragment-ktx:2.7.1'
|
||||||
implementation 'androidx.navigation:navigation-ui-ktx:2.6.0'
|
implementation 'androidx.navigation:navigation-ui-ktx:2.7.1'
|
||||||
implementation 'androidx.recyclerview:recyclerview:1.3.0'
|
implementation 'androidx.recyclerview:recyclerview:1.3.1'
|
||||||
implementation 'androidx.room:room-runtime:2.5.2'
|
implementation 'androidx.room:room-runtime:2.5.2'
|
||||||
implementation 'androidx.core:core-splashscreen:1.0.1'
|
implementation 'androidx.core:core-splashscreen:1.0.1'
|
||||||
|
implementation "androidx.appcompat:appcompat:1.6.1"
|
||||||
|
|
||||||
// Android Material
|
// Android Material
|
||||||
implementation 'com.google.android.material:material:1.9.0'
|
implementation 'com.google.android.material:material:1.9.0'
|
||||||
|
|
||||||
// Glide
|
// Glide
|
||||||
implementation 'com.github.bumptech.glide:glide:4.15.1'
|
implementation 'com.github.bumptech.glide:glide:4.16.0'
|
||||||
implementation 'com.github.bumptech.glide:annotations:4.15.1'
|
implementation 'com.github.bumptech.glide:annotations:4.16.0'
|
||||||
|
|
||||||
// Media3
|
// Media3
|
||||||
implementation 'androidx.media3:media3-session:1.0.2'
|
implementation 'androidx.media3:media3-session:1.1.1'
|
||||||
implementation 'androidx.media3:media3-common:1.0.2'
|
implementation 'androidx.media3:media3-common:1.1.1'
|
||||||
implementation 'androidx.media3:media3-exoplayer:1.0.2'
|
implementation 'androidx.media3:media3-exoplayer:1.1.1'
|
||||||
implementation 'androidx.media3:media3-ui:1.0.2'
|
implementation 'androidx.media3:media3-ui:1.1.1'
|
||||||
tempoImplementation 'androidx.media3:media3-cast:1.0.2'
|
tempoImplementation 'androidx.media3:media3-cast:1.1.1'
|
||||||
|
|
||||||
annotationProcessor 'com.github.bumptech.glide:compiler:4.15.1'
|
annotationProcessor 'com.github.bumptech.glide:compiler:4.16.0'
|
||||||
annotationProcessor 'androidx.room:room-compiler:2.5.2'
|
annotationProcessor 'androidx.room:room-compiler:2.5.2'
|
||||||
|
|
||||||
// Retrofit
|
// Retrofit
|
||||||
|
|||||||
1
app/proguard-rules.pro
vendored
1
app/proguard-rules.pro
vendored
@@ -22,3 +22,4 @@
|
|||||||
|
|
||||||
-keepattributes SourceFile, LineNumberTable
|
-keepattributes SourceFile, LineNumberTable
|
||||||
-keep public class * extends java.lang.Exception
|
-keep public class * extends java.lang.Exception
|
||||||
|
-keep class retrofit2.** { *; }
|
||||||
@@ -0,0 +1,746 @@
|
|||||||
|
{
|
||||||
|
"formatVersion": 1,
|
||||||
|
"database": {
|
||||||
|
"version": 1,
|
||||||
|
"identityHash": "1f4e50f90f58fb9cb53c89747d142fd9",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"tableName": "queue",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `track_order` INTEGER NOT NULL, `last_play` INTEGER NOT NULL, `playing_changed` INTEGER NOT NULL, `stream_id` TEXT, `parent_id` TEXT, `is_dir` INTEGER NOT NULL, `title` TEXT, `album` TEXT, `artist` TEXT, `track` INTEGER, `year` INTEGER, `genre` TEXT, `cover_art_id` TEXT, `size` INTEGER, `content_type` TEXT, `suffix` TEXT, `transcoding_content_type` TEXT, `transcoded_suffix` TEXT, `duration` INTEGER, `bitrate` INTEGER, `path` TEXT, `is_video` INTEGER NOT NULL, `user_rating` INTEGER, `average_rating` REAL, `play_count` INTEGER, `disc_number` INTEGER, `created` INTEGER, `starred` INTEGER, `album_id` TEXT, `artist_id` TEXT, `type` TEXT, `bookmark_position` INTEGER, `original_width` INTEGER, `original_height` INTEGER, PRIMARY KEY(`track_order`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "trackOrder",
|
||||||
|
"columnName": "track_order",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lastPlay",
|
||||||
|
"columnName": "last_play",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "playingChanged",
|
||||||
|
"columnName": "playing_changed",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "streamId",
|
||||||
|
"columnName": "stream_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "parentId",
|
||||||
|
"columnName": "parent_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isDir",
|
||||||
|
"columnName": "is_dir",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "title",
|
||||||
|
"columnName": "title",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "album",
|
||||||
|
"columnName": "album",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "artist",
|
||||||
|
"columnName": "artist",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "track",
|
||||||
|
"columnName": "track",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "year",
|
||||||
|
"columnName": "year",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "genre",
|
||||||
|
"columnName": "genre",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "coverArtId",
|
||||||
|
"columnName": "cover_art_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "size",
|
||||||
|
"columnName": "size",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "contentType",
|
||||||
|
"columnName": "content_type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "suffix",
|
||||||
|
"columnName": "suffix",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "transcodedContentType",
|
||||||
|
"columnName": "transcoding_content_type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "transcodedSuffix",
|
||||||
|
"columnName": "transcoded_suffix",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "duration",
|
||||||
|
"columnName": "duration",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "bitrate",
|
||||||
|
"columnName": "bitrate",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "path",
|
||||||
|
"columnName": "path",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isVideo",
|
||||||
|
"columnName": "is_video",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "userRating",
|
||||||
|
"columnName": "user_rating",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "averageRating",
|
||||||
|
"columnName": "average_rating",
|
||||||
|
"affinity": "REAL",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "playCount",
|
||||||
|
"columnName": "play_count",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "discNumber",
|
||||||
|
"columnName": "disc_number",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "created",
|
||||||
|
"columnName": "created",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "starred",
|
||||||
|
"columnName": "starred",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "albumId",
|
||||||
|
"columnName": "album_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "artistId",
|
||||||
|
"columnName": "artist_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "type",
|
||||||
|
"columnName": "type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "bookmarkPosition",
|
||||||
|
"columnName": "bookmark_position",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "originalWidth",
|
||||||
|
"columnName": "original_width",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "originalHeight",
|
||||||
|
"columnName": "original_height",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"track_order"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "server",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `server_name` TEXT NOT NULL, `username` TEXT NOT NULL, `password` TEXT NOT NULL, `address` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, `low_security` INTEGER NOT NULL DEFAULT false, PRIMARY KEY(`id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "serverId",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "serverName",
|
||||||
|
"columnName": "server_name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "username",
|
||||||
|
"columnName": "username",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "password",
|
||||||
|
"columnName": "password",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "address",
|
||||||
|
"columnName": "address",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "timestamp",
|
||||||
|
"columnName": "timestamp",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isLowSecurity",
|
||||||
|
"columnName": "low_security",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "recent_search",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`search` TEXT NOT NULL, PRIMARY KEY(`search`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "search",
|
||||||
|
"columnName": "search",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"search"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "download",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `playlist_id` TEXT, `playlist_name` TEXT, `download_state` INTEGER NOT NULL DEFAULT 1, `parent_id` TEXT, `is_dir` INTEGER NOT NULL, `title` TEXT, `album` TEXT, `artist` TEXT, `track` INTEGER, `year` INTEGER, `genre` TEXT, `cover_art_id` TEXT, `size` INTEGER, `content_type` TEXT, `suffix` TEXT, `transcoding_content_type` TEXT, `transcoded_suffix` TEXT, `duration` INTEGER, `bitrate` INTEGER, `path` TEXT, `is_video` INTEGER NOT NULL, `user_rating` INTEGER, `average_rating` REAL, `play_count` INTEGER, `disc_number` INTEGER, `created` INTEGER, `starred` INTEGER, `album_id` TEXT, `artist_id` TEXT, `type` TEXT, `bookmark_position` INTEGER, `original_width` INTEGER, `original_height` INTEGER, PRIMARY KEY(`id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "playlistId",
|
||||||
|
"columnName": "playlist_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "playlistName",
|
||||||
|
"columnName": "playlist_name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "downloadState",
|
||||||
|
"columnName": "download_state",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "parentId",
|
||||||
|
"columnName": "parent_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isDir",
|
||||||
|
"columnName": "is_dir",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "title",
|
||||||
|
"columnName": "title",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "album",
|
||||||
|
"columnName": "album",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "artist",
|
||||||
|
"columnName": "artist",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "track",
|
||||||
|
"columnName": "track",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "year",
|
||||||
|
"columnName": "year",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "genre",
|
||||||
|
"columnName": "genre",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "coverArtId",
|
||||||
|
"columnName": "cover_art_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "size",
|
||||||
|
"columnName": "size",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "contentType",
|
||||||
|
"columnName": "content_type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "suffix",
|
||||||
|
"columnName": "suffix",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "transcodedContentType",
|
||||||
|
"columnName": "transcoding_content_type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "transcodedSuffix",
|
||||||
|
"columnName": "transcoded_suffix",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "duration",
|
||||||
|
"columnName": "duration",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "bitrate",
|
||||||
|
"columnName": "bitrate",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "path",
|
||||||
|
"columnName": "path",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isVideo",
|
||||||
|
"columnName": "is_video",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "userRating",
|
||||||
|
"columnName": "user_rating",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "averageRating",
|
||||||
|
"columnName": "average_rating",
|
||||||
|
"affinity": "REAL",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "playCount",
|
||||||
|
"columnName": "play_count",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "discNumber",
|
||||||
|
"columnName": "disc_number",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "created",
|
||||||
|
"columnName": "created",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "starred",
|
||||||
|
"columnName": "starred",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "albumId",
|
||||||
|
"columnName": "album_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "artistId",
|
||||||
|
"columnName": "artist_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "type",
|
||||||
|
"columnName": "type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "bookmarkPosition",
|
||||||
|
"columnName": "bookmark_position",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "originalWidth",
|
||||||
|
"columnName": "original_width",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "originalHeight",
|
||||||
|
"columnName": "original_height",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "chronology",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, `server` TEXT, `parent_id` TEXT, `is_dir` INTEGER NOT NULL, `title` TEXT, `album` TEXT, `artist` TEXT, `track` INTEGER, `year` INTEGER, `genre` TEXT, `cover_art_id` TEXT, `size` INTEGER, `content_type` TEXT, `suffix` TEXT, `transcoding_content_type` TEXT, `transcoded_suffix` TEXT, `duration` INTEGER, `bitrate` INTEGER, `path` TEXT, `is_video` INTEGER NOT NULL, `user_rating` INTEGER, `average_rating` REAL, `play_count` INTEGER, `disc_number` INTEGER, `created` INTEGER, `starred` INTEGER, `album_id` TEXT, `artist_id` TEXT, `type` TEXT, `bookmark_position` INTEGER, `original_width` INTEGER, `original_height` INTEGER, PRIMARY KEY(`id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "timestamp",
|
||||||
|
"columnName": "timestamp",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "server",
|
||||||
|
"columnName": "server",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "parentId",
|
||||||
|
"columnName": "parent_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isDir",
|
||||||
|
"columnName": "is_dir",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "title",
|
||||||
|
"columnName": "title",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "album",
|
||||||
|
"columnName": "album",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "artist",
|
||||||
|
"columnName": "artist",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "track",
|
||||||
|
"columnName": "track",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "year",
|
||||||
|
"columnName": "year",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "genre",
|
||||||
|
"columnName": "genre",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "coverArtId",
|
||||||
|
"columnName": "cover_art_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "size",
|
||||||
|
"columnName": "size",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "contentType",
|
||||||
|
"columnName": "content_type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "suffix",
|
||||||
|
"columnName": "suffix",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "transcodedContentType",
|
||||||
|
"columnName": "transcoding_content_type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "transcodedSuffix",
|
||||||
|
"columnName": "transcoded_suffix",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "duration",
|
||||||
|
"columnName": "duration",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "bitrate",
|
||||||
|
"columnName": "bitrate",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "path",
|
||||||
|
"columnName": "path",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isVideo",
|
||||||
|
"columnName": "is_video",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "userRating",
|
||||||
|
"columnName": "user_rating",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "averageRating",
|
||||||
|
"columnName": "average_rating",
|
||||||
|
"affinity": "REAL",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "playCount",
|
||||||
|
"columnName": "play_count",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "discNumber",
|
||||||
|
"columnName": "disc_number",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "created",
|
||||||
|
"columnName": "created",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "starred",
|
||||||
|
"columnName": "starred",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "albumId",
|
||||||
|
"columnName": "album_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "artistId",
|
||||||
|
"columnName": "artist_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "type",
|
||||||
|
"columnName": "type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "bookmarkPosition",
|
||||||
|
"columnName": "bookmark_position",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "originalWidth",
|
||||||
|
"columnName": "original_width",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "originalHeight",
|
||||||
|
"columnName": "original_height",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"views": [],
|
||||||
|
"setupQueries": [
|
||||||
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||||
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '1f4e50f90f58fb9cb53c89747d142fd9')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,790 @@
|
|||||||
|
{
|
||||||
|
"formatVersion": 1,
|
||||||
|
"database": {
|
||||||
|
"version": 2,
|
||||||
|
"identityHash": "ff99e331b4c34a82c560588c4dd5735f",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"tableName": "queue",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `track_order` INTEGER NOT NULL, `last_play` INTEGER NOT NULL, `playing_changed` INTEGER NOT NULL, `stream_id` TEXT, `parent_id` TEXT, `is_dir` INTEGER NOT NULL, `title` TEXT, `album` TEXT, `artist` TEXT, `track` INTEGER, `year` INTEGER, `genre` TEXT, `cover_art_id` TEXT, `size` INTEGER, `content_type` TEXT, `suffix` TEXT, `transcoding_content_type` TEXT, `transcoded_suffix` TEXT, `duration` INTEGER, `bitrate` INTEGER, `path` TEXT, `is_video` INTEGER NOT NULL, `user_rating` INTEGER, `average_rating` REAL, `play_count` INTEGER, `disc_number` INTEGER, `created` INTEGER, `starred` INTEGER, `album_id` TEXT, `artist_id` TEXT, `type` TEXT, `bookmark_position` INTEGER, `original_width` INTEGER, `original_height` INTEGER, PRIMARY KEY(`track_order`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "trackOrder",
|
||||||
|
"columnName": "track_order",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lastPlay",
|
||||||
|
"columnName": "last_play",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "playingChanged",
|
||||||
|
"columnName": "playing_changed",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "streamId",
|
||||||
|
"columnName": "stream_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "parentId",
|
||||||
|
"columnName": "parent_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isDir",
|
||||||
|
"columnName": "is_dir",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "title",
|
||||||
|
"columnName": "title",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "album",
|
||||||
|
"columnName": "album",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "artist",
|
||||||
|
"columnName": "artist",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "track",
|
||||||
|
"columnName": "track",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "year",
|
||||||
|
"columnName": "year",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "genre",
|
||||||
|
"columnName": "genre",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "coverArtId",
|
||||||
|
"columnName": "cover_art_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "size",
|
||||||
|
"columnName": "size",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "contentType",
|
||||||
|
"columnName": "content_type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "suffix",
|
||||||
|
"columnName": "suffix",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "transcodedContentType",
|
||||||
|
"columnName": "transcoding_content_type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "transcodedSuffix",
|
||||||
|
"columnName": "transcoded_suffix",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "duration",
|
||||||
|
"columnName": "duration",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "bitrate",
|
||||||
|
"columnName": "bitrate",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "path",
|
||||||
|
"columnName": "path",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isVideo",
|
||||||
|
"columnName": "is_video",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "userRating",
|
||||||
|
"columnName": "user_rating",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "averageRating",
|
||||||
|
"columnName": "average_rating",
|
||||||
|
"affinity": "REAL",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "playCount",
|
||||||
|
"columnName": "play_count",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "discNumber",
|
||||||
|
"columnName": "disc_number",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "created",
|
||||||
|
"columnName": "created",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "starred",
|
||||||
|
"columnName": "starred",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "albumId",
|
||||||
|
"columnName": "album_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "artistId",
|
||||||
|
"columnName": "artist_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "type",
|
||||||
|
"columnName": "type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "bookmarkPosition",
|
||||||
|
"columnName": "bookmark_position",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "originalWidth",
|
||||||
|
"columnName": "original_width",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "originalHeight",
|
||||||
|
"columnName": "original_height",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"track_order"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "server",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `server_name` TEXT NOT NULL, `username` TEXT NOT NULL, `password` TEXT NOT NULL, `address` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, `low_security` INTEGER NOT NULL DEFAULT false, PRIMARY KEY(`id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "serverId",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "serverName",
|
||||||
|
"columnName": "server_name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "username",
|
||||||
|
"columnName": "username",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "password",
|
||||||
|
"columnName": "password",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "address",
|
||||||
|
"columnName": "address",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "timestamp",
|
||||||
|
"columnName": "timestamp",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isLowSecurity",
|
||||||
|
"columnName": "low_security",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "recent_search",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`search` TEXT NOT NULL, PRIMARY KEY(`search`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "search",
|
||||||
|
"columnName": "search",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"search"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "download",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `playlist_id` TEXT, `playlist_name` TEXT, `download_state` INTEGER NOT NULL DEFAULT 1, `parent_id` TEXT, `is_dir` INTEGER NOT NULL, `title` TEXT, `album` TEXT, `artist` TEXT, `track` INTEGER, `year` INTEGER, `genre` TEXT, `cover_art_id` TEXT, `size` INTEGER, `content_type` TEXT, `suffix` TEXT, `transcoding_content_type` TEXT, `transcoded_suffix` TEXT, `duration` INTEGER, `bitrate` INTEGER, `path` TEXT, `is_video` INTEGER NOT NULL, `user_rating` INTEGER, `average_rating` REAL, `play_count` INTEGER, `disc_number` INTEGER, `created` INTEGER, `starred` INTEGER, `album_id` TEXT, `artist_id` TEXT, `type` TEXT, `bookmark_position` INTEGER, `original_width` INTEGER, `original_height` INTEGER, PRIMARY KEY(`id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "playlistId",
|
||||||
|
"columnName": "playlist_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "playlistName",
|
||||||
|
"columnName": "playlist_name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "downloadState",
|
||||||
|
"columnName": "download_state",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "parentId",
|
||||||
|
"columnName": "parent_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isDir",
|
||||||
|
"columnName": "is_dir",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "title",
|
||||||
|
"columnName": "title",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "album",
|
||||||
|
"columnName": "album",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "artist",
|
||||||
|
"columnName": "artist",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "track",
|
||||||
|
"columnName": "track",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "year",
|
||||||
|
"columnName": "year",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "genre",
|
||||||
|
"columnName": "genre",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "coverArtId",
|
||||||
|
"columnName": "cover_art_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "size",
|
||||||
|
"columnName": "size",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "contentType",
|
||||||
|
"columnName": "content_type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "suffix",
|
||||||
|
"columnName": "suffix",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "transcodedContentType",
|
||||||
|
"columnName": "transcoding_content_type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "transcodedSuffix",
|
||||||
|
"columnName": "transcoded_suffix",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "duration",
|
||||||
|
"columnName": "duration",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "bitrate",
|
||||||
|
"columnName": "bitrate",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "path",
|
||||||
|
"columnName": "path",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isVideo",
|
||||||
|
"columnName": "is_video",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "userRating",
|
||||||
|
"columnName": "user_rating",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "averageRating",
|
||||||
|
"columnName": "average_rating",
|
||||||
|
"affinity": "REAL",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "playCount",
|
||||||
|
"columnName": "play_count",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "discNumber",
|
||||||
|
"columnName": "disc_number",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "created",
|
||||||
|
"columnName": "created",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "starred",
|
||||||
|
"columnName": "starred",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "albumId",
|
||||||
|
"columnName": "album_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "artistId",
|
||||||
|
"columnName": "artist_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "type",
|
||||||
|
"columnName": "type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "bookmarkPosition",
|
||||||
|
"columnName": "bookmark_position",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "originalWidth",
|
||||||
|
"columnName": "original_width",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "originalHeight",
|
||||||
|
"columnName": "original_height",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "chronology",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, `server` TEXT, `parent_id` TEXT, `is_dir` INTEGER NOT NULL, `title` TEXT, `album` TEXT, `artist` TEXT, `track` INTEGER, `year` INTEGER, `genre` TEXT, `cover_art_id` TEXT, `size` INTEGER, `content_type` TEXT, `suffix` TEXT, `transcoding_content_type` TEXT, `transcoded_suffix` TEXT, `duration` INTEGER, `bitrate` INTEGER, `path` TEXT, `is_video` INTEGER NOT NULL, `user_rating` INTEGER, `average_rating` REAL, `play_count` INTEGER, `disc_number` INTEGER, `created` INTEGER, `starred` INTEGER, `album_id` TEXT, `artist_id` TEXT, `type` TEXT, `bookmark_position` INTEGER, `original_width` INTEGER, `original_height` INTEGER, PRIMARY KEY(`id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "timestamp",
|
||||||
|
"columnName": "timestamp",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "server",
|
||||||
|
"columnName": "server",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "parentId",
|
||||||
|
"columnName": "parent_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isDir",
|
||||||
|
"columnName": "is_dir",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "title",
|
||||||
|
"columnName": "title",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "album",
|
||||||
|
"columnName": "album",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "artist",
|
||||||
|
"columnName": "artist",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "track",
|
||||||
|
"columnName": "track",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "year",
|
||||||
|
"columnName": "year",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "genre",
|
||||||
|
"columnName": "genre",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "coverArtId",
|
||||||
|
"columnName": "cover_art_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "size",
|
||||||
|
"columnName": "size",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "contentType",
|
||||||
|
"columnName": "content_type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "suffix",
|
||||||
|
"columnName": "suffix",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "transcodedContentType",
|
||||||
|
"columnName": "transcoding_content_type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "transcodedSuffix",
|
||||||
|
"columnName": "transcoded_suffix",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "duration",
|
||||||
|
"columnName": "duration",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "bitrate",
|
||||||
|
"columnName": "bitrate",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "path",
|
||||||
|
"columnName": "path",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isVideo",
|
||||||
|
"columnName": "is_video",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "userRating",
|
||||||
|
"columnName": "user_rating",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "averageRating",
|
||||||
|
"columnName": "average_rating",
|
||||||
|
"affinity": "REAL",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "playCount",
|
||||||
|
"columnName": "play_count",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "discNumber",
|
||||||
|
"columnName": "disc_number",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "created",
|
||||||
|
"columnName": "created",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "starred",
|
||||||
|
"columnName": "starred",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "albumId",
|
||||||
|
"columnName": "album_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "artistId",
|
||||||
|
"columnName": "artist_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "type",
|
||||||
|
"columnName": "type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "bookmarkPosition",
|
||||||
|
"columnName": "bookmark_position",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "originalWidth",
|
||||||
|
"columnName": "original_width",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "originalHeight",
|
||||||
|
"columnName": "original_height",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "favorite",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`timestamp` INTEGER NOT NULL, `songId` TEXT, `albumId` TEXT, `artistId` TEXT, `toStar` INTEGER NOT NULL, PRIMARY KEY(`timestamp`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "timestamp",
|
||||||
|
"columnName": "timestamp",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "songId",
|
||||||
|
"columnName": "songId",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "albumId",
|
||||||
|
"columnName": "albumId",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "artistId",
|
||||||
|
"columnName": "artistId",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "toStar",
|
||||||
|
"columnName": "toStar",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"timestamp"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"views": [],
|
||||||
|
"setupQueries": [
|
||||||
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||||
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'ff99e331b4c34a82c560588c4dd5735f')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,797 @@
|
|||||||
|
{
|
||||||
|
"formatVersion": 1,
|
||||||
|
"database": {
|
||||||
|
"version": 3,
|
||||||
|
"identityHash": "6ea111217793c58d54eabb1190dd92ec",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"tableName": "queue",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `track_order` INTEGER NOT NULL, `last_play` INTEGER NOT NULL, `playing_changed` INTEGER NOT NULL, `stream_id` TEXT, `parent_id` TEXT, `is_dir` INTEGER NOT NULL, `title` TEXT, `album` TEXT, `artist` TEXT, `track` INTEGER, `year` INTEGER, `genre` TEXT, `cover_art_id` TEXT, `size` INTEGER, `content_type` TEXT, `suffix` TEXT, `transcoding_content_type` TEXT, `transcoded_suffix` TEXT, `duration` INTEGER, `bitrate` INTEGER, `path` TEXT, `is_video` INTEGER NOT NULL, `user_rating` INTEGER, `average_rating` REAL, `play_count` INTEGER, `disc_number` INTEGER, `created` INTEGER, `starred` INTEGER, `album_id` TEXT, `artist_id` TEXT, `type` TEXT, `bookmark_position` INTEGER, `original_width` INTEGER, `original_height` INTEGER, PRIMARY KEY(`track_order`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "trackOrder",
|
||||||
|
"columnName": "track_order",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lastPlay",
|
||||||
|
"columnName": "last_play",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "playingChanged",
|
||||||
|
"columnName": "playing_changed",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "streamId",
|
||||||
|
"columnName": "stream_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "parentId",
|
||||||
|
"columnName": "parent_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isDir",
|
||||||
|
"columnName": "is_dir",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "title",
|
||||||
|
"columnName": "title",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "album",
|
||||||
|
"columnName": "album",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "artist",
|
||||||
|
"columnName": "artist",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "track",
|
||||||
|
"columnName": "track",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "year",
|
||||||
|
"columnName": "year",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "genre",
|
||||||
|
"columnName": "genre",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "coverArtId",
|
||||||
|
"columnName": "cover_art_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "size",
|
||||||
|
"columnName": "size",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "contentType",
|
||||||
|
"columnName": "content_type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "suffix",
|
||||||
|
"columnName": "suffix",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "transcodedContentType",
|
||||||
|
"columnName": "transcoding_content_type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "transcodedSuffix",
|
||||||
|
"columnName": "transcoded_suffix",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "duration",
|
||||||
|
"columnName": "duration",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "bitrate",
|
||||||
|
"columnName": "bitrate",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "path",
|
||||||
|
"columnName": "path",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isVideo",
|
||||||
|
"columnName": "is_video",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "userRating",
|
||||||
|
"columnName": "user_rating",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "averageRating",
|
||||||
|
"columnName": "average_rating",
|
||||||
|
"affinity": "REAL",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "playCount",
|
||||||
|
"columnName": "play_count",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "discNumber",
|
||||||
|
"columnName": "disc_number",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "created",
|
||||||
|
"columnName": "created",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "starred",
|
||||||
|
"columnName": "starred",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "albumId",
|
||||||
|
"columnName": "album_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "artistId",
|
||||||
|
"columnName": "artist_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "type",
|
||||||
|
"columnName": "type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "bookmarkPosition",
|
||||||
|
"columnName": "bookmark_position",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "originalWidth",
|
||||||
|
"columnName": "original_width",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "originalHeight",
|
||||||
|
"columnName": "original_height",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"track_order"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "server",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `server_name` TEXT NOT NULL, `username` TEXT NOT NULL, `password` TEXT NOT NULL, `address` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, `low_security` INTEGER NOT NULL DEFAULT false, PRIMARY KEY(`id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "serverId",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "serverName",
|
||||||
|
"columnName": "server_name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "username",
|
||||||
|
"columnName": "username",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "password",
|
||||||
|
"columnName": "password",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "address",
|
||||||
|
"columnName": "address",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "timestamp",
|
||||||
|
"columnName": "timestamp",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isLowSecurity",
|
||||||
|
"columnName": "low_security",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "false"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "recent_search",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`search` TEXT NOT NULL, PRIMARY KEY(`search`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "search",
|
||||||
|
"columnName": "search",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"search"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "download",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `playlist_id` TEXT, `playlist_name` TEXT, `download_state` INTEGER NOT NULL DEFAULT 1, `download_uri` TEXT DEFAULT '', `parent_id` TEXT, `is_dir` INTEGER NOT NULL, `title` TEXT, `album` TEXT, `artist` TEXT, `track` INTEGER, `year` INTEGER, `genre` TEXT, `cover_art_id` TEXT, `size` INTEGER, `content_type` TEXT, `suffix` TEXT, `transcoding_content_type` TEXT, `transcoded_suffix` TEXT, `duration` INTEGER, `bitrate` INTEGER, `path` TEXT, `is_video` INTEGER NOT NULL, `user_rating` INTEGER, `average_rating` REAL, `play_count` INTEGER, `disc_number` INTEGER, `created` INTEGER, `starred` INTEGER, `album_id` TEXT, `artist_id` TEXT, `type` TEXT, `bookmark_position` INTEGER, `original_width` INTEGER, `original_height` INTEGER, PRIMARY KEY(`id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "playlistId",
|
||||||
|
"columnName": "playlist_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "playlistName",
|
||||||
|
"columnName": "playlist_name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "downloadState",
|
||||||
|
"columnName": "download_state",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true,
|
||||||
|
"defaultValue": "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "downloadUri",
|
||||||
|
"columnName": "download_uri",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false,
|
||||||
|
"defaultValue": "''"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "parentId",
|
||||||
|
"columnName": "parent_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isDir",
|
||||||
|
"columnName": "is_dir",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "title",
|
||||||
|
"columnName": "title",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "album",
|
||||||
|
"columnName": "album",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "artist",
|
||||||
|
"columnName": "artist",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "track",
|
||||||
|
"columnName": "track",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "year",
|
||||||
|
"columnName": "year",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "genre",
|
||||||
|
"columnName": "genre",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "coverArtId",
|
||||||
|
"columnName": "cover_art_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "size",
|
||||||
|
"columnName": "size",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "contentType",
|
||||||
|
"columnName": "content_type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "suffix",
|
||||||
|
"columnName": "suffix",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "transcodedContentType",
|
||||||
|
"columnName": "transcoding_content_type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "transcodedSuffix",
|
||||||
|
"columnName": "transcoded_suffix",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "duration",
|
||||||
|
"columnName": "duration",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "bitrate",
|
||||||
|
"columnName": "bitrate",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "path",
|
||||||
|
"columnName": "path",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isVideo",
|
||||||
|
"columnName": "is_video",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "userRating",
|
||||||
|
"columnName": "user_rating",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "averageRating",
|
||||||
|
"columnName": "average_rating",
|
||||||
|
"affinity": "REAL",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "playCount",
|
||||||
|
"columnName": "play_count",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "discNumber",
|
||||||
|
"columnName": "disc_number",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "created",
|
||||||
|
"columnName": "created",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "starred",
|
||||||
|
"columnName": "starred",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "albumId",
|
||||||
|
"columnName": "album_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "artistId",
|
||||||
|
"columnName": "artist_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "type",
|
||||||
|
"columnName": "type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "bookmarkPosition",
|
||||||
|
"columnName": "bookmark_position",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "originalWidth",
|
||||||
|
"columnName": "original_width",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "originalHeight",
|
||||||
|
"columnName": "original_height",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "chronology",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, `server` TEXT, `parent_id` TEXT, `is_dir` INTEGER NOT NULL, `title` TEXT, `album` TEXT, `artist` TEXT, `track` INTEGER, `year` INTEGER, `genre` TEXT, `cover_art_id` TEXT, `size` INTEGER, `content_type` TEXT, `suffix` TEXT, `transcoding_content_type` TEXT, `transcoded_suffix` TEXT, `duration` INTEGER, `bitrate` INTEGER, `path` TEXT, `is_video` INTEGER NOT NULL, `user_rating` INTEGER, `average_rating` REAL, `play_count` INTEGER, `disc_number` INTEGER, `created` INTEGER, `starred` INTEGER, `album_id` TEXT, `artist_id` TEXT, `type` TEXT, `bookmark_position` INTEGER, `original_width` INTEGER, `original_height` INTEGER, PRIMARY KEY(`id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "timestamp",
|
||||||
|
"columnName": "timestamp",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "server",
|
||||||
|
"columnName": "server",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "parentId",
|
||||||
|
"columnName": "parent_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isDir",
|
||||||
|
"columnName": "is_dir",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "title",
|
||||||
|
"columnName": "title",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "album",
|
||||||
|
"columnName": "album",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "artist",
|
||||||
|
"columnName": "artist",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "track",
|
||||||
|
"columnName": "track",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "year",
|
||||||
|
"columnName": "year",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "genre",
|
||||||
|
"columnName": "genre",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "coverArtId",
|
||||||
|
"columnName": "cover_art_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "size",
|
||||||
|
"columnName": "size",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "contentType",
|
||||||
|
"columnName": "content_type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "suffix",
|
||||||
|
"columnName": "suffix",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "transcodedContentType",
|
||||||
|
"columnName": "transcoding_content_type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "transcodedSuffix",
|
||||||
|
"columnName": "transcoded_suffix",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "duration",
|
||||||
|
"columnName": "duration",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "bitrate",
|
||||||
|
"columnName": "bitrate",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "path",
|
||||||
|
"columnName": "path",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isVideo",
|
||||||
|
"columnName": "is_video",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "userRating",
|
||||||
|
"columnName": "user_rating",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "averageRating",
|
||||||
|
"columnName": "average_rating",
|
||||||
|
"affinity": "REAL",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "playCount",
|
||||||
|
"columnName": "play_count",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "discNumber",
|
||||||
|
"columnName": "disc_number",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "created",
|
||||||
|
"columnName": "created",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "starred",
|
||||||
|
"columnName": "starred",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "albumId",
|
||||||
|
"columnName": "album_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "artistId",
|
||||||
|
"columnName": "artist_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "type",
|
||||||
|
"columnName": "type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "bookmarkPosition",
|
||||||
|
"columnName": "bookmark_position",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "originalWidth",
|
||||||
|
"columnName": "original_width",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "originalHeight",
|
||||||
|
"columnName": "original_height",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "favorite",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`timestamp` INTEGER NOT NULL, `songId` TEXT, `albumId` TEXT, `artistId` TEXT, `toStar` INTEGER NOT NULL, PRIMARY KEY(`timestamp`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "timestamp",
|
||||||
|
"columnName": "timestamp",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "songId",
|
||||||
|
"columnName": "songId",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "albumId",
|
||||||
|
"columnName": "albumId",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "artistId",
|
||||||
|
"columnName": "artistId",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "toStar",
|
||||||
|
"columnName": "toStar",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"timestamp"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"views": [],
|
||||||
|
"setupQueries": [
|
||||||
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||||
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '6ea111217793c58d54eabb1190dd92ec')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,45 +6,66 @@
|
|||||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name="App"
|
android:name="App"
|
||||||
|
android:allowBackup="false"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
|
android:localeConfig="@xml/locale_config"
|
||||||
android:roundIcon="@mipmap/ic_launcher"
|
android:roundIcon="@mipmap/ic_launcher"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/AppTheme.SplashScreen"
|
android:theme="@style/AppTheme.SplashScreen"
|
||||||
android:usesCleartextTraffic="true"
|
android:usesCleartextTraffic="true">
|
||||||
android:allowBackup="false">
|
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
|
android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
|
||||||
android:value="androidx.media3.cast.DefaultCastOptionsProvider" />
|
android:value="androidx.media3.cast.DefaultCastOptionsProvider" />
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="androidx.car.app.TintableAttributionIcon"
|
||||||
|
android:resource="@drawable/ic_graphic_eq" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.activity.MainActivity"
|
android:name=".ui.activity.MainActivity"
|
||||||
android:windowSoftInputMode="adjustPan|adjustResize"
|
android:exported="true"
|
||||||
android:exported="true">
|
android:windowSoftInputMode="adjustPan|adjustResize">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name=".service.MediaService"
|
android:name=".service.MediaService"
|
||||||
android:foregroundServiceType="mediaPlayback"
|
android:exported="true"
|
||||||
android:exported="true">
|
android:foregroundServiceType="mediaPlayback">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="androidx.media3.session.MediaLibraryService" />
|
<action android:name="androidx.media3.session.MediaLibraryService" />
|
||||||
<action android:name="androidx.media3.session.MediaBrowserService" />
|
<action android:name="androidx.media3.session.MediaBrowserService" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</service>
|
</service>
|
||||||
<service android:name=".service.DownloaderService"
|
|
||||||
android:exported="true">
|
<service
|
||||||
|
android:name=".service.DownloaderService"
|
||||||
|
android:exported="true"
|
||||||
|
android:foregroundServiceType="dataSync">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="androidx.media3.exoplayer.downloadService.action.RESTART" />
|
<action android:name="androidx.media3.exoplayer.downloadService.action.RESTART" />
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
|
||||||
|
android:enabled="false"
|
||||||
|
android:exported="false">
|
||||||
|
<meta-data
|
||||||
|
android:name="autoStoreLocales"
|
||||||
|
android:value="true" />
|
||||||
|
</service>
|
||||||
</application>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
||||||
@@ -10,7 +10,6 @@ import com.cappielloantonio.tempo.helper.ThemeHelper;
|
|||||||
import com.cappielloantonio.tempo.subsonic.Subsonic;
|
import com.cappielloantonio.tempo.subsonic.Subsonic;
|
||||||
import com.cappielloantonio.tempo.subsonic.SubsonicPreferences;
|
import com.cappielloantonio.tempo.subsonic.SubsonicPreferences;
|
||||||
import com.cappielloantonio.tempo.util.Preferences;
|
import com.cappielloantonio.tempo.util.Preferences;
|
||||||
import com.google.android.material.color.DynamicColors;
|
|
||||||
|
|
||||||
public class App extends Application {
|
public class App extends Application {
|
||||||
private static App instance;
|
private static App instance;
|
||||||
@@ -22,7 +21,6 @@ public class App extends Application {
|
|||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
super.onCreate();
|
super.onCreate();
|
||||||
|
|
||||||
DynamicColors.applyToActivitiesIfAvailable(this);
|
|
||||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
|
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
|
||||||
String themePref = sharedPreferences.getString(Preferences.THEME, ThemeHelper.DEFAULT_MODE);
|
String themePref = sharedPreferences.getString(Preferences.THEME, ThemeHelper.DEFAULT_MODE);
|
||||||
ThemeHelper.applyTheme(themePref);
|
ThemeHelper.applyTheme(themePref);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.cappielloantonio.tempo.database;
|
package com.cappielloantonio.tempo.database;
|
||||||
|
|
||||||
|
import androidx.room.AutoMigration;
|
||||||
import androidx.room.Database;
|
import androidx.room.Database;
|
||||||
import androidx.room.Room;
|
import androidx.room.Room;
|
||||||
import androidx.room.RoomDatabase;
|
import androidx.room.RoomDatabase;
|
||||||
@@ -9,19 +10,21 @@ import com.cappielloantonio.tempo.App;
|
|||||||
import com.cappielloantonio.tempo.database.converter.DateConverters;
|
import com.cappielloantonio.tempo.database.converter.DateConverters;
|
||||||
import com.cappielloantonio.tempo.database.dao.ChronologyDao;
|
import com.cappielloantonio.tempo.database.dao.ChronologyDao;
|
||||||
import com.cappielloantonio.tempo.database.dao.DownloadDao;
|
import com.cappielloantonio.tempo.database.dao.DownloadDao;
|
||||||
|
import com.cappielloantonio.tempo.database.dao.FavoriteDao;
|
||||||
import com.cappielloantonio.tempo.database.dao.QueueDao;
|
import com.cappielloantonio.tempo.database.dao.QueueDao;
|
||||||
import com.cappielloantonio.tempo.database.dao.RecentSearchDao;
|
import com.cappielloantonio.tempo.database.dao.RecentSearchDao;
|
||||||
import com.cappielloantonio.tempo.database.dao.ServerDao;
|
import com.cappielloantonio.tempo.database.dao.ServerDao;
|
||||||
import com.cappielloantonio.tempo.model.Chronology;
|
import com.cappielloantonio.tempo.model.Chronology;
|
||||||
import com.cappielloantonio.tempo.model.Download;
|
import com.cappielloantonio.tempo.model.Download;
|
||||||
|
import com.cappielloantonio.tempo.model.Favorite;
|
||||||
import com.cappielloantonio.tempo.model.Queue;
|
import com.cappielloantonio.tempo.model.Queue;
|
||||||
import com.cappielloantonio.tempo.model.RecentSearch;
|
import com.cappielloantonio.tempo.model.RecentSearch;
|
||||||
import com.cappielloantonio.tempo.model.Server;
|
import com.cappielloantonio.tempo.model.Server;
|
||||||
|
|
||||||
@Database(
|
@Database(
|
||||||
version = 1,
|
version = 3,
|
||||||
entities = {Queue.class, Server.class, RecentSearch.class, Download.class, Chronology.class}
|
entities = {Queue.class, Server.class, RecentSearch.class, Download.class, Chronology.class, Favorite.class},
|
||||||
// autoMigrations = {@AutoMigration(from = 61, to = 62)}
|
autoMigrations = {@AutoMigration(from = 2, to = 3)}
|
||||||
)
|
)
|
||||||
@TypeConverters({DateConverters.class})
|
@TypeConverters({DateConverters.class})
|
||||||
public abstract class AppDatabase extends RoomDatabase {
|
public abstract class AppDatabase extends RoomDatabase {
|
||||||
@@ -47,4 +50,6 @@ public abstract class AppDatabase extends RoomDatabase {
|
|||||||
public abstract DownloadDao downloadDao();
|
public abstract DownloadDao downloadDao();
|
||||||
|
|
||||||
public abstract ChronologyDao chronologyDao();
|
public abstract ChronologyDao chronologyDao();
|
||||||
|
|
||||||
|
public abstract FavoriteDao favoriteDao();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,9 @@ public interface DownloadDao {
|
|||||||
@Query("SELECT * FROM download WHERE download_state = 1 ORDER BY artist, album, track ASC")
|
@Query("SELECT * FROM download WHERE download_state = 1 ORDER BY artist, album, track ASC")
|
||||||
LiveData<List<Download>> getAll();
|
LiveData<List<Download>> getAll();
|
||||||
|
|
||||||
|
@Query("SELECT * FROM download WHERE id = :id")
|
||||||
|
Download getOne(String id);
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
void insert(Download download);
|
void insert(Download download);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package com.cappielloantonio.tempo.database.dao;
|
||||||
|
|
||||||
|
import androidx.room.Dao;
|
||||||
|
import androidx.room.Delete;
|
||||||
|
import androidx.room.Insert;
|
||||||
|
import androidx.room.OnConflictStrategy;
|
||||||
|
import androidx.room.Query;
|
||||||
|
|
||||||
|
import com.cappielloantonio.tempo.model.Favorite;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
public interface FavoriteDao {
|
||||||
|
@Query("SELECT * FROM favorite")
|
||||||
|
List<Favorite> getAll();
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||||
|
void insert(Favorite favorite);
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
void delete(Favorite favorite);
|
||||||
|
|
||||||
|
@Query("DELETE FROM favorite")
|
||||||
|
void deleteAll();
|
||||||
|
}
|
||||||
@@ -12,7 +12,7 @@ import java.util.List;
|
|||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
public interface RecentSearchDao {
|
public interface RecentSearchDao {
|
||||||
@Query("SELECT * FROM recent_search ORDER BY search ASC")
|
@Query("SELECT * FROM recent_search ORDER BY search DESC")
|
||||||
List<String> getRecent();
|
List<String> getRecent();
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import com.bumptech.glide.request.RequestOptions;
|
|||||||
import com.bumptech.glide.signature.ObjectKey;
|
import com.bumptech.glide.signature.ObjectKey;
|
||||||
import com.cappielloantonio.tempo.App;
|
import com.cappielloantonio.tempo.App;
|
||||||
import com.cappielloantonio.tempo.util.Preferences;
|
import com.cappielloantonio.tempo.util.Preferences;
|
||||||
|
import com.cappielloantonio.tempo.util.Util;
|
||||||
import com.google.android.material.elevation.SurfaceColors;
|
import com.google.android.material.elevation.SurfaceColors;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -46,7 +47,7 @@ public class CustomGlideRequest {
|
|||||||
uri.append("getCoverArt");
|
uri.append("getCoverArt");
|
||||||
|
|
||||||
if (params.containsKey("u") && params.get("u") != null)
|
if (params.containsKey("u") && params.get("u") != null)
|
||||||
uri.append("?u=").append(params.get("u"));
|
uri.append("?u=").append(Util.encode(params.get("u")));
|
||||||
if (params.containsKey("p") && params.get("p") != null)
|
if (params.containsKey("p") && params.get("p") != null)
|
||||||
uri.append("&p=").append(params.get("p"));
|
uri.append("&p=").append(params.get("p"));
|
||||||
if (params.containsKey("s") && params.get("s") != null)
|
if (params.containsKey("s") && params.get("s") != null)
|
||||||
|
|||||||
@@ -0,0 +1,197 @@
|
|||||||
|
package com.cappielloantonio.tempo.helper.recyclerview;
|
||||||
|
|
||||||
|
import android.animation.Animator;
|
||||||
|
import android.animation.AnimatorListenerAdapter;
|
||||||
|
import android.animation.ObjectAnimator;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.IdRes;
|
||||||
|
import androidx.annotation.LayoutRes;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.core.view.ViewCompat;
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
public class FastScrollbar extends LinearLayout {
|
||||||
|
private static final int BUBBLE_ANIMATION_DURATION = 100;
|
||||||
|
private static final int TRACK_SNAP_RANGE = 5;
|
||||||
|
|
||||||
|
private TextView bubble;
|
||||||
|
private View handle;
|
||||||
|
private RecyclerView recyclerView;
|
||||||
|
private int height;
|
||||||
|
private boolean isInitialized = false;
|
||||||
|
private ObjectAnimator currentAnimator = null;
|
||||||
|
|
||||||
|
private final RecyclerView.OnScrollListener onScrollListener = new RecyclerView.OnScrollListener() {
|
||||||
|
@Override
|
||||||
|
public void onScrolled(@NonNull final RecyclerView recyclerView, final int dx, final int dy) {
|
||||||
|
updateBubbleAndHandlePosition();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public interface BubbleTextGetter {
|
||||||
|
String getTextToShowInBubble(int pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FastScrollbar(final Context context, final AttributeSet attrs, final int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
init(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FastScrollbar(final Context context) {
|
||||||
|
super(context);
|
||||||
|
init(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FastScrollbar(final Context context, final AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
init(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void init(Context context) {
|
||||||
|
if (isInitialized) return;
|
||||||
|
isInitialized = true;
|
||||||
|
setOrientation(HORIZONTAL);
|
||||||
|
setClipChildren(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setViewsToUse(@LayoutRes int layoutResId, @IdRes int bubbleResId, @IdRes int handleResId) {
|
||||||
|
final LayoutInflater inflater = LayoutInflater.from(getContext());
|
||||||
|
inflater.inflate(layoutResId, this, true);
|
||||||
|
bubble = findViewById(bubbleResId);
|
||||||
|
if (bubble != null) bubble.setVisibility(INVISIBLE);
|
||||||
|
handle = findViewById(handleResId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||||
|
super.onSizeChanged(w, h, oldw, oldh);
|
||||||
|
height = h;
|
||||||
|
updateBubbleAndHandlePosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onTouchEvent(@NonNull MotionEvent event) {
|
||||||
|
final int action = event.getAction();
|
||||||
|
switch (action) {
|
||||||
|
case MotionEvent.ACTION_DOWN:
|
||||||
|
if (event.getX() < handle.getX() - ViewCompat.getPaddingStart(handle)) return false;
|
||||||
|
if (currentAnimator != null) currentAnimator.cancel();
|
||||||
|
if (bubble != null && bubble.getVisibility() == INVISIBLE) showBubble();
|
||||||
|
handle.setSelected(true);
|
||||||
|
case MotionEvent.ACTION_MOVE:
|
||||||
|
final float y = event.getY();
|
||||||
|
setBubbleAndHandlePosition(y);
|
||||||
|
setRecyclerViewPosition(y);
|
||||||
|
return true;
|
||||||
|
case MotionEvent.ACTION_UP:
|
||||||
|
case MotionEvent.ACTION_CANCEL:
|
||||||
|
handle.setSelected(false);
|
||||||
|
hideBubble();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return super.onTouchEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRecyclerView(final RecyclerView recyclerView) {
|
||||||
|
if (this.recyclerView != recyclerView) {
|
||||||
|
if (this.recyclerView != null)
|
||||||
|
this.recyclerView.removeOnScrollListener(onScrollListener);
|
||||||
|
this.recyclerView = recyclerView;
|
||||||
|
if (this.recyclerView == null) return;
|
||||||
|
recyclerView.addOnScrollListener(onScrollListener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDetachedFromWindow() {
|
||||||
|
super.onDetachedFromWindow();
|
||||||
|
if (recyclerView != null) {
|
||||||
|
recyclerView.removeOnScrollListener(onScrollListener);
|
||||||
|
recyclerView = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setRecyclerViewPosition(float y) {
|
||||||
|
if (recyclerView != null) {
|
||||||
|
final int itemCount = recyclerView.getAdapter().getItemCount();
|
||||||
|
float proportion;
|
||||||
|
if (handle.getY() == 0) proportion = 0f;
|
||||||
|
else if (handle.getY() + handle.getHeight() >= height - TRACK_SNAP_RANGE)
|
||||||
|
proportion = 1f;
|
||||||
|
else proportion = y / (float) height;
|
||||||
|
final int targetPos = getValueInRange(0, itemCount - 1, (int) (proportion * (float) itemCount));
|
||||||
|
((LinearLayoutManager) recyclerView.getLayoutManager()).scrollToPositionWithOffset(targetPos, 0);
|
||||||
|
final String bubbleText = ((BubbleTextGetter) recyclerView.getAdapter()).getTextToShowInBubble(targetPos);
|
||||||
|
if (bubble != null) {
|
||||||
|
bubble.setText(bubbleText);
|
||||||
|
if (TextUtils.isEmpty(bubbleText)) {
|
||||||
|
hideBubble();
|
||||||
|
} else if (bubble.getVisibility() == View.INVISIBLE) {
|
||||||
|
showBubble();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getValueInRange(int min, int max, int value) {
|
||||||
|
int minimum = Math.max(min, value);
|
||||||
|
return Math.min(minimum, max);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateBubbleAndHandlePosition() {
|
||||||
|
if (bubble == null || handle.isSelected()) return;
|
||||||
|
|
||||||
|
final int verticalScrollOffset = recyclerView.computeVerticalScrollOffset();
|
||||||
|
final int verticalScrollRange = recyclerView.computeVerticalScrollRange();
|
||||||
|
float proportion = (float) verticalScrollOffset / ((float) verticalScrollRange - height);
|
||||||
|
setBubbleAndHandlePosition(height * proportion);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setBubbleAndHandlePosition(float y) {
|
||||||
|
final int handleHeight = handle.getHeight();
|
||||||
|
handle.setY(getValueInRange(0, height - handleHeight, (int) (y - handleHeight / 2)));
|
||||||
|
if (bubble != null) {
|
||||||
|
int bubbleHeight = bubble.getHeight();
|
||||||
|
bubble.setY(getValueInRange(0, height - bubbleHeight - handleHeight / 2, (int) (y - bubbleHeight)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showBubble() {
|
||||||
|
if (bubble == null) return;
|
||||||
|
bubble.setVisibility(VISIBLE);
|
||||||
|
if (currentAnimator != null) currentAnimator.cancel();
|
||||||
|
currentAnimator = ObjectAnimator.ofFloat(bubble, "alpha", 0f, 1f).setDuration(BUBBLE_ANIMATION_DURATION);
|
||||||
|
currentAnimator.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void hideBubble() {
|
||||||
|
if (bubble == null) return;
|
||||||
|
if (currentAnimator != null) currentAnimator.cancel();
|
||||||
|
currentAnimator = ObjectAnimator.ofFloat(bubble, "alpha", 1f, 0f).setDuration(BUBBLE_ANIMATION_DURATION);
|
||||||
|
currentAnimator.addListener(new AnimatorListenerAdapter() {
|
||||||
|
@Override
|
||||||
|
public void onAnimationEnd(Animator animation) {
|
||||||
|
super.onAnimationEnd(animation);
|
||||||
|
bubble.setVisibility(INVISIBLE);
|
||||||
|
currentAnimator = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnimationCancel(Animator animation) {
|
||||||
|
super.onAnimationCancel(animation);
|
||||||
|
bubble.setVisibility(INVISIBLE);
|
||||||
|
currentAnimator = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
currentAnimator.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,6 +20,7 @@ public interface ClickCallback {
|
|||||||
default void onServerClick(Bundle bundle) {}
|
default void onServerClick(Bundle bundle) {}
|
||||||
default void onServerLongClick(Bundle bundle) {}
|
default void onServerLongClick(Bundle bundle) {}
|
||||||
default void onPodcastEpisodeClick(Bundle bundle) {}
|
default void onPodcastEpisodeClick(Bundle bundle) {}
|
||||||
|
default void onPodcastEpisodeAltClick(Bundle bundle) {}
|
||||||
default void onPodcastEpisodeLongClick(Bundle bundle) {}
|
default void onPodcastEpisodeLongClick(Bundle bundle) {}
|
||||||
default void onPodcastChannelClick(Bundle bundle) {}
|
default void onPodcastChannelClick(Bundle bundle) {}
|
||||||
default void onPodcastChannelLongClick(Bundle bundle) {}
|
default void onPodcastChannelLongClick(Bundle bundle) {}
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.cappielloantonio.tempo.interfaces;
|
||||||
|
|
||||||
|
|
||||||
|
import androidx.annotation.Keep;
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
public interface DialogClickCallback {
|
||||||
|
default void onPositiveClick() {}
|
||||||
|
|
||||||
|
default void onNegativeClick() {}
|
||||||
|
|
||||||
|
default void onNeutralClick() {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.cappielloantonio.tempo.interfaces;
|
||||||
|
|
||||||
|
import androidx.annotation.Keep;
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
public interface PlaylistCallback {
|
||||||
|
default void onDismiss() {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.cappielloantonio.tempo.interfaces;
|
||||||
|
|
||||||
|
import androidx.annotation.Keep;
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
public interface StarCallback {
|
||||||
|
default void onError() {}
|
||||||
|
default void onSuccess() {}
|
||||||
|
}
|
||||||
@@ -20,6 +20,9 @@ class Download(@PrimaryKey override val id: String) : Child(id) {
|
|||||||
@ColumnInfo(name = "download_state", defaultValue = "1")
|
@ColumnInfo(name = "download_state", defaultValue = "1")
|
||||||
var downloadState: Int = 0
|
var downloadState: Int = 0
|
||||||
|
|
||||||
|
@ColumnInfo(name = "download_uri", defaultValue = "")
|
||||||
|
var downloadUri: String? = null
|
||||||
|
|
||||||
constructor(child: Child) : this(child.id) {
|
constructor(child: Child) : this(child.id) {
|
||||||
parentId = child.parentId
|
parentId = child.parentId
|
||||||
isDir = child.isDir
|
isDir = child.isDir
|
||||||
@@ -53,3 +56,9 @@ class Download(@PrimaryKey override val id: String) : Child(id) {
|
|||||||
originalHeight = child.originalHeight
|
originalHeight = child.originalHeight
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
data class DownloadStack(
|
||||||
|
var id: String,
|
||||||
|
var view: String?
|
||||||
|
)
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package com.cappielloantonio.tempo.model
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import androidx.annotation.Keep
|
||||||
|
import androidx.annotation.Nullable
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
@Parcelize
|
||||||
|
@Entity(tableName = "favorite")
|
||||||
|
data class Favorite(
|
||||||
|
@PrimaryKey
|
||||||
|
@ColumnInfo(name = "timestamp")
|
||||||
|
var timestamp: Long,
|
||||||
|
|
||||||
|
@ColumnInfo(name = "songId")
|
||||||
|
val songId: String?,
|
||||||
|
|
||||||
|
@ColumnInfo(name = "albumId")
|
||||||
|
val albumId: String?,
|
||||||
|
|
||||||
|
@ColumnInfo(name = "artistId")
|
||||||
|
val artistId: String?,
|
||||||
|
|
||||||
|
@ColumnInfo(name = "toStar")
|
||||||
|
val toStar: Boolean,
|
||||||
|
) : Parcelable {
|
||||||
|
override fun toString(): String = (songId ?: "null") + (albumId ?: "null") + (artistId ?: "null")
|
||||||
|
}
|
||||||
@@ -21,8 +21,6 @@ import retrofit2.Callback;
|
|||||||
import retrofit2.Response;
|
import retrofit2.Response;
|
||||||
|
|
||||||
public class AlbumRepository {
|
public class AlbumRepository {
|
||||||
private static final String TAG = "AlbumRepository";
|
|
||||||
|
|
||||||
public MutableLiveData<List<AlbumID3>> getAlbums(String type, int size, Integer fromYear, Integer toYear) {
|
public MutableLiveData<List<AlbumID3>> getAlbums(String type, int size, Integer fromYear, Integer toYear) {
|
||||||
MutableLiveData<List<AlbumID3>> listLiveAlbums = new MutableLiveData<>(new ArrayList<>());
|
MutableLiveData<List<AlbumID3>> listLiveAlbums = new MutableLiveData<>(new ArrayList<>());
|
||||||
|
|
||||||
@@ -78,40 +76,6 @@ public class AlbumRepository {
|
|||||||
return starredAlbums;
|
return starredAlbums;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void star(String id) {
|
|
||||||
App.getSubsonicClientInstance(false)
|
|
||||||
.getMediaAnnotationClient()
|
|
||||||
.star(null, id, null)
|
|
||||||
.enqueue(new Callback<ApiResponse>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void unstar(String id) {
|
|
||||||
App.getSubsonicClientInstance(false)
|
|
||||||
.getMediaAnnotationClient()
|
|
||||||
.unstar(null, id, null)
|
|
||||||
.enqueue(new Callback<ApiResponse>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRating(String id, int rating) {
|
public void setRating(String id, int rating) {
|
||||||
App.getSubsonicClientInstance(false)
|
App.getSubsonicClientInstance(false)
|
||||||
.getMediaAnnotationClient()
|
.getMediaAnnotationClient()
|
||||||
@@ -141,8 +105,10 @@ public class AlbumRepository {
|
|||||||
List<Child> tracks = new ArrayList<>();
|
List<Child> tracks = new ArrayList<>();
|
||||||
|
|
||||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getAlbum() != null) {
|
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getAlbum() != null) {
|
||||||
|
if (response.body().getSubsonicResponse().getAlbum().getSongs() != null) {
|
||||||
tracks.addAll(response.body().getSubsonicResponse().getAlbum().getSongs());
|
tracks.addAll(response.body().getSubsonicResponse().getAlbum().getSongs());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
albumTracks.setValue(tracks);
|
albumTracks.setValue(tracks);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,9 +63,13 @@ public class ArtistRepository {
|
|||||||
if (response.isSuccessful() && response.body() != null) {
|
if (response.isSuccessful() && response.body() != null) {
|
||||||
List<ArtistID3> artists = new ArrayList<>();
|
List<ArtistID3> artists = new ArrayList<>();
|
||||||
|
|
||||||
|
if(response.body().getSubsonicResponse().getArtists() != null && response.body().getSubsonicResponse().getArtists().getIndices() != null) {
|
||||||
for (IndexID3 index : response.body().getSubsonicResponse().getArtists().getIndices()) {
|
for (IndexID3 index : response.body().getSubsonicResponse().getArtists().getIndices()) {
|
||||||
|
if(index != null && index.getArtists() != null) {
|
||||||
artists.addAll(index.getArtists());
|
artists.addAll(index.getArtists());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (random) {
|
if (random) {
|
||||||
Collections.shuffle(artists);
|
Collections.shuffle(artists);
|
||||||
@@ -135,9 +139,6 @@ public class ArtistRepository {
|
|||||||
return artist;
|
return artist;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Metodo che mi restituisce le informazioni complete dell'artista (bio, immagini prese da last.fm, artisti simili...)
|
|
||||||
*/
|
|
||||||
public MutableLiveData<ArtistInfo2> getArtistFullInfo(String id) {
|
public MutableLiveData<ArtistInfo2> getArtistFullInfo(String id) {
|
||||||
MutableLiveData<ArtistInfo2> artistFullInfo = new MutableLiveData<>(null);
|
MutableLiveData<ArtistInfo2> artistFullInfo = new MutableLiveData<>(null);
|
||||||
|
|
||||||
@@ -161,40 +162,6 @@ public class ArtistRepository {
|
|||||||
return artistFullInfo;
|
return artistFullInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void star(String id) {
|
|
||||||
App.getSubsonicClientInstance(false)
|
|
||||||
.getMediaAnnotationClient()
|
|
||||||
.star(null, null, id)
|
|
||||||
.enqueue(new Callback<ApiResponse>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void unstar(String id) {
|
|
||||||
App.getSubsonicClientInstance(false)
|
|
||||||
.getMediaAnnotationClient()
|
|
||||||
.unstar(null, null, id)
|
|
||||||
.enqueue(new Callback<ApiResponse>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRating(String id, int rating) {
|
public void setRating(String id, int rating) {
|
||||||
App.getSubsonicClientInstance(false)
|
App.getSubsonicClientInstance(false)
|
||||||
.getMediaAnnotationClient()
|
.getMediaAnnotationClient()
|
||||||
|
|||||||
@@ -4,8 +4,11 @@ import androidx.lifecycle.LiveData;
|
|||||||
|
|
||||||
import com.cappielloantonio.tempo.database.AppDatabase;
|
import com.cappielloantonio.tempo.database.AppDatabase;
|
||||||
import com.cappielloantonio.tempo.database.dao.DownloadDao;
|
import com.cappielloantonio.tempo.database.dao.DownloadDao;
|
||||||
|
import com.cappielloantonio.tempo.database.dao.FavoriteDao;
|
||||||
import com.cappielloantonio.tempo.model.Download;
|
import com.cappielloantonio.tempo.model.Download;
|
||||||
|
import com.cappielloantonio.tempo.model.Favorite;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class DownloadRepository {
|
public class DownloadRepository {
|
||||||
@@ -15,6 +18,43 @@ public class DownloadRepository {
|
|||||||
return downloadDao.getAll();
|
return downloadDao.getAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Download getDownload(String id) {
|
||||||
|
Download download = null;
|
||||||
|
|
||||||
|
GetDownloadThreadSafe getDownloadThreadSafe = new GetDownloadThreadSafe(downloadDao, id);
|
||||||
|
Thread thread = new Thread(getDownloadThreadSafe);
|
||||||
|
thread.start();
|
||||||
|
|
||||||
|
try {
|
||||||
|
thread.join();
|
||||||
|
download = getDownloadThreadSafe.getDownload();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return download;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class GetDownloadThreadSafe implements Runnable {
|
||||||
|
private final DownloadDao downloadDao;
|
||||||
|
private final String id;
|
||||||
|
private Download download;
|
||||||
|
|
||||||
|
public GetDownloadThreadSafe(DownloadDao downloadDao, String id) {
|
||||||
|
this.downloadDao = downloadDao;
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
download = downloadDao.getOne(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Download getDownload() {
|
||||||
|
return download;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void insert(Download download) {
|
public void insert(Download download) {
|
||||||
InsertThreadSafe insert = new InsertThreadSafe(downloadDao, download);
|
InsertThreadSafe insert = new InsertThreadSafe(downloadDao, download);
|
||||||
Thread thread = new Thread(insert);
|
Thread thread = new Thread(insert);
|
||||||
|
|||||||
@@ -0,0 +1,140 @@
|
|||||||
|
package com.cappielloantonio.tempo.repository;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import com.cappielloantonio.tempo.App;
|
||||||
|
import com.cappielloantonio.tempo.database.AppDatabase;
|
||||||
|
import com.cappielloantonio.tempo.database.dao.FavoriteDao;
|
||||||
|
import com.cappielloantonio.tempo.interfaces.StarCallback;
|
||||||
|
import com.cappielloantonio.tempo.model.Favorite;
|
||||||
|
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import retrofit2.Call;
|
||||||
|
import retrofit2.Callback;
|
||||||
|
import retrofit2.Response;
|
||||||
|
|
||||||
|
public class FavoriteRepository {
|
||||||
|
private final FavoriteDao favoriteDao = AppDatabase.getInstance().favoriteDao();
|
||||||
|
|
||||||
|
public void star(String id, String albumId, String artistId, StarCallback starCallback) {
|
||||||
|
App.getSubsonicClientInstance(false)
|
||||||
|
.getMediaAnnotationClient()
|
||||||
|
.star(id, albumId, artistId)
|
||||||
|
.enqueue(new Callback<ApiResponse>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||||
|
if (response.isSuccessful()) {
|
||||||
|
starCallback.onSuccess();
|
||||||
|
} else {
|
||||||
|
starCallback.onError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||||
|
starCallback.onError();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unstar(String id, String albumId, String artistId, StarCallback starCallback) {
|
||||||
|
App.getSubsonicClientInstance(false)
|
||||||
|
.getMediaAnnotationClient()
|
||||||
|
.unstar(id, albumId, artistId)
|
||||||
|
.enqueue(new Callback<ApiResponse>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||||
|
if (response.isSuccessful()) {
|
||||||
|
starCallback.onSuccess();
|
||||||
|
} else {
|
||||||
|
starCallback.onError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||||
|
starCallback.onError();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Favorite> getFavorites() {
|
||||||
|
List<Favorite> favorites = new ArrayList<>();
|
||||||
|
|
||||||
|
GetAllThreadSafe getAllThreadSafe = new GetAllThreadSafe(favoriteDao);
|
||||||
|
Thread thread = new Thread(getAllThreadSafe);
|
||||||
|
thread.start();
|
||||||
|
|
||||||
|
try {
|
||||||
|
thread.join();
|
||||||
|
favorites = getAllThreadSafe.getFavorites();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return favorites;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class GetAllThreadSafe implements Runnable {
|
||||||
|
private final FavoriteDao favoriteDao;
|
||||||
|
private List<Favorite> favorites = new ArrayList<>();
|
||||||
|
|
||||||
|
public GetAllThreadSafe(FavoriteDao favoriteDao) {
|
||||||
|
this.favoriteDao = favoriteDao;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
favorites = favoriteDao.getAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Favorite> getFavorites() {
|
||||||
|
return favorites;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void starLater(String id, String albumId, String artistId, boolean toStar) {
|
||||||
|
InsertThreadSafe insert = new InsertThreadSafe(favoriteDao, new Favorite(System.currentTimeMillis(), id, albumId, artistId, toStar));
|
||||||
|
Thread thread = new Thread(insert);
|
||||||
|
thread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class InsertThreadSafe implements Runnable {
|
||||||
|
private final FavoriteDao favoriteDao;
|
||||||
|
private final Favorite favorite;
|
||||||
|
|
||||||
|
public InsertThreadSafe(FavoriteDao favoriteDao, Favorite favorite) {
|
||||||
|
this.favoriteDao = favoriteDao;
|
||||||
|
this.favorite = favorite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
favoriteDao.insert(favorite);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void delete(Favorite favorite) {
|
||||||
|
DeleteThreadSafe delete = new DeleteThreadSafe(favoriteDao, favorite);
|
||||||
|
Thread thread = new Thread(delete);
|
||||||
|
thread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class DeleteThreadSafe implements Runnable {
|
||||||
|
private final FavoriteDao favoriteDao;
|
||||||
|
private final Favorite favorite;
|
||||||
|
|
||||||
|
public DeleteThreadSafe(FavoriteDao favoriteDao, Favorite favorite) {
|
||||||
|
this.favoriteDao = favoriteDao;
|
||||||
|
this.favorite = favorite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
favoriteDao.delete(favorite);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,9 +24,14 @@ public class GenreRepository {
|
|||||||
.enqueue(new Callback<ApiResponse>() {
|
.enqueue(new Callback<ApiResponse>() {
|
||||||
@Override
|
@Override
|
||||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getGenres() != null) {
|
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse() != null && response.body().getSubsonicResponse().getGenres() != null) {
|
||||||
List<Genre> genreList = response.body().getSubsonicResponse().getGenres().getGenres();
|
List<Genre> genreList = response.body().getSubsonicResponse().getGenres().getGenres();
|
||||||
|
|
||||||
|
if (genreList == null || genreList.isEmpty()) {
|
||||||
|
genres.setValue(Collections.emptyList());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (random) {
|
if (random) {
|
||||||
Collections.shuffle(genreList);
|
Collections.shuffle(genreList);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,12 +95,29 @@ public class PlaylistRepository {
|
|||||||
.enqueue(new Callback<ApiResponse>() {
|
.enqueue(new Callback<ApiResponse>() {
|
||||||
@Override
|
@Override
|
||||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||||
Log.d("PLAYLIST", response.toString());
|
Log.d("createPlaylist", "onResponse: ");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||||
Log.d("PLAYLIST", t.toString());
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updatePlaylist(String playlistId, String name, ArrayList<String> songsId) {
|
||||||
|
App.getSubsonicClientInstance(false)
|
||||||
|
.getPlaylistClient()
|
||||||
|
.deletePlaylist(playlistId)
|
||||||
|
.enqueue(new Callback<ApiResponse>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||||
|
createPlaylist(null, name, songsId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -133,4 +133,21 @@ public class PodcastRepository {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void downloadPodcastEpisode(String episodeId) {
|
||||||
|
App.getSubsonicClientInstance(false)
|
||||||
|
.getPodcastClient()
|
||||||
|
.downloadPodcastEpisode(episodeId)
|
||||||
|
.enqueue(new Callback<ApiResponse>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package com.cappielloantonio.tempo.repository;
|
package com.cappielloantonio.tempo.repository;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.lifecycle.MutableLiveData;
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
|
||||||
@@ -100,6 +102,8 @@ public class SearchingRepository {
|
|||||||
LinkedHashSet<String> hashSet = new LinkedHashSet<>(newSuggestions);
|
LinkedHashSet<String> hashSet = new LinkedHashSet<>(newSuggestions);
|
||||||
ArrayList<String> suggestionsWithoutDuplicates = new ArrayList<>(hashSet);
|
ArrayList<String> suggestionsWithoutDuplicates = new ArrayList<>(hashSet);
|
||||||
|
|
||||||
|
Log.d("suggestionsWithoutDuplicates", suggestionsWithoutDuplicates.toString());
|
||||||
|
|
||||||
suggestions.setValue(suggestionsWithoutDuplicates);
|
suggestions.setValue(suggestionsWithoutDuplicates);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.lifecycle.MutableLiveData;
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
|
||||||
import com.cappielloantonio.tempo.App;
|
import com.cappielloantonio.tempo.App;
|
||||||
|
import com.cappielloantonio.tempo.database.AppDatabase;
|
||||||
|
import com.cappielloantonio.tempo.database.dao.FavoriteDao;
|
||||||
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||||
|
|
||||||
@@ -119,40 +121,6 @@ public class SongRepository {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void star(String id) {
|
|
||||||
App.getSubsonicClientInstance(false)
|
|
||||||
.getMediaAnnotationClient()
|
|
||||||
.star(id, null, null)
|
|
||||||
.enqueue(new Callback<ApiResponse>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void unstar(String id) {
|
|
||||||
App.getSubsonicClientInstance(false)
|
|
||||||
.getMediaAnnotationClient()
|
|
||||||
.unstar(id, null, null)
|
|
||||||
.enqueue(new Callback<ApiResponse>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRating(String id, int rating) {
|
public void setRating(String id, int rating) {
|
||||||
App.getSubsonicClientInstance(false)
|
App.getSubsonicClientInstance(false)
|
||||||
.getMediaAnnotationClient()
|
.getMediaAnnotationClient()
|
||||||
@@ -173,8 +141,6 @@ public class SongRepository {
|
|||||||
public MutableLiveData<List<Child>> getSongsByGenre(String id, int page) {
|
public MutableLiveData<List<Child>> getSongsByGenre(String id, int page) {
|
||||||
MutableLiveData<List<Child>> songsByGenre = new MutableLiveData<>();
|
MutableLiveData<List<Child>> songsByGenre = new MutableLiveData<>();
|
||||||
|
|
||||||
Log.d(TAG, "onScrolled PAGE: " + page);
|
|
||||||
|
|
||||||
App.getSubsonicClientInstance(false)
|
App.getSubsonicClientInstance(false)
|
||||||
.getAlbumSongListClient()
|
.getAlbumSongListClient()
|
||||||
.getSongsByGenre(id, 100, 100 * page)
|
.getSongsByGenre(id, 100, 100 * page)
|
||||||
|
|||||||
@@ -77,6 +77,8 @@ public class DownloaderManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void download(MediaItem mediaItem, com.cappielloantonio.tempo.model.Download download) {
|
public void download(MediaItem mediaItem, com.cappielloantonio.tempo.model.Download download) {
|
||||||
|
download.setDownloadUri(mediaItem.requestMetadata.mediaUri.toString());
|
||||||
|
|
||||||
DownloadService.sendAddDownload(context, DownloaderService.class, buildDownloadRequest(mediaItem), false);
|
DownloadService.sendAddDownload(context, DownloaderService.class, buildDownloadRequest(mediaItem), false);
|
||||||
insertDatabase(download);
|
insertDatabase(download);
|
||||||
}
|
}
|
||||||
@@ -89,6 +91,7 @@ public class DownloaderManager {
|
|||||||
|
|
||||||
public void remove(MediaItem mediaItem, com.cappielloantonio.tempo.model.Download download) {
|
public void remove(MediaItem mediaItem, com.cappielloantonio.tempo.model.Download download) {
|
||||||
DownloadService.sendRemoveDownload(context, DownloaderService.class, buildDownloadRequest(mediaItem).id, false);
|
DownloadService.sendRemoveDownload(context, DownloaderService.class, buildDownloadRequest(mediaItem).id, false);
|
||||||
|
deleteDatabase(download.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void remove(List<MediaItem> mediaItems, List<com.cappielloantonio.tempo.model.Download> downloads) {
|
public void remove(List<MediaItem> mediaItems, List<com.cappielloantonio.tempo.model.Download> downloads) {
|
||||||
@@ -97,6 +100,12 @@ public class DownloaderManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void removeAll() {
|
||||||
|
DownloadService.sendRemoveAllDownloads(context, DownloaderService.class, false);
|
||||||
|
deleteAllDatabase();
|
||||||
|
DownloadUtil.eraseDownloadFolder(context);
|
||||||
|
}
|
||||||
|
|
||||||
private void loadDownloads() {
|
private void loadDownloads() {
|
||||||
try (DownloadCursor loadedDownloads = downloadIndex.getDownloads()) {
|
try (DownloadCursor loadedDownloads = downloadIndex.getDownloads()) {
|
||||||
while (loadedDownloads.moveToNext()) {
|
while (loadedDownloads.moveToNext()) {
|
||||||
@@ -108,6 +117,11 @@ public class DownloaderManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getDownloadNotificationMessage(String id) {
|
||||||
|
com.cappielloantonio.tempo.model.Download download = getDownloadRepository().getDownload(id);
|
||||||
|
return download != null ? download.getTitle() : null;
|
||||||
|
}
|
||||||
|
|
||||||
private static DownloadRepository getDownloadRepository() {
|
private static DownloadRepository getDownloadRepository() {
|
||||||
return new DownloadRepository();
|
return new DownloadRepository();
|
||||||
}
|
}
|
||||||
@@ -120,6 +134,10 @@ public class DownloaderManager {
|
|||||||
getDownloadRepository().delete(id);
|
getDownloadRepository().delete(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void deleteAllDatabase() {
|
||||||
|
getDownloadRepository().deleteAll();
|
||||||
|
}
|
||||||
|
|
||||||
public static void updateDatabase(String id) {
|
public static void updateDatabase(String id) {
|
||||||
getDownloadRepository().update(id);
|
getDownloadRepository().update(id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media3.common.util.NotificationUtil;
|
import androidx.media3.common.util.NotificationUtil;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
import androidx.media3.common.util.Util;
|
|
||||||
import androidx.media3.exoplayer.offline.Download;
|
import androidx.media3.exoplayer.offline.Download;
|
||||||
import androidx.media3.exoplayer.offline.DownloadManager;
|
import androidx.media3.exoplayer.offline.DownloadManager;
|
||||||
import androidx.media3.exoplayer.offline.DownloadNotificationHelper;
|
import androidx.media3.exoplayer.offline.DownloadNotificationHelper;
|
||||||
@@ -52,6 +51,8 @@ public class DownloaderService extends androidx.media3.exoplayer.offline.Downloa
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static final class TerminalStateNotificationHelper implements DownloadManager.Listener {
|
private static final class TerminalStateNotificationHelper implements DownloadManager.Listener {
|
||||||
|
private static final String TAG = "TerminalStateNotificatinHelper";
|
||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
private final DownloadNotificationHelper notificationHelper;
|
private final DownloadNotificationHelper notificationHelper;
|
||||||
|
|
||||||
@@ -68,10 +69,10 @@ public class DownloaderService extends androidx.media3.exoplayer.offline.Downloa
|
|||||||
Notification notification;
|
Notification notification;
|
||||||
|
|
||||||
if (download.state == Download.STATE_COMPLETED) {
|
if (download.state == Download.STATE_COMPLETED) {
|
||||||
notification = notificationHelper.buildDownloadCompletedNotification(context, R.drawable.ic_check_circle, null, Util.fromUtf8Bytes(download.request.data));
|
notification = notificationHelper.buildDownloadCompletedNotification(context, R.drawable.ic_check_circle, null, DownloaderManager.getDownloadNotificationMessage(download.request.id));
|
||||||
DownloaderManager.updateDatabase(download.request.id);
|
DownloaderManager.updateDatabase(download.request.id);
|
||||||
} else if (download.state == Download.STATE_FAILED) {
|
} else if (download.state == Download.STATE_FAILED) {
|
||||||
notification = notificationHelper.buildDownloadFailedNotification(context, R.drawable.ic_error, null, Util.fromUtf8Bytes(download.request.data));
|
notification = notificationHelper.buildDownloadFailedNotification(context, R.drawable.ic_error, null, DownloaderManager.getDownloadNotificationMessage(download.request.id));
|
||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -204,6 +204,22 @@ public class MediaManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void shuffle(ListenableFuture<MediaBrowser> mediaBrowserListenableFuture, List<Child> media, int startIndex, int endIndex) {
|
||||||
|
if (mediaBrowserListenableFuture != null) {
|
||||||
|
mediaBrowserListenableFuture.addListener(() -> {
|
||||||
|
try {
|
||||||
|
if (mediaBrowserListenableFuture.isDone()) {
|
||||||
|
mediaBrowserListenableFuture.get().removeMediaItems(startIndex, endIndex + 1);
|
||||||
|
mediaBrowserListenableFuture.get().addMediaItems(MappingUtil.mapMediaItems(media).subList(startIndex, endIndex + 1));
|
||||||
|
swapDatabase(media);
|
||||||
|
}
|
||||||
|
} catch (ExecutionException | InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}, MoreExecutors.directExecutor());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static void swap(ListenableFuture<MediaBrowser> mediaBrowserListenableFuture, List<Child> media, int from, int to) {
|
public static void swap(ListenableFuture<MediaBrowser> mediaBrowserListenableFuture, List<Child> media, int from, int to) {
|
||||||
if (mediaBrowserListenableFuture != null) {
|
if (mediaBrowserListenableFuture != null) {
|
||||||
mediaBrowserListenableFuture.addListener(() -> {
|
mediaBrowserListenableFuture.addListener(() -> {
|
||||||
|
|||||||
@@ -48,4 +48,9 @@ public class PodcastClient {
|
|||||||
Log.d(TAG, "deletePodcastEpisode()");
|
Log.d(TAG, "deletePodcastEpisode()");
|
||||||
return podcastService.deletePodcastEpisode(subsonic.getParams(), episodeId);
|
return podcastService.deletePodcastEpisode(subsonic.getParams(), episodeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Call<ApiResponse> downloadPodcastEpisode(String episodeId) {
|
||||||
|
Log.d(TAG, "downloadPodcastEpisode()");
|
||||||
|
return podcastService.downloadPodcastEpisode(subsonic.getParams(), episodeId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,4 +27,7 @@ public interface PodcastService {
|
|||||||
|
|
||||||
@GET("deletePodcastEpisode")
|
@GET("deletePodcastEpisode")
|
||||||
Call<ApiResponse> deletePodcastEpisode(@QueryMap Map<String, String> params, @Query("id") String id);
|
Call<ApiResponse> deletePodcastEpisode(@QueryMap Map<String, String> params, @Query("id") String id);
|
||||||
|
|
||||||
|
@GET("downloadPodcastEpisode")
|
||||||
|
Call<ApiResponse> downloadPodcastEpisode(@QueryMap Map<String, String> params, @Query("id") String id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package com.cappielloantonio.tempo.subsonic.models
|
|||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import androidx.annotation.Keep
|
import androidx.annotation.Keep
|
||||||
import androidx.room.ColumnInfo
|
|
||||||
import com.google.gson.annotations.SerializedName
|
import com.google.gson.annotations.SerializedName
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@@ -29,11 +28,9 @@ class PodcastEpisode : Parcelable {
|
|||||||
var transcodedContentType: String? = null
|
var transcodedContentType: String? = null
|
||||||
var transcodedSuffix: String? = null
|
var transcodedSuffix: String? = null
|
||||||
var duration: Int? = null
|
var duration: Int? = null
|
||||||
@ColumnInfo("bitrate")
|
|
||||||
@SerializedName("bitRate")
|
@SerializedName("bitRate")
|
||||||
var bitrate: Int? = null
|
var bitrate: Int? = null
|
||||||
var path: String? = null
|
var path: String? = null
|
||||||
@ColumnInfo(name = "is_video")
|
|
||||||
@SerializedName("isVideo")
|
@SerializedName("isVideo")
|
||||||
var isVideo: Boolean = false
|
var isVideo: Boolean = false
|
||||||
var userRating: Int? = null
|
var userRating: Int? = null
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.cappielloantonio.tempo.ui.activity;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
import android.net.ConnectivityManager;
|
import android.net.ConnectivityManager;
|
||||||
|
import android.net.NetworkInfo;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -30,6 +31,7 @@ import com.cappielloantonio.tempo.util.Preferences;
|
|||||||
import com.cappielloantonio.tempo.viewmodel.MainViewModel;
|
import com.cappielloantonio.tempo.viewmodel.MainViewModel;
|
||||||
import com.google.android.material.bottomnavigation.BottomNavigationView;
|
import com.google.android.material.bottomnavigation.BottomNavigationView;
|
||||||
import com.google.android.material.bottomsheet.BottomSheetBehavior;
|
import com.google.android.material.bottomsheet.BottomSheetBehavior;
|
||||||
|
import com.google.android.material.color.DynamicColors;
|
||||||
import com.google.common.util.concurrent.MoreExecutors;
|
import com.google.common.util.concurrent.MoreExecutors;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
@@ -50,10 +52,10 @@ public class MainActivity extends BaseActivity {
|
|||||||
|
|
||||||
ConnectivityStatusBroadcastReceiver connectivityStatusBroadcastReceiver;
|
ConnectivityStatusBroadcastReceiver connectivityStatusBroadcastReceiver;
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
SplashScreen.installSplashScreen(this);
|
SplashScreen.installSplashScreen(this);
|
||||||
|
DynamicColors.applyToActivityIfAvailable(this);
|
||||||
|
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
@@ -117,6 +119,8 @@ public class MainActivity extends BaseActivity {
|
|||||||
fragmentManager.beginTransaction().replace(R.id.player_bottom_sheet, new PlayerBottomSheetFragment(), "PlayerBottomSheet").commit();
|
fragmentManager.beginTransaction().replace(R.id.player_bottom_sheet, new PlayerBottomSheetFragment(), "PlayerBottomSheet").commit();
|
||||||
|
|
||||||
setBottomSheetInPeek(mainViewModel.isQueueLoaded());
|
setBottomSheetInPeek(mainViewModel.isQueueLoaded());
|
||||||
|
|
||||||
|
collapseBottomSheet();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setBottomSheetInPeek(Boolean isVisible) {
|
public void setBottomSheetInPeek(Boolean isVisible) {
|
||||||
@@ -338,7 +342,9 @@ public class MainActivity extends BaseActivity {
|
|||||||
private void checkConnectionType() {
|
private void checkConnectionType() {
|
||||||
if (Preferences.isWifiOnly()) {
|
if (Preferences.isWifiOnly()) {
|
||||||
ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
|
ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||||
if (connectivityManager.getActiveNetworkInfo().getType() != ConnectivityManager.TYPE_WIFI) {
|
NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
|
||||||
|
|
||||||
|
if (networkInfo != null && networkInfo.getType() != ConnectivityManager.TYPE_WIFI) {
|
||||||
ConnectionAlertDialog dialog = new ConnectionAlertDialog();
|
ConnectionAlertDialog dialog = new ConnectionAlertDialog();
|
||||||
dialog.show(getSupportFragmentManager(), null);
|
dialog.show(getSupportFragmentManager(), null);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ public class BaseActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void checkBatteryOptimization() {
|
private void checkBatteryOptimization() {
|
||||||
if (detectBatteryOptimization() && Boolean.TRUE.equals(Preferences.askForOptimization())) {
|
if (detectBatteryOptimization() && Preferences.askForOptimization()) {
|
||||||
showBatteryOptimizationDialog();
|
showBatteryOptimizationDialog();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -98,6 +98,7 @@ public class BaseActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setNavigationBarColor() {
|
private void setNavigationBarColor() {
|
||||||
getWindow().setNavigationBarColor(SurfaceColors.getColorForElevation(this, 10));
|
getWindow().setNavigationBarColor(SurfaceColors.getColorForElevation(this, 8));
|
||||||
|
getWindow().setStatusBarColor(SurfaceColors.getColorForElevation(this, 0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,25 +11,35 @@ import androidx.recyclerview.widget.RecyclerView;
|
|||||||
|
|
||||||
import com.cappielloantonio.tempo.R;
|
import com.cappielloantonio.tempo.R;
|
||||||
import com.cappielloantonio.tempo.databinding.ItemHorizontalDownloadBinding;
|
import com.cappielloantonio.tempo.databinding.ItemHorizontalDownloadBinding;
|
||||||
|
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
||||||
import com.cappielloantonio.tempo.interfaces.ClickCallback;
|
import com.cappielloantonio.tempo.interfaces.ClickCallback;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||||
import com.cappielloantonio.tempo.util.Constants;
|
import com.cappielloantonio.tempo.util.Constants;
|
||||||
import com.cappielloantonio.tempo.util.MusicUtil;
|
import com.cappielloantonio.tempo.util.MusicUtil;
|
||||||
|
import com.cappielloantonio.tempo.util.Util;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
public class DownloadHorizontalAdapter extends RecyclerView.Adapter<DownloadHorizontalAdapter.ViewHolder> {
|
public class DownloadHorizontalAdapter extends RecyclerView.Adapter<DownloadHorizontalAdapter.ViewHolder> {
|
||||||
private final ClickCallback click;
|
private final ClickCallback click;
|
||||||
|
|
||||||
|
private String view;
|
||||||
|
private String filterKey;
|
||||||
|
private String filterValue;
|
||||||
|
|
||||||
private List<Child> songs;
|
private List<Child> songs;
|
||||||
|
private List<Child> grouped;
|
||||||
|
|
||||||
public DownloadHorizontalAdapter(ClickCallback click) {
|
public DownloadHorizontalAdapter(ClickCallback click) {
|
||||||
this.click = click;
|
this.click = click;
|
||||||
|
this.view = Constants.DOWNLOAD_TYPE_TRACK;
|
||||||
this.songs = Collections.emptyList();
|
this.songs = Collections.emptyList();
|
||||||
|
this.grouped = Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@@ -41,31 +51,43 @@ public class DownloadHorizontalAdapter extends RecyclerView.Adapter<DownloadHori
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(ViewHolder holder, int position) {
|
public void onBindViewHolder(ViewHolder holder, int position) {
|
||||||
Child song = songs.get(position);
|
switch (view) {
|
||||||
|
case Constants.DOWNLOAD_TYPE_TRACK:
|
||||||
holder.item.downloadedSongTitleTextView.setText(MusicUtil.getReadableString(song.getTitle()));
|
initTrackLayout(holder, position);
|
||||||
holder.item.downloadedSongArtistTextView.setText(holder.itemView.getContext().getString(R.string.song_subtitle_formatter, MusicUtil.getReadableString(song.getArtist()), MusicUtil.getReadableDurationString(song.getDuration(), false)));
|
break;
|
||||||
holder.item.downloadedSongAlbumTextView.setText(MusicUtil.getReadableString(song.getAlbum()));
|
case Constants.DOWNLOAD_TYPE_ALBUM:
|
||||||
|
initAlbumLayout(holder, position);
|
||||||
if (position > 0 && songs.get(position - 1) != null && !Objects.equals(songs.get(position - 1).getAlbum(), songs.get(position).getAlbum())) {
|
break;
|
||||||
holder.item.divider.setPadding(0, (int) holder.itemView.getContext().getResources().getDimension(R.dimen.downloaded_item_padding), 0, 0);
|
case Constants.DOWNLOAD_TYPE_ARTIST:
|
||||||
} else {
|
initArtistLayout(holder, position);
|
||||||
if (position > 0) holder.item.divider.setVisibility(View.GONE);
|
break;
|
||||||
|
case Constants.DOWNLOAD_TYPE_GENRE:
|
||||||
|
initGenreLayout(holder, position);
|
||||||
|
break;
|
||||||
|
case Constants.DOWNLOAD_TYPE_YEAR:
|
||||||
|
initYearLayout(holder, position);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getItemCount() {
|
public int getItemCount() {
|
||||||
return songs.size();
|
return grouped.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setItems(List<Child> songs) {
|
public void setItems(String view, String filterKey, String filterValue, List<Child> songs) {
|
||||||
|
this.view = filterValue != null ? view : filterKey;
|
||||||
|
this.filterKey = filterKey;
|
||||||
|
this.filterValue = filterValue;
|
||||||
|
|
||||||
this.songs = songs;
|
this.songs = songs;
|
||||||
|
this.grouped = groupSong(songs);
|
||||||
|
|
||||||
notifyDataSetChanged();
|
notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Child getItem(int id) {
|
public Child getItem(int id) {
|
||||||
return songs.get(id);
|
return grouped.get(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -78,6 +100,145 @@ public class DownloadHorizontalAdapter extends RecyclerView.Adapter<DownloadHori
|
|||||||
return position;
|
return position;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<Child> groupSong(List<Child> songs) {
|
||||||
|
switch (view) {
|
||||||
|
case Constants.DOWNLOAD_TYPE_TRACK:
|
||||||
|
return filterSong(filterKey, filterValue, songs.stream().filter(song -> Objects.nonNull(song.getId())).filter(Util.distinctByKey(Child::getId)).collect(Collectors.toList()));
|
||||||
|
case Constants.DOWNLOAD_TYPE_ALBUM:
|
||||||
|
return filterSong(filterKey, filterValue, songs.stream().filter(song -> Objects.nonNull(song.getAlbumId())).filter(Util.distinctByKey(Child::getAlbumId)).collect(Collectors.toList()));
|
||||||
|
case Constants.DOWNLOAD_TYPE_ARTIST:
|
||||||
|
return filterSong(filterKey, filterValue, songs.stream().filter(song -> Objects.nonNull(song.getArtistId())).filter(Util.distinctByKey(Child::getArtistId)).collect(Collectors.toList()));
|
||||||
|
case Constants.DOWNLOAD_TYPE_GENRE:
|
||||||
|
return filterSong(filterKey, filterValue, songs.stream().filter(song -> Objects.nonNull(song.getGenre())).filter(Util.distinctByKey(Child::getGenre)).collect(Collectors.toList()));
|
||||||
|
case Constants.DOWNLOAD_TYPE_YEAR:
|
||||||
|
return filterSong(filterKey, filterValue, songs.stream().filter(song -> Objects.nonNull(song.getYear())).filter(Util.distinctByKey(Child::getYear)).collect(Collectors.toList()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Child> filterSong(String filterKey, String filterValue, List<Child> songs) {
|
||||||
|
if (filterValue != null) {
|
||||||
|
switch (filterKey) {
|
||||||
|
case Constants.DOWNLOAD_TYPE_TRACK:
|
||||||
|
return songs.stream().filter(child -> child.getId().equals(filterValue)).collect(Collectors.toList());
|
||||||
|
case Constants.DOWNLOAD_TYPE_ALBUM:
|
||||||
|
return songs.stream().filter(child -> Objects.equals(child.getAlbumId(), filterValue)).collect(Collectors.toList());
|
||||||
|
case Constants.DOWNLOAD_TYPE_GENRE:
|
||||||
|
return songs.stream().filter(child -> Objects.equals(child.getGenre(), filterValue)).collect(Collectors.toList());
|
||||||
|
case Constants.DOWNLOAD_TYPE_YEAR:
|
||||||
|
return songs.stream().filter(child -> Objects.equals(child.getYear(), Integer.valueOf(filterValue))).collect(Collectors.toList());
|
||||||
|
case Constants.DOWNLOAD_TYPE_ARTIST:
|
||||||
|
return songs.stream().filter(child -> Objects.equals(child.getArtistId(), filterValue)).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return songs;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String countSong(String filterKey, String filterValue, List<Child> songs) {
|
||||||
|
if (filterValue != null) {
|
||||||
|
switch (filterKey) {
|
||||||
|
case Constants.DOWNLOAD_TYPE_TRACK:
|
||||||
|
return String.valueOf(songs.stream().filter(child -> child.getId().equals(filterValue)).count());
|
||||||
|
case Constants.DOWNLOAD_TYPE_ALBUM:
|
||||||
|
return String.valueOf(songs.stream().filter(child -> Objects.equals(child.getAlbumId(), filterValue)).count());
|
||||||
|
case Constants.DOWNLOAD_TYPE_GENRE:
|
||||||
|
return String.valueOf(songs.stream().filter(child -> Objects.equals(child.getGenre(), filterValue)).count());
|
||||||
|
case Constants.DOWNLOAD_TYPE_YEAR:
|
||||||
|
return String.valueOf(songs.stream().filter(child -> Objects.equals(child.getYear(), Integer.valueOf(filterValue))).count());
|
||||||
|
case Constants.DOWNLOAD_TYPE_ARTIST:
|
||||||
|
return String.valueOf(songs.stream().filter(child -> Objects.equals(child.getArtistId(), filterValue)).count());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "0";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initTrackLayout(ViewHolder holder, int position) {
|
||||||
|
Child song = grouped.get(position);
|
||||||
|
|
||||||
|
holder.item.downloadedItemTitleTextView.setText(MusicUtil.getReadableString(song.getTitle()));
|
||||||
|
holder.item.downloadedItemSubtitleTextView.setText(holder.itemView.getContext().getString(R.string.song_subtitle_formatter, MusicUtil.getReadableString(song.getArtist()), MusicUtil.getReadableDurationString(song.getDuration(), false)));
|
||||||
|
holder.item.downloadedItemPreTextView.setText(MusicUtil.getReadableString(song.getAlbum()));
|
||||||
|
|
||||||
|
CustomGlideRequest.Builder
|
||||||
|
.from(holder.itemView.getContext(), song.getCoverArtId())
|
||||||
|
.build()
|
||||||
|
.into(holder.item.itemCoverImageView);
|
||||||
|
|
||||||
|
holder.item.itemCoverImageView.setVisibility(View.VISIBLE);
|
||||||
|
holder.item.downloadedItemMoreButton.setVisibility(View.VISIBLE);
|
||||||
|
holder.item.divider.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
if (position > 0 && grouped.get(position - 1) != null && !Objects.equals(grouped.get(position - 1).getAlbum(), grouped.get(position).getAlbum())) {
|
||||||
|
holder.item.divider.setPadding(0, (int) holder.itemView.getContext().getResources().getDimension(R.dimen.downloaded_item_padding), 0, 0);
|
||||||
|
} else {
|
||||||
|
if (position > 0) holder.item.divider.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initAlbumLayout(ViewHolder holder, int position) {
|
||||||
|
Child song = grouped.get(position);
|
||||||
|
|
||||||
|
holder.item.downloadedItemTitleTextView.setText(MusicUtil.getReadableString(song.getAlbum()));
|
||||||
|
holder.item.downloadedItemSubtitleTextView.setText(holder.itemView.getContext().getString(R.string.download_item_single_subtitle_formatter, countSong(Constants.DOWNLOAD_TYPE_ALBUM, song.getAlbumId(), songs)));
|
||||||
|
holder.item.downloadedItemPreTextView.setText(MusicUtil.getReadableString(song.getArtist()));
|
||||||
|
|
||||||
|
CustomGlideRequest.Builder
|
||||||
|
.from(holder.itemView.getContext(), song.getCoverArtId())
|
||||||
|
.build()
|
||||||
|
.into(holder.item.itemCoverImageView);
|
||||||
|
|
||||||
|
holder.item.itemCoverImageView.setVisibility(View.VISIBLE);
|
||||||
|
holder.item.downloadedItemMoreButton.setVisibility(View.GONE);
|
||||||
|
holder.item.divider.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
if (position > 0 && grouped.get(position - 1) != null && !Objects.equals(grouped.get(position - 1).getArtist(), grouped.get(position).getArtist())) {
|
||||||
|
holder.item.divider.setPadding(0, (int) holder.itemView.getContext().getResources().getDimension(R.dimen.downloaded_item_padding), 0, 0);
|
||||||
|
} else {
|
||||||
|
if (position > 0) holder.item.divider.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initArtistLayout(ViewHolder holder, int position) {
|
||||||
|
Child song = grouped.get(position);
|
||||||
|
|
||||||
|
holder.item.downloadedItemTitleTextView.setText(MusicUtil.getReadableString(song.getArtist()));
|
||||||
|
holder.item.downloadedItemSubtitleTextView.setText(holder.itemView.getContext().getString(R.string.download_item_single_subtitle_formatter, countSong(Constants.DOWNLOAD_TYPE_ARTIST, song.getArtistId(), songs)));
|
||||||
|
|
||||||
|
CustomGlideRequest.Builder
|
||||||
|
.from(holder.itemView.getContext(), song.getCoverArtId())
|
||||||
|
.build()
|
||||||
|
.into(holder.item.itemCoverImageView);
|
||||||
|
|
||||||
|
holder.item.itemCoverImageView.setVisibility(View.VISIBLE);
|
||||||
|
holder.item.downloadedItemMoreButton.setVisibility(View.GONE);
|
||||||
|
holder.item.divider.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initGenreLayout(ViewHolder holder, int position) {
|
||||||
|
Child song = grouped.get(position);
|
||||||
|
|
||||||
|
holder.item.downloadedItemTitleTextView.setText(MusicUtil.getReadableString(song.getGenre()));
|
||||||
|
holder.item.downloadedItemSubtitleTextView.setText(holder.itemView.getContext().getString(R.string.download_item_single_subtitle_formatter, countSong(Constants.DOWNLOAD_TYPE_GENRE, song.getGenre(), songs)));
|
||||||
|
|
||||||
|
holder.item.itemCoverImageView.setVisibility(View.GONE);
|
||||||
|
holder.item.downloadedItemMoreButton.setVisibility(View.GONE);
|
||||||
|
holder.item.divider.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initYearLayout(ViewHolder holder, int position) {
|
||||||
|
Child song = grouped.get(position);
|
||||||
|
|
||||||
|
holder.item.downloadedItemTitleTextView.setText(String.valueOf(song.getYear()));
|
||||||
|
holder.item.downloadedItemSubtitleTextView.setText(holder.itemView.getContext().getString(R.string.download_item_single_subtitle_formatter, countSong(Constants.DOWNLOAD_TYPE_YEAR, song.getYear().toString(), songs)));
|
||||||
|
|
||||||
|
holder.item.itemCoverImageView.setVisibility(View.GONE);
|
||||||
|
holder.item.downloadedItemMoreButton.setVisibility(View.GONE);
|
||||||
|
holder.item.divider.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
public class ViewHolder extends RecyclerView.ViewHolder {
|
public class ViewHolder extends RecyclerView.ViewHolder {
|
||||||
ItemHorizontalDownloadBinding item;
|
ItemHorizontalDownloadBinding item;
|
||||||
|
|
||||||
@@ -86,30 +247,62 @@ public class DownloadHorizontalAdapter extends RecyclerView.Adapter<DownloadHori
|
|||||||
|
|
||||||
this.item = item;
|
this.item = item;
|
||||||
|
|
||||||
item.downloadedSongTitleTextView.setSelected(true);
|
item.downloadedItemTitleTextView.setSelected(true);
|
||||||
item.downloadedSongArtistTextView.setSelected(true);
|
item.downloadedItemSubtitleTextView.setSelected(true);
|
||||||
|
|
||||||
itemView.setOnClickListener(v -> onClick());
|
itemView.setOnClickListener(v -> onClick());
|
||||||
itemView.setOnLongClickListener(v -> onLongClick());
|
itemView.setOnLongClickListener(v -> onLongClick());
|
||||||
|
|
||||||
item.downloadedSongMoreButton.setOnClickListener(v -> onLongClick());
|
item.downloadedItemMoreButton.setOnClickListener(v -> onLongClick());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onClick() {
|
public void onClick() {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putParcelableArrayList(Constants.TRACKS_OBJECT, new ArrayList<>(songs));
|
|
||||||
bundle.putInt(Constants.ITEM_POSITION, getBindingAdapterPosition());
|
|
||||||
|
|
||||||
|
switch (view) {
|
||||||
|
case Constants.DOWNLOAD_TYPE_TRACK:
|
||||||
|
bundle.putParcelableArrayList(Constants.TRACKS_OBJECT, new ArrayList<>(grouped));
|
||||||
|
bundle.putInt(Constants.ITEM_POSITION, getBindingAdapterPosition());
|
||||||
click.onMediaClick(bundle);
|
click.onMediaClick(bundle);
|
||||||
|
break;
|
||||||
|
case Constants.DOWNLOAD_TYPE_ALBUM:
|
||||||
|
bundle.putString(Constants.DOWNLOAD_TYPE_ALBUM, grouped.get(getBindingAdapterPosition()).getAlbumId());
|
||||||
|
click.onAlbumClick(bundle);
|
||||||
|
break;
|
||||||
|
case Constants.DOWNLOAD_TYPE_ARTIST:
|
||||||
|
bundle.putString(Constants.DOWNLOAD_TYPE_ARTIST, grouped.get(getBindingAdapterPosition()).getArtistId());
|
||||||
|
click.onArtistClick(bundle);
|
||||||
|
break;
|
||||||
|
case Constants.DOWNLOAD_TYPE_GENRE:
|
||||||
|
bundle.putString(Constants.DOWNLOAD_TYPE_GENRE, grouped.get(getBindingAdapterPosition()).getGenre());
|
||||||
|
click.onGenreClick(bundle);
|
||||||
|
break;
|
||||||
|
case Constants.DOWNLOAD_TYPE_YEAR:
|
||||||
|
bundle.putString(Constants.DOWNLOAD_TYPE_YEAR, grouped.get(getBindingAdapterPosition()).getYear().toString());
|
||||||
|
click.onYearClick(bundle);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean onLongClick() {
|
private boolean onLongClick() {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putParcelable(Constants.TRACK_OBJECT, songs.get(getBindingAdapterPosition()));
|
|
||||||
|
|
||||||
|
switch (view) {
|
||||||
|
case Constants.DOWNLOAD_TYPE_TRACK:
|
||||||
|
bundle.putParcelable(Constants.TRACK_OBJECT, grouped.get(getBindingAdapterPosition()));
|
||||||
click.onMediaLongClick(bundle);
|
click.onMediaLongClick(bundle);
|
||||||
|
return true;
|
||||||
|
case Constants.DOWNLOAD_TYPE_ALBUM:
|
||||||
|
bundle.putString(Constants.DOWNLOAD_TYPE_ALBUM, grouped.get(getBindingAdapterPosition()).getAlbumId());
|
||||||
|
click.onAlbumLongClick(bundle);
|
||||||
|
return true;
|
||||||
|
case Constants.DOWNLOAD_TYPE_ARTIST:
|
||||||
|
bundle.putString(Constants.DOWNLOAD_TYPE_ARTIST, grouped.get(getBindingAdapterPosition()).getArtistId());
|
||||||
|
click.onArtistLongClick(bundle);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.cappielloantonio.tempo.ui.adapter;
|
|||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
@@ -46,6 +47,9 @@ public class MusicDirectoryAdapter extends RecyclerView.Adapter<MusicDirectoryAd
|
|||||||
.from(holder.itemView.getContext(), child.getCoverArtId())
|
.from(holder.itemView.getContext(), child.getCoverArtId())
|
||||||
.build()
|
.build()
|
||||||
.into(holder.item.musicDirectoryCoverImageView);
|
.into(holder.item.musicDirectoryCoverImageView);
|
||||||
|
|
||||||
|
holder.item.musicDirectoryMoreButton.setVisibility(child.isDir() ? View.VISIBLE : View.INVISIBLE);
|
||||||
|
holder.item.musicDirectoryPlayButton.setVisibility(child.isDir() ? View.INVISIBLE : View.VISIBLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -9,16 +9,17 @@ import androidx.media3.common.util.UnstableApi;
|
|||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import com.cappielloantonio.tempo.databinding.ItemLibraryMusicIndexBinding;
|
import com.cappielloantonio.tempo.databinding.ItemLibraryMusicIndexBinding;
|
||||||
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
import com.cappielloantonio.tempo.helper.recyclerview.FastScrollbar;
|
||||||
import com.cappielloantonio.tempo.interfaces.ClickCallback;
|
import com.cappielloantonio.tempo.interfaces.ClickCallback;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.Artist;
|
import com.cappielloantonio.tempo.subsonic.models.Artist;
|
||||||
import com.cappielloantonio.tempo.util.Constants;
|
import com.cappielloantonio.tempo.util.Constants;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
public class MusicIndexAdapter extends RecyclerView.Adapter<MusicIndexAdapter.ViewHolder> {
|
public class MusicIndexAdapter extends RecyclerView.Adapter<MusicIndexAdapter.ViewHolder> implements FastScrollbar.BubbleTextGetter {
|
||||||
private final ClickCallback click;
|
private final ClickCallback click;
|
||||||
|
|
||||||
private List<Artist> artists;
|
private List<Artist> artists;
|
||||||
@@ -41,10 +42,10 @@ public class MusicIndexAdapter extends RecyclerView.Adapter<MusicIndexAdapter.Vi
|
|||||||
|
|
||||||
holder.item.musicIndexTitleTextView.setText(artist.getName());
|
holder.item.musicIndexTitleTextView.setText(artist.getName());
|
||||||
|
|
||||||
CustomGlideRequest.Builder
|
/* CustomGlideRequest.Builder
|
||||||
.from(holder.itemView.getContext(), artist.getName())
|
.from(holder.itemView.getContext(), artist.getName())
|
||||||
.build()
|
.build()
|
||||||
.into(holder.item.musicIndexCoverImageView);
|
.into(holder.item.musicIndexCoverImageView); */
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -57,6 +58,11 @@ public class MusicIndexAdapter extends RecyclerView.Adapter<MusicIndexAdapter.Vi
|
|||||||
notifyDataSetChanged();
|
notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getTextToShowInBubble(int pos) {
|
||||||
|
return artists != null && !artists.isEmpty() ? Character.toString(Objects.requireNonNull(artists.get(pos).getName().toUpperCase()).charAt(0)) : null;
|
||||||
|
}
|
||||||
|
|
||||||
public class ViewHolder extends RecyclerView.ViewHolder {
|
public class ViewHolder extends RecyclerView.ViewHolder {
|
||||||
ItemLibraryMusicIndexBinding item;
|
ItemLibraryMusicIndexBinding item;
|
||||||
|
|
||||||
|
|||||||
@@ -128,14 +128,14 @@ public class PodcastChannelCatalogueAdapter extends RecyclerView.Adapter<Podcast
|
|||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putParcelable(Constants.PODCAST_CHANNEL_OBJECT, podcastChannels.get(getBindingAdapterPosition()));
|
bundle.putParcelable(Constants.PODCAST_CHANNEL_OBJECT, podcastChannels.get(getBindingAdapterPosition()));
|
||||||
|
|
||||||
click.onAlbumClick(bundle);
|
click.onPodcastChannelClick(bundle);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean onLongClick() {
|
private boolean onLongClick() {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putParcelable(Constants.PODCAST_CHANNEL_OBJECT, podcastChannels.get(getBindingAdapterPosition()));
|
bundle.putParcelable(Constants.PODCAST_CHANNEL_OBJECT, podcastChannels.get(getBindingAdapterPosition()));
|
||||||
|
|
||||||
click.onAlbumLongClick(bundle);
|
click.onPodcastChannelLongClick(bundle);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.cappielloantonio.tempo.ui.adapter;
|
|||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
@@ -18,11 +19,14 @@ import com.cappielloantonio.tempo.util.MusicUtil;
|
|||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class PodcastEpisodeAdapter extends RecyclerView.Adapter<PodcastEpisodeAdapter.ViewHolder> {
|
public class PodcastEpisodeAdapter extends RecyclerView.Adapter<PodcastEpisodeAdapter.ViewHolder> {
|
||||||
private final ClickCallback click;
|
private final ClickCallback click;
|
||||||
|
|
||||||
private List<PodcastEpisode> podcastEpisodes;
|
private List<PodcastEpisode> podcastEpisodes;
|
||||||
|
private List<PodcastEpisode> podcastEpisodesFull;
|
||||||
|
|
||||||
public PodcastEpisodeAdapter(ClickCallback click) {
|
public PodcastEpisodeAdapter(ClickCallback click) {
|
||||||
this.click = click;
|
this.click = click;
|
||||||
@@ -50,6 +54,10 @@ public class PodcastEpisodeAdapter extends RecyclerView.Adapter<PodcastEpisodeAd
|
|||||||
.from(holder.itemView.getContext(), podcastEpisode.getCoverArtId())
|
.from(holder.itemView.getContext(), podcastEpisode.getCoverArtId())
|
||||||
.build()
|
.build()
|
||||||
.into(holder.item.podcastCoverImageView);
|
.into(holder.item.podcastCoverImageView);
|
||||||
|
|
||||||
|
holder.item.podcastPlayButton.setEnabled(podcastEpisode.getStatus().equals("completed"));
|
||||||
|
holder.item.podcastMoreButton.setVisibility(podcastEpisode.getStatus().equals("completed") ? View.VISIBLE : View.GONE);
|
||||||
|
holder.item.podcastDownloadRequestButton.setVisibility(podcastEpisode.getStatus().equals("completed") ? View.GONE : View.VISIBLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -58,7 +66,8 @@ public class PodcastEpisodeAdapter extends RecyclerView.Adapter<PodcastEpisodeAd
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setItems(List<PodcastEpisode> podcastEpisodes) {
|
public void setItems(List<PodcastEpisode> podcastEpisodes) {
|
||||||
this.podcastEpisodes = podcastEpisodes;
|
this.podcastEpisodesFull = podcastEpisodes;
|
||||||
|
this.podcastEpisodes = podcastEpisodesFull.stream().filter(podcastEpisode -> Objects.equals(podcastEpisode.getStatus(), "completed")).collect(Collectors.toList());
|
||||||
notifyDataSetChanged();
|
notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,16 +94,24 @@ public class PodcastEpisodeAdapter extends RecyclerView.Adapter<PodcastEpisodeAd
|
|||||||
|
|
||||||
item.podcastPlayButton.setOnClickListener(v -> onClick());
|
item.podcastPlayButton.setOnClickListener(v -> onClick());
|
||||||
item.podcastMoreButton.setOnClickListener(v -> openMore());
|
item.podcastMoreButton.setOnClickListener(v -> openMore());
|
||||||
|
item.podcastDownloadRequestButton.setOnClickListener(v -> requestDownload());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onClick() {
|
public void onClick() {
|
||||||
|
PodcastEpisode podcastEpisode = podcastEpisodes.get(getBindingAdapterPosition());
|
||||||
|
|
||||||
|
if (podcastEpisode.getStatus().equals("completed")) {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putParcelable(Constants.PODCAST_OBJECT, podcastEpisodes.get(getBindingAdapterPosition()));
|
bundle.putParcelable(Constants.PODCAST_OBJECT, podcastEpisodes.get(getBindingAdapterPosition()));
|
||||||
|
|
||||||
click.onPodcastEpisodeClick(bundle);
|
click.onPodcastEpisodeClick(bundle);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private boolean openMore() {
|
private boolean openMore() {
|
||||||
|
PodcastEpisode podcastEpisode = podcastEpisodes.get(getBindingAdapterPosition());
|
||||||
|
|
||||||
|
if (podcastEpisode.getStatus().equals("completed")) {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putParcelable(Constants.PODCAST_OBJECT, podcastEpisodes.get(getBindingAdapterPosition()));
|
bundle.putParcelable(Constants.PODCAST_OBJECT, podcastEpisodes.get(getBindingAdapterPosition()));
|
||||||
|
|
||||||
@@ -102,5 +119,32 @@ public class PodcastEpisodeAdapter extends RecyclerView.Adapter<PodcastEpisodeAd
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void requestDownload() {
|
||||||
|
PodcastEpisode podcastEpisode = podcastEpisodes.get(getBindingAdapterPosition());
|
||||||
|
|
||||||
|
if (!podcastEpisode.getStatus().equals("completed")) {
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
bundle.putParcelable(Constants.PODCAST_OBJECT, podcastEpisodes.get(getBindingAdapterPosition()));
|
||||||
|
|
||||||
|
click.onPodcastEpisodeAltClick(bundle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sort(String order) {
|
||||||
|
switch (order) {
|
||||||
|
case Constants.PODCAST_FILTER_BY_DOWNLOAD:
|
||||||
|
podcastEpisodes = podcastEpisodesFull.stream().filter(podcastEpisode -> Objects.equals(podcastEpisode.getStatus(), "completed")).collect(Collectors.toList());
|
||||||
|
break;
|
||||||
|
case Constants.PODCAST_FILTER_BY_ALL:
|
||||||
|
podcastEpisodes = podcastEpisodesFull;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -25,13 +25,15 @@ import java.util.List;
|
|||||||
@UnstableApi
|
@UnstableApi
|
||||||
public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAdapter.ViewHolder> {
|
public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAdapter.ViewHolder> {
|
||||||
private final ClickCallback click;
|
private final ClickCallback click;
|
||||||
private final boolean isCoverVisible;
|
private final boolean showCoverArt;
|
||||||
|
private final boolean showAlbum;
|
||||||
|
|
||||||
private List<Child> songs;
|
private List<Child> songs;
|
||||||
|
|
||||||
public SongHorizontalAdapter(ClickCallback click, boolean isCoverVisible) {
|
public SongHorizontalAdapter(ClickCallback click, boolean showCoverArt, boolean showAlbum) {
|
||||||
this.click = click;
|
this.click = click;
|
||||||
this.isCoverVisible = isCoverVisible;
|
this.showCoverArt = showCoverArt;
|
||||||
|
this.showAlbum = showAlbum;
|
||||||
this.songs = Collections.emptyList();
|
this.songs = Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,8 +49,8 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAd
|
|||||||
Child song = songs.get(position);
|
Child song = songs.get(position);
|
||||||
|
|
||||||
holder.item.searchResultSongTitleTextView.setText(MusicUtil.getReadableString(song.getTitle()));
|
holder.item.searchResultSongTitleTextView.setText(MusicUtil.getReadableString(song.getTitle()));
|
||||||
holder.item.searchResultSongSubtitleTextView.setText(holder.itemView.getContext().getString(R.string.song_subtitle_formatter, MusicUtil.getReadableString(song.getArtist()), MusicUtil.getReadableDurationString(song.getDuration() != null ? song.getDuration() : 0, false)));
|
holder.item.searchResultSongSubtitleTextView.setText(holder.itemView.getContext().getString(R.string.song_subtitle_formatter, MusicUtil.getReadableString(this.showAlbum ? song.getAlbum() : song.getArtist()), MusicUtil.getReadableDurationString(song.getDuration() != null ? song.getDuration() : 0, false)));
|
||||||
holder.item.trackNumberTextView.setText(String.valueOf(song.getTrack()));
|
holder.item.trackNumberTextView.setText(MusicUtil.getReadableTrackNumber(holder.itemView.getContext(), song.getTrack()));
|
||||||
|
|
||||||
if (DownloadUtil.getDownloadTracker(holder.itemView.getContext()).isDownloaded(song.getId())) {
|
if (DownloadUtil.getDownloadTracker(holder.itemView.getContext()).isDownloaded(song.getId())) {
|
||||||
holder.item.searchResultDowanloadIndicatorImageView.setVisibility(View.VISIBLE);
|
holder.item.searchResultDowanloadIndicatorImageView.setVisibility(View.VISIBLE);
|
||||||
@@ -56,16 +58,16 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAd
|
|||||||
holder.item.searchResultDowanloadIndicatorImageView.setVisibility(View.GONE);
|
holder.item.searchResultDowanloadIndicatorImageView.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isCoverVisible) CustomGlideRequest.Builder
|
if (showCoverArt) CustomGlideRequest.Builder
|
||||||
.from(holder.itemView.getContext(), song.getCoverArtId())
|
.from(holder.itemView.getContext(), song.getCoverArtId())
|
||||||
.build()
|
.build()
|
||||||
.into(holder.item.songCoverImageView);
|
.into(holder.item.songCoverImageView);
|
||||||
|
|
||||||
if (isCoverVisible) holder.item.trackNumberTextView.setVisibility(View.INVISIBLE);
|
if (showCoverArt) holder.item.trackNumberTextView.setVisibility(View.INVISIBLE);
|
||||||
|
|
||||||
if (!isCoverVisible) holder.item.songCoverImageView.setVisibility(View.INVISIBLE);
|
if (!showCoverArt) holder.item.songCoverImageView.setVisibility(View.INVISIBLE);
|
||||||
|
|
||||||
if (!isCoverVisible && (position > 0 && songs.get(position - 1) != null && songs.get(position - 1).getDiscNumber() != null && songs.get(position).getDiscNumber() != null && songs.get(position - 1).getDiscNumber() < songs.get(position).getDiscNumber())) {
|
if (!showCoverArt && (position > 0 && songs.get(position - 1) != null && songs.get(position - 1).getDiscNumber() != null && songs.get(position).getDiscNumber() != null && songs.get(position - 1).getDiscNumber() < songs.get(position).getDiscNumber())) {
|
||||||
holder.item.differentDiskDivider.setVisibility(View.VISIBLE);
|
holder.item.differentDiskDivider.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -76,10 +78,20 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAd
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setItems(List<Child> songs) {
|
public void setItems(List<Child> songs) {
|
||||||
this.songs = songs;
|
this.songs = songs != null ? songs : Collections.emptyList();
|
||||||
notifyDataSetChanged();
|
notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemViewType(int position) {
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getItemId(int position) {
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
public Child getItem(int id) {
|
public Child getItem(int id) {
|
||||||
return songs.get(id);
|
return songs.get(id);
|
||||||
}
|
}
|
||||||
@@ -104,7 +116,7 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAd
|
|||||||
public void onClick() {
|
public void onClick() {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putParcelableArrayList(Constants.TRACKS_OBJECT, new ArrayList<>(MusicUtil.limitPlayableMedia(songs, getBindingAdapterPosition())));
|
bundle.putParcelableArrayList(Constants.TRACKS_OBJECT, new ArrayList<>(MusicUtil.limitPlayableMedia(songs, getBindingAdapterPosition())));
|
||||||
bundle.putInt(Constants.ITEM_POSITION, MusicUtil.getPlayableMediaPosition(getBindingAdapterPosition()));
|
bundle.putInt(Constants.ITEM_POSITION, MusicUtil.getPlayableMediaPosition(songs, getBindingAdapterPosition()));
|
||||||
|
|
||||||
click.onMediaClick(bundle);
|
click.onMediaClick(bundle);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package com.cappielloantonio.tempo.ui.dialog;
|
||||||
|
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.app.Dialog;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.widget.Button;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.OptIn;
|
||||||
|
import androidx.fragment.app.DialogFragment;
|
||||||
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
|
||||||
|
import com.cappielloantonio.tempo.R;
|
||||||
|
import com.cappielloantonio.tempo.databinding.DialogDeleteDownloadStorageBinding;
|
||||||
|
import com.cappielloantonio.tempo.interfaces.DialogClickCallback;
|
||||||
|
import com.cappielloantonio.tempo.util.DownloadUtil;
|
||||||
|
|
||||||
|
@OptIn(markerClass = UnstableApi.class)
|
||||||
|
public class DeleteDownloadStorageDialog extends DialogFragment {
|
||||||
|
private DialogDeleteDownloadStorageBinding bind;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||||
|
bind = DialogDeleteDownloadStorageBinding.inflate(getLayoutInflater());
|
||||||
|
|
||||||
|
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||||
|
|
||||||
|
builder.setView(bind.getRoot())
|
||||||
|
.setTitle(R.string.delete_download_storage_dialog_title)
|
||||||
|
.setPositiveButton(R.string.delete_download_storage_dialog_positive_button, null)
|
||||||
|
.setNegativeButton(R.string.delete_download_storage_dialog_negative_button, null);
|
||||||
|
|
||||||
|
return builder.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
setButtonAction();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroyView() {
|
||||||
|
super.onDestroyView();
|
||||||
|
bind = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setButtonAction() {
|
||||||
|
AlertDialog dialog = ((AlertDialog) getDialog());
|
||||||
|
|
||||||
|
if (dialog != null) {
|
||||||
|
Button positiveButton = dialog.getButton(Dialog.BUTTON_POSITIVE);
|
||||||
|
positiveButton.setOnClickListener(v -> {
|
||||||
|
DownloadUtil.getDownloadTracker(requireContext()).removeAll();
|
||||||
|
dialog.dismiss();
|
||||||
|
});
|
||||||
|
|
||||||
|
Button negativeButton = dialog.getButton(Dialog.BUTTON_NEGATIVE);
|
||||||
|
negativeButton.setOnClickListener(v -> {
|
||||||
|
dialog.dismiss();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
package com.cappielloantonio.tempo.ui.dialog;
|
||||||
|
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.app.Dialog;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.widget.Button;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.OptIn;
|
||||||
|
import androidx.fragment.app.DialogFragment;
|
||||||
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
|
||||||
|
import com.cappielloantonio.tempo.R;
|
||||||
|
import com.cappielloantonio.tempo.databinding.DialogDownloadStorageBinding;
|
||||||
|
import com.cappielloantonio.tempo.interfaces.DialogClickCallback;
|
||||||
|
import com.cappielloantonio.tempo.util.DownloadUtil;
|
||||||
|
import com.cappielloantonio.tempo.util.Preferences;
|
||||||
|
|
||||||
|
@OptIn(markerClass = UnstableApi.class)
|
||||||
|
public class DownloadStorageDialog extends DialogFragment {
|
||||||
|
private DialogDownloadStorageBinding bind;
|
||||||
|
|
||||||
|
private final DialogClickCallback dialogClickCallback;
|
||||||
|
|
||||||
|
public DownloadStorageDialog(DialogClickCallback dialogClickCallback) {
|
||||||
|
this.dialogClickCallback = dialogClickCallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||||
|
bind = DialogDownloadStorageBinding.inflate(getLayoutInflater());
|
||||||
|
|
||||||
|
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||||
|
|
||||||
|
builder.setView(bind.getRoot())
|
||||||
|
.setTitle(R.string.download_storage_dialog_title)
|
||||||
|
.setPositiveButton(R.string.download_storage_external_dialog_positive_button, null)
|
||||||
|
.setNegativeButton(R.string.download_storage_internal_dialog_negative_button, null);
|
||||||
|
|
||||||
|
return builder.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
setButtonAction();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroyView() {
|
||||||
|
super.onDestroyView();
|
||||||
|
bind = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setButtonAction() {
|
||||||
|
AlertDialog dialog = ((AlertDialog) getDialog());
|
||||||
|
|
||||||
|
if (dialog != null) {
|
||||||
|
Button positiveButton = dialog.getButton(Dialog.BUTTON_POSITIVE);
|
||||||
|
positiveButton.setOnClickListener(v -> {
|
||||||
|
int currentPreference = Preferences.getDownloadStoragePreference();
|
||||||
|
int newPreference = 1;
|
||||||
|
|
||||||
|
if (currentPreference != newPreference) {
|
||||||
|
Preferences.setDownloadStoragePreference(newPreference);
|
||||||
|
DownloadUtil.getDownloadTracker(requireContext()).removeAll();
|
||||||
|
dialogClickCallback.onPositiveClick();
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog.dismiss();
|
||||||
|
});
|
||||||
|
|
||||||
|
Button negativeButton = dialog.getButton(Dialog.BUTTON_NEGATIVE);
|
||||||
|
negativeButton.setOnClickListener(v -> {
|
||||||
|
int currentPreference = Preferences.getDownloadStoragePreference();
|
||||||
|
int newPreference = 0;
|
||||||
|
|
||||||
|
if (currentPreference != newPreference) {
|
||||||
|
Preferences.setDownloadStoragePreference(newPreference);
|
||||||
|
DownloadUtil.getDownloadTracker(requireContext()).removeAll();
|
||||||
|
dialogClickCallback.onNegativeClick();
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog.dismiss();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -67,7 +67,7 @@ public class PlaylistChooserDialog extends DialogFragment implements ClickCallba
|
|||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putParcelable(Constants.TRACK_OBJECT, playlistChooserViewModel.getSongToAdd());
|
bundle.putParcelable(Constants.TRACK_OBJECT, playlistChooserViewModel.getSongToAdd());
|
||||||
|
|
||||||
PlaylistEditorDialog dialog = new PlaylistEditorDialog();
|
PlaylistEditorDialog dialog = new PlaylistEditorDialog(null);
|
||||||
dialog.setArguments(bundle);
|
dialog.setArguments(bundle);
|
||||||
dialog.show(requireActivity().getSupportFragmentManager(), null);
|
dialog.show(requireActivity().getSupportFragmentManager(), null);
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import androidx.recyclerview.widget.RecyclerView;
|
|||||||
|
|
||||||
import com.cappielloantonio.tempo.R;
|
import com.cappielloantonio.tempo.R;
|
||||||
import com.cappielloantonio.tempo.databinding.DialogPlaylistEditorBinding;
|
import com.cappielloantonio.tempo.databinding.DialogPlaylistEditorBinding;
|
||||||
|
import com.cappielloantonio.tempo.interfaces.PlaylistCallback;
|
||||||
import com.cappielloantonio.tempo.ui.adapter.PlaylistDialogSongHorizontalAdapter;
|
import com.cappielloantonio.tempo.ui.adapter.PlaylistDialogSongHorizontalAdapter;
|
||||||
import com.cappielloantonio.tempo.util.Constants;
|
import com.cappielloantonio.tempo.util.Constants;
|
||||||
import com.cappielloantonio.tempo.util.MusicUtil;
|
import com.cappielloantonio.tempo.util.MusicUtil;
|
||||||
@@ -25,10 +26,15 @@ import java.util.Objects;
|
|||||||
public class PlaylistEditorDialog extends DialogFragment {
|
public class PlaylistEditorDialog extends DialogFragment {
|
||||||
private DialogPlaylistEditorBinding bind;
|
private DialogPlaylistEditorBinding bind;
|
||||||
private PlaylistEditorViewModel playlistEditorViewModel;
|
private PlaylistEditorViewModel playlistEditorViewModel;
|
||||||
|
private PlaylistCallback playlistCallback;
|
||||||
|
|
||||||
private String playlistName;
|
private String playlistName;
|
||||||
private PlaylistDialogSongHorizontalAdapter playlistDialogSongHorizontalAdapter;
|
private PlaylistDialogSongHorizontalAdapter playlistDialogSongHorizontalAdapter;
|
||||||
|
|
||||||
|
public PlaylistEditorDialog(PlaylistCallback playlistCallback) {
|
||||||
|
this.playlistCallback = playlistCallback;
|
||||||
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||||
@@ -85,13 +91,13 @@ public class PlaylistEditorDialog extends DialogFragment {
|
|||||||
playlistEditorViewModel.updatePlaylist(playlistName);
|
playlistEditorViewModel.updatePlaylist(playlistName);
|
||||||
}
|
}
|
||||||
|
|
||||||
Objects.requireNonNull(getDialog()).dismiss();
|
dialogDismiss();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
((AlertDialog) Objects.requireNonNull(getDialog())).getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener(v -> {
|
((AlertDialog) Objects.requireNonNull(getDialog())).getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener(v -> {
|
||||||
playlistEditorViewModel.deletePlaylist();
|
playlistEditorViewModel.deletePlaylist();
|
||||||
Objects.requireNonNull(getDialog()).dismiss();
|
dialogDismiss();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,7 +108,9 @@ public class PlaylistEditorDialog extends DialogFragment {
|
|||||||
playlistDialogSongHorizontalAdapter = new PlaylistDialogSongHorizontalAdapter();
|
playlistDialogSongHorizontalAdapter = new PlaylistDialogSongHorizontalAdapter();
|
||||||
bind.playlistSongRecyclerView.setAdapter(playlistDialogSongHorizontalAdapter);
|
bind.playlistSongRecyclerView.setAdapter(playlistDialogSongHorizontalAdapter);
|
||||||
|
|
||||||
playlistEditorViewModel.getPlaylistSongLiveList().observe(requireActivity(), songs -> playlistDialogSongHorizontalAdapter.setItems(songs));
|
playlistEditorViewModel.getPlaylistSongLiveList().observe(requireActivity(), songs -> {
|
||||||
|
if (songs != null) playlistDialogSongHorizontalAdapter.setItems(songs);
|
||||||
|
});
|
||||||
|
|
||||||
new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.LEFT) {
|
new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.LEFT) {
|
||||||
int originalPosition = -1;
|
int originalPosition = -1;
|
||||||
@@ -157,4 +165,9 @@ public class PlaylistEditorDialog extends DialogFragment {
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void dialogDismiss() {
|
||||||
|
Objects.requireNonNull(getDialog()).dismiss();
|
||||||
|
playlistCallback.onDismiss();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,132 @@
|
|||||||
|
package com.cappielloantonio.tempo.ui.dialog;
|
||||||
|
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.app.Dialog;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.fragment.app.DialogFragment;
|
||||||
|
import androidx.media3.common.MediaMetadata;
|
||||||
|
|
||||||
|
import com.cappielloantonio.tempo.R;
|
||||||
|
import com.cappielloantonio.tempo.databinding.DialogTrackInfoBinding;
|
||||||
|
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
||||||
|
import com.cappielloantonio.tempo.util.Constants;
|
||||||
|
import com.cappielloantonio.tempo.util.MusicUtil;
|
||||||
|
import com.cappielloantonio.tempo.util.Preferences;
|
||||||
|
|
||||||
|
public class TrackInfoDialog extends DialogFragment {
|
||||||
|
private static final String TAG = "TrackInfoDialog";
|
||||||
|
|
||||||
|
private DialogTrackInfoBinding bind;
|
||||||
|
private MediaMetadata mediaMetadata;
|
||||||
|
|
||||||
|
public TrackInfoDialog(MediaMetadata mediaMetadata) {
|
||||||
|
this.mediaMetadata = mediaMetadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||||
|
bind = DialogTrackInfoBinding.inflate(getLayoutInflater());
|
||||||
|
|
||||||
|
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||||
|
|
||||||
|
builder.setView(bind.getRoot())
|
||||||
|
.setPositiveButton(R.string.track_info_dialog_positive_button, (dialog, id) -> dialog.cancel());
|
||||||
|
|
||||||
|
return builder.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStart() {
|
||||||
|
super.onStart();
|
||||||
|
|
||||||
|
setTrackInfo();
|
||||||
|
setTrackTranscodingInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroyView() {
|
||||||
|
super.onDestroyView();
|
||||||
|
bind = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setTrackInfo() {
|
||||||
|
bind.trakTitleInfoTextView.setText(mediaMetadata.title);
|
||||||
|
bind.trakArtistInfoTextView.setText(mediaMetadata.artist);
|
||||||
|
|
||||||
|
if (mediaMetadata.extras != null) {
|
||||||
|
CustomGlideRequest.Builder
|
||||||
|
.from(requireContext(), mediaMetadata.extras.getString("coverArtId", ""))
|
||||||
|
.build()
|
||||||
|
.into(bind.trackCoverInfoImageView);
|
||||||
|
|
||||||
|
bind.titleValueSector.setText(mediaMetadata.extras.getString("title", getString(R.string.label_placeholder)));
|
||||||
|
bind.albumValueSector.setText(mediaMetadata.extras.getString("album", getString(R.string.label_placeholder)));
|
||||||
|
bind.artistValueSector.setText(mediaMetadata.extras.getString("artist", getString(R.string.label_placeholder)));
|
||||||
|
bind.trackNumberValueSector.setText(String.valueOf(mediaMetadata.extras.getInt("track", 0)));
|
||||||
|
bind.yearValueSector.setText(String.valueOf(mediaMetadata.extras.getInt("year", 0)));
|
||||||
|
bind.genreValueSector.setText(mediaMetadata.extras.getString("genre", getString(R.string.label_placeholder)));
|
||||||
|
bind.sizeValueSector.setText(MusicUtil.getReadableByteCount(mediaMetadata.extras.getLong("size", 0)));
|
||||||
|
bind.contentTypeValueSector.setText(mediaMetadata.extras.getString("contentType", getString(R.string.label_placeholder)));
|
||||||
|
bind.suffixValueSector.setText(mediaMetadata.extras.getString("suffix", getString(R.string.label_placeholder)));
|
||||||
|
bind.transcodedContentTypeValueSector.setText(mediaMetadata.extras.getString("transcodedContentType", getString(R.string.label_placeholder)));
|
||||||
|
bind.transcodedSuffixValueSector.setText(mediaMetadata.extras.getString("transcodedSuffix", getString(R.string.label_placeholder)));
|
||||||
|
bind.durationValueSector.setText(MusicUtil.getReadableDurationString(mediaMetadata.extras.getInt("duration", 0), false));
|
||||||
|
bind.bitrateValueSector.setText(mediaMetadata.extras.getInt("bitrate", 0) + " kbps");
|
||||||
|
bind.pathValueSector.setText(mediaMetadata.extras.getString("path", getString(R.string.label_placeholder)));
|
||||||
|
bind.discNumberValueSector.setText(String.valueOf(mediaMetadata.extras.getInt("discNumber", 0)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setTrackTranscodingInfo() {
|
||||||
|
StringBuilder info = new StringBuilder();
|
||||||
|
|
||||||
|
boolean prioritizeServerTranscoding = Preferences.isServerPrioritized();
|
||||||
|
|
||||||
|
String transcodingExtension = MusicUtil.getTranscodingFormatPreference();
|
||||||
|
String transcodingBitrate = Integer.parseInt(MusicUtil.getBitratePreference()) != 0 ? Integer.parseInt(MusicUtil.getBitratePreference()) + "kbps" : "Original";
|
||||||
|
|
||||||
|
if (mediaMetadata.extras != null && mediaMetadata.extras.getString("uri", "").contains(Constants.DOWNLOAD_URI)) {
|
||||||
|
info.append(getString(R.string.track_info_summary_downloaded_file));
|
||||||
|
|
||||||
|
bind.trakTranscodingInfoTextView.setText(info);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prioritizeServerTranscoding) {
|
||||||
|
info.append(getString(R.string.track_info_summary_server_prioritized));
|
||||||
|
|
||||||
|
bind.trakTranscodingInfoTextView.setText(info);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!prioritizeServerTranscoding && transcodingExtension.equals("raw") && transcodingBitrate.equals("Original")) {
|
||||||
|
info.append(getString(R.string.track_info_summary_original_file));
|
||||||
|
|
||||||
|
bind.trakTranscodingInfoTextView.setText(info);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!prioritizeServerTranscoding && !transcodingExtension.equals("raw") && transcodingBitrate.equals("Original")) {
|
||||||
|
info.append(getString(R.string.track_info_summary_transcoding_codec, transcodingExtension));
|
||||||
|
|
||||||
|
bind.trakTranscodingInfoTextView.setText(info);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!prioritizeServerTranscoding && transcodingExtension.equals("raw") && !transcodingBitrate.equals("Original")) {
|
||||||
|
info.append(getString(R.string.track_info_summary_transcoding_bitrate, transcodingBitrate));
|
||||||
|
|
||||||
|
bind.trakTranscodingInfoTextView.setText(info);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!prioritizeServerTranscoding && !transcodingExtension.equals("raw") && !transcodingBitrate.equals("Original")) {
|
||||||
|
info.append(getString(R.string.track_info_summary_full_transcode, transcodingExtension, transcodingBitrate));
|
||||||
|
|
||||||
|
bind.trakTranscodingInfoTextView.setText(info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -181,7 +181,7 @@ public class AlbumPageFragment extends Fragment implements ClickCallback {
|
|||||||
bind.songRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
|
bind.songRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
|
||||||
bind.songRecyclerView.setHasFixedSize(true);
|
bind.songRecyclerView.setHasFixedSize(true);
|
||||||
|
|
||||||
songHorizontalAdapter = new SongHorizontalAdapter(this, false);
|
songHorizontalAdapter = new SongHorizontalAdapter(this, false, false);
|
||||||
bind.songRecyclerView.setAdapter(songHorizontalAdapter);
|
bind.songRecyclerView.setAdapter(songHorizontalAdapter);
|
||||||
|
|
||||||
albumPageViewModel.getAlbumSongLiveList().observe(getViewLifecycleOwner(), songs -> songHorizontalAdapter.setItems(songs));
|
albumPageViewModel.getAlbumSongLiveList().observe(getViewLifecycleOwner(), songs -> songHorizontalAdapter.setItems(songs));
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
|
|||||||
private void initTopSongsView() {
|
private void initTopSongsView() {
|
||||||
bind.mostStreamedSongRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
|
bind.mostStreamedSongRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
|
||||||
|
|
||||||
songHorizontalAdapter = new SongHorizontalAdapter(this, true);
|
songHorizontalAdapter = new SongHorizontalAdapter(this, true, true);
|
||||||
bind.mostStreamedSongRecyclerView.setAdapter(songHorizontalAdapter);
|
bind.mostStreamedSongRecyclerView.setAdapter(songHorizontalAdapter);
|
||||||
artistPageViewModel.getArtistTopSongList().observe(getViewLifecycleOwner(), songs -> {
|
artistPageViewModel.getArtistTopSongList().observe(getViewLifecycleOwner(), songs -> {
|
||||||
if (songs == null) {
|
if (songs == null) {
|
||||||
|
|||||||
@@ -3,10 +3,14 @@ package com.cappielloantonio.tempo.ui.fragment;
|
|||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuInflater;
|
||||||
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.core.view.ViewCompat;
|
import androidx.core.view.ViewCompat;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
@@ -19,14 +23,21 @@ import androidx.recyclerview.widget.LinearLayoutManager;
|
|||||||
import com.cappielloantonio.tempo.R;
|
import com.cappielloantonio.tempo.R;
|
||||||
import com.cappielloantonio.tempo.databinding.FragmentDirectoryBinding;
|
import com.cappielloantonio.tempo.databinding.FragmentDirectoryBinding;
|
||||||
import com.cappielloantonio.tempo.interfaces.ClickCallback;
|
import com.cappielloantonio.tempo.interfaces.ClickCallback;
|
||||||
|
import com.cappielloantonio.tempo.model.Download;
|
||||||
import com.cappielloantonio.tempo.service.MediaManager;
|
import com.cappielloantonio.tempo.service.MediaManager;
|
||||||
import com.cappielloantonio.tempo.service.MediaService;
|
import com.cappielloantonio.tempo.service.MediaService;
|
||||||
|
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||||
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
||||||
import com.cappielloantonio.tempo.ui.adapter.MusicDirectoryAdapter;
|
import com.cappielloantonio.tempo.ui.adapter.MusicDirectoryAdapter;
|
||||||
import com.cappielloantonio.tempo.util.Constants;
|
import com.cappielloantonio.tempo.util.Constants;
|
||||||
|
import com.cappielloantonio.tempo.util.DownloadUtil;
|
||||||
|
import com.cappielloantonio.tempo.util.MappingUtil;
|
||||||
import com.cappielloantonio.tempo.viewmodel.DirectoryViewModel;
|
import com.cappielloantonio.tempo.viewmodel.DirectoryViewModel;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
public class DirectoryFragment extends Fragment implements ClickCallback {
|
public class DirectoryFragment extends Fragment implements ClickCallback {
|
||||||
private static final String TAG = "DirectoryFragment";
|
private static final String TAG = "DirectoryFragment";
|
||||||
@@ -39,6 +50,18 @@ public class DirectoryFragment extends Fragment implements ClickCallback {
|
|||||||
|
|
||||||
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setHasOptionsMenu(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
|
||||||
|
super.onCreateOptionsMenu(menu, inflater);
|
||||||
|
inflater.inflate(R.menu.directory_page_menu, menu);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
activity = (MainActivity) getActivity();
|
activity = (MainActivity) getActivity();
|
||||||
@@ -71,6 +94,24 @@ public class DirectoryFragment extends Fragment implements ClickCallback {
|
|||||||
bind = null;
|
bind = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||||
|
if (item.getItemId() == R.id.action_download_directory) {
|
||||||
|
directoryViewModel.loadMusicDirectory(getArguments().getString(Constants.MUSIC_DIRECTORY_ID)).observe(getViewLifecycleOwner(), directory -> {
|
||||||
|
if (isVisible() && getActivity() != null) {
|
||||||
|
List<Child> songs = directory.getChildren().stream().filter(child -> !child.isDir()).collect(Collectors.toList());
|
||||||
|
DownloadUtil.getDownloadTracker(requireContext()).download(
|
||||||
|
MappingUtil.mapDownloads(songs),
|
||||||
|
songs.stream().map(Download::new).collect(Collectors.toList())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private void initAppBar() {
|
private void initAppBar() {
|
||||||
activity.setSupportActionBar(bind.toolbar);
|
activity.setSupportActionBar(bind.toolbar);
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import android.os.Bundle;
|
|||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.PopupMenu;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
@@ -19,15 +20,19 @@ import androidx.recyclerview.widget.LinearLayoutManager;
|
|||||||
import com.cappielloantonio.tempo.R;
|
import com.cappielloantonio.tempo.R;
|
||||||
import com.cappielloantonio.tempo.databinding.FragmentDownloadBinding;
|
import com.cappielloantonio.tempo.databinding.FragmentDownloadBinding;
|
||||||
import com.cappielloantonio.tempo.interfaces.ClickCallback;
|
import com.cappielloantonio.tempo.interfaces.ClickCallback;
|
||||||
|
import com.cappielloantonio.tempo.model.DownloadStack;
|
||||||
import com.cappielloantonio.tempo.service.MediaManager;
|
import com.cappielloantonio.tempo.service.MediaManager;
|
||||||
import com.cappielloantonio.tempo.service.MediaService;
|
import com.cappielloantonio.tempo.service.MediaService;
|
||||||
|
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||||
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
||||||
import com.cappielloantonio.tempo.ui.adapter.DownloadHorizontalAdapter;
|
import com.cappielloantonio.tempo.ui.adapter.DownloadHorizontalAdapter;
|
||||||
import com.cappielloantonio.tempo.util.Constants;
|
import com.cappielloantonio.tempo.util.Constants;
|
||||||
|
import com.cappielloantonio.tempo.util.Preferences;
|
||||||
import com.cappielloantonio.tempo.viewmodel.DownloadViewModel;
|
import com.cappielloantonio.tempo.viewmodel.DownloadViewModel;
|
||||||
import com.google.android.material.appbar.MaterialToolbar;
|
import com.google.android.material.appbar.MaterialToolbar;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@@ -59,7 +64,7 @@ public class DownloadFragment extends Fragment implements ClickCallback {
|
|||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
|
||||||
initAppBar();
|
initAppBar();
|
||||||
initDownloadedSongView();
|
initDownloadedView();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -90,11 +95,12 @@ public class DownloadFragment extends Fragment implements ClickCallback {
|
|||||||
Objects.requireNonNull(materialToolbar.getOverflowIcon()).setTint(requireContext().getResources().getColor(R.color.titleTextColor, null));
|
Objects.requireNonNull(materialToolbar.getOverflowIcon()).setTint(requireContext().getResources().getColor(R.color.titleTextColor, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initDownloadedSongView() {
|
private void initDownloadedView() {
|
||||||
bind.downloadedTracksRecyclerView.setHasFixedSize(true);
|
bind.downloadedRecyclerView.setHasFixedSize(true);
|
||||||
|
|
||||||
downloadHorizontalAdapter = new DownloadHorizontalAdapter(this);
|
downloadHorizontalAdapter = new DownloadHorizontalAdapter(this);
|
||||||
bind.downloadedTracksRecyclerView.setAdapter(downloadHorizontalAdapter);
|
bind.downloadedRecyclerView.setAdapter(downloadHorizontalAdapter);
|
||||||
|
|
||||||
downloadViewModel.getDownloadedTracks(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), songs -> {
|
downloadViewModel.getDownloadedTracks(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), songs -> {
|
||||||
if (songs != null) {
|
if (songs != null) {
|
||||||
if (songs.isEmpty()) {
|
if (songs.isEmpty()) {
|
||||||
@@ -102,26 +108,92 @@ public class DownloadFragment extends Fragment implements ClickCallback {
|
|||||||
bind.emptyDownloadLayout.setVisibility(View.VISIBLE);
|
bind.emptyDownloadLayout.setVisibility(View.VISIBLE);
|
||||||
bind.fragmentDownloadNestedScrollView.setVisibility(View.GONE);
|
bind.fragmentDownloadNestedScrollView.setVisibility(View.GONE);
|
||||||
|
|
||||||
bind.downloadDownloadedTracksPlaceholder.placeholder.setVisibility(View.VISIBLE);
|
bind.downloadDownloadedPlaceholder.placeholder.setVisibility(View.VISIBLE);
|
||||||
bind.downloadDownloadedTracksSector.setVisibility(View.GONE);
|
bind.downloadDownloadedSector.setVisibility(View.GONE);
|
||||||
|
|
||||||
|
bind.downloadedGroupByImageView.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (bind != null) {
|
if (bind != null) {
|
||||||
bind.emptyDownloadLayout.setVisibility(View.GONE);
|
bind.emptyDownloadLayout.setVisibility(View.GONE);
|
||||||
bind.fragmentDownloadNestedScrollView.setVisibility(View.VISIBLE);
|
bind.fragmentDownloadNestedScrollView.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
bind.downloadDownloadedTracksPlaceholder.placeholder.setVisibility(View.GONE);
|
bind.downloadDownloadedPlaceholder.placeholder.setVisibility(View.GONE);
|
||||||
bind.downloadDownloadedTracksSector.setVisibility(View.VISIBLE);
|
bind.downloadDownloadedSector.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
bind.downloadedTracksRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
|
bind.downloadedGroupByImageView.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
downloadHorizontalAdapter.setItems(songs);
|
finishDownloadView(songs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bind != null) bind.loadingProgressBar.setVisibility(View.GONE);
|
if (bind != null) bind.loadingProgressBar.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
bind.downloadedGroupByImageView.setOnClickListener(view -> showPopupMenu(view, R.menu.download_popup_menu));
|
||||||
|
bind.downloadedGoBackImageView.setOnClickListener(view -> downloadViewModel.popViewStack());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void finishDownloadView(List<Child> songs) {
|
||||||
|
downloadViewModel.getViewStack().observe(getViewLifecycleOwner(), stack -> {
|
||||||
|
bind.downloadedRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
|
||||||
|
|
||||||
|
DownloadStack lastLevel = stack.get(stack.size() - 1);
|
||||||
|
|
||||||
|
switch (lastLevel.getId()) {
|
||||||
|
case Constants.DOWNLOAD_TYPE_TRACK:
|
||||||
|
downloadHorizontalAdapter.setItems(Constants.DOWNLOAD_TYPE_TRACK, lastLevel.getId(), lastLevel.getView(), songs);
|
||||||
|
break;
|
||||||
|
case Constants.DOWNLOAD_TYPE_ALBUM:
|
||||||
|
downloadHorizontalAdapter.setItems(Constants.DOWNLOAD_TYPE_TRACK, lastLevel.getId(), lastLevel.getView(), songs);
|
||||||
|
break;
|
||||||
|
case Constants.DOWNLOAD_TYPE_ARTIST:
|
||||||
|
downloadHorizontalAdapter.setItems(Constants.DOWNLOAD_TYPE_ALBUM, lastLevel.getId(), lastLevel.getView(), songs);
|
||||||
|
break;
|
||||||
|
case Constants.DOWNLOAD_TYPE_GENRE:
|
||||||
|
downloadHorizontalAdapter.setItems(Constants.DOWNLOAD_TYPE_TRACK, lastLevel.getId(), lastLevel.getView(), songs);
|
||||||
|
break;
|
||||||
|
case Constants.DOWNLOAD_TYPE_YEAR:
|
||||||
|
downloadHorizontalAdapter.setItems(Constants.DOWNLOAD_TYPE_TRACK, lastLevel.getId(), lastLevel.getView(), songs);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
bind.downloadedGoBackImageView.setVisibility(stack.size() > 1 ? View.VISIBLE : View.GONE);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showPopupMenu(View view, int menuResource) {
|
||||||
|
PopupMenu popup = new PopupMenu(requireContext(), view);
|
||||||
|
popup.getMenuInflater().inflate(menuResource, popup.getMenu());
|
||||||
|
|
||||||
|
popup.setOnMenuItemClickListener(menuItem -> {
|
||||||
|
if (menuItem.getItemId() == R.id.menu_download_group_by_track) {
|
||||||
|
downloadViewModel.initViewStack(new DownloadStack(Constants.DOWNLOAD_TYPE_TRACK, null));
|
||||||
|
Preferences.setDefaultDownloadViewType(Constants.DOWNLOAD_TYPE_TRACK);
|
||||||
|
return true;
|
||||||
|
} else if (menuItem.getItemId() == R.id.menu_download_group_by_album) {
|
||||||
|
downloadViewModel.initViewStack(new DownloadStack(Constants.DOWNLOAD_TYPE_ALBUM, null));
|
||||||
|
Preferences.setDefaultDownloadViewType(Constants.DOWNLOAD_TYPE_ALBUM);
|
||||||
|
return true;
|
||||||
|
} else if (menuItem.getItemId() == R.id.menu_download_group_by_artist) {
|
||||||
|
downloadViewModel.initViewStack(new DownloadStack(Constants.DOWNLOAD_TYPE_ARTIST, null));
|
||||||
|
Preferences.setDefaultDownloadViewType(Constants.DOWNLOAD_TYPE_ARTIST);
|
||||||
|
return true;
|
||||||
|
} else if (menuItem.getItemId() == R.id.menu_download_group_by_genre) {
|
||||||
|
downloadViewModel.initViewStack(new DownloadStack(Constants.DOWNLOAD_TYPE_GENRE, null));
|
||||||
|
Preferences.setDefaultDownloadViewType(Constants.DOWNLOAD_TYPE_GENRE);
|
||||||
|
return true;
|
||||||
|
} else if (menuItem.getItemId() == R.id.menu_download_group_by_year) {
|
||||||
|
downloadViewModel.initViewStack(new DownloadStack(Constants.DOWNLOAD_TYPE_YEAR, null));
|
||||||
|
Preferences.setDefaultDownloadViewType(Constants.DOWNLOAD_TYPE_YEAR);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
popup.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeMediaBrowser() {
|
private void initializeMediaBrowser() {
|
||||||
@@ -132,6 +204,26 @@ public class DownloadFragment extends Fragment implements ClickCallback {
|
|||||||
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
|
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onYearClick(Bundle bundle) {
|
||||||
|
downloadViewModel.pushViewStack(new DownloadStack(Constants.DOWNLOAD_TYPE_YEAR, bundle.getString(Constants.DOWNLOAD_TYPE_YEAR)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onGenreClick(Bundle bundle) {
|
||||||
|
downloadViewModel.pushViewStack(new DownloadStack(Constants.DOWNLOAD_TYPE_GENRE, bundle.getString(Constants.DOWNLOAD_TYPE_GENRE)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onArtistClick(Bundle bundle) {
|
||||||
|
downloadViewModel.pushViewStack(new DownloadStack(Constants.DOWNLOAD_TYPE_ARTIST, bundle.getString(Constants.DOWNLOAD_TYPE_ARTIST)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAlbumClick(Bundle bundle) {
|
||||||
|
downloadViewModel.pushViewStack(new DownloadStack(Constants.DOWNLOAD_TYPE_ALBUM, bundle.getString(Constants.DOWNLOAD_TYPE_ALBUM)));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onMediaClick(Bundle bundle) {
|
public void onMediaClick(Bundle bundle) {
|
||||||
MediaManager.startQueue(mediaBrowserListenableFuture, bundle.getParcelableArrayList(Constants.TRACKS_OBJECT), bundle.getInt(Constants.ITEM_POSITION));
|
MediaManager.startQueue(mediaBrowserListenableFuture, bundle.getParcelableArrayList(Constants.TRACKS_OBJECT), bundle.getInt(Constants.ITEM_POSITION));
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import android.os.Bundle;
|
|||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
@@ -393,7 +392,7 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
|||||||
private void initStarredTracksView() {
|
private void initStarredTracksView() {
|
||||||
bind.starredTracksRecyclerView.setHasFixedSize(true);
|
bind.starredTracksRecyclerView.setHasFixedSize(true);
|
||||||
|
|
||||||
starredSongAdapter = new SongHorizontalAdapter(this, true);
|
starredSongAdapter = new SongHorizontalAdapter(this, true, false);
|
||||||
bind.starredTracksRecyclerView.setAdapter(starredSongAdapter);
|
bind.starredTracksRecyclerView.setAdapter(starredSongAdapter);
|
||||||
homeViewModel.getStarredTracks(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), songs -> {
|
homeViewModel.getStarredTracks(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), songs -> {
|
||||||
if (songs == null) {
|
if (songs == null) {
|
||||||
@@ -619,25 +618,6 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void reorder() {
|
|
||||||
if (bind != null) {
|
|
||||||
// bind.homeLinearLayoutContainer.removeAllViews();
|
|
||||||
// bind.homeLinearLayoutContainer.addView(bind.homeDiscoverSector);
|
|
||||||
|
|
||||||
// bind.homeLinearLayoutContainer.addView(bind.homeSimilarTracksSector);
|
|
||||||
// bind.homeLinearLayoutContainer.addView(bind.homeRadioArtistSector);
|
|
||||||
// bind.homeLinearLayoutContainer.addView(bind.homeGridTracksSector);
|
|
||||||
// bind.homeLinearLayoutContainer.addView(bind.starredTracksSector);
|
|
||||||
// bind.homeLinearLayoutContainer.addView(bind.starredAlbumsSector);
|
|
||||||
// bind.homeLinearLayoutContainer.addView(bind.starredArtistsSector);
|
|
||||||
|
|
||||||
// bind.homeLinearLayoutContainer.addView(bind.homeRecentlyAddedAlbumsSector);
|
|
||||||
// bind.homeLinearLayoutContainer.addView(bind.homeFlashbackSector);
|
|
||||||
// bind.homeLinearLayoutContainer.addView(bind.homeMostPlayedAlbumsSector);
|
|
||||||
// bind.homeLinearLayoutContainer.addView(bind.homeRecentlyPlayedAlbumsSector);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initializeMediaBrowser() {
|
private void initializeMediaBrowser() {
|
||||||
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
|
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,17 +85,22 @@ public class IndexFragment extends Fragment implements ClickCallback {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void initDirectoryListView() {
|
private void initDirectoryListView() {
|
||||||
|
MusicFolder musicFolder = getArguments().getParcelable(Constants.MUSIC_FOLDER_OBJECT);
|
||||||
|
|
||||||
bind.indexRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
|
bind.indexRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
|
||||||
bind.indexRecyclerView.setHasFixedSize(true);
|
bind.indexRecyclerView.setHasFixedSize(true);
|
||||||
|
|
||||||
musicIndexAdapter = new MusicIndexAdapter(this);
|
musicIndexAdapter = new MusicIndexAdapter(this);
|
||||||
bind.indexRecyclerView.setAdapter(musicIndexAdapter);
|
bind.indexRecyclerView.setAdapter(musicIndexAdapter);
|
||||||
|
|
||||||
indexViewModel.getIndexes().observe(getViewLifecycleOwner(), indexes -> {
|
indexViewModel.getIndexes(musicFolder != null ? musicFolder.getId() : null).observe(getViewLifecycleOwner(), indexes -> {
|
||||||
if (indexes != null) {
|
if (indexes != null) {
|
||||||
musicIndexAdapter.setItems(IndexUtil.getArtist(indexes));
|
musicIndexAdapter.setItems(IndexUtil.getArtist(indexes));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
bind.fastScrollbar.setRecyclerView(bind.indexRecyclerView);
|
||||||
|
bind.fastScrollbar.setViewsToUse(R.layout.layout_fast_scrollbar, R.id.fastscroller_bubble, R.id.fastscroller_handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.cappielloantonio.tempo.ui.fragment;
|
package com.cappielloantonio.tempo.ui.fragment;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
@@ -18,6 +19,7 @@ import com.cappielloantonio.tempo.R;
|
|||||||
import com.cappielloantonio.tempo.databinding.FragmentLibraryBinding;
|
import com.cappielloantonio.tempo.databinding.FragmentLibraryBinding;
|
||||||
import com.cappielloantonio.tempo.helper.recyclerview.CustomLinearSnapHelper;
|
import com.cappielloantonio.tempo.helper.recyclerview.CustomLinearSnapHelper;
|
||||||
import com.cappielloantonio.tempo.interfaces.ClickCallback;
|
import com.cappielloantonio.tempo.interfaces.ClickCallback;
|
||||||
|
import com.cappielloantonio.tempo.interfaces.PlaylistCallback;
|
||||||
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
||||||
import com.cappielloantonio.tempo.ui.adapter.AlbumAdapter;
|
import com.cappielloantonio.tempo.ui.adapter.AlbumAdapter;
|
||||||
import com.cappielloantonio.tempo.ui.adapter.ArtistAdapter;
|
import com.cappielloantonio.tempo.ui.adapter.ArtistAdapter;
|
||||||
@@ -71,7 +73,7 @@ public class LibraryFragment extends Fragment implements ClickCallback {
|
|||||||
initAlbumView();
|
initAlbumView();
|
||||||
initArtistView();
|
initArtistView();
|
||||||
initGenreView();
|
initGenreView();
|
||||||
initPlaylistSlideView();
|
initPlaylistView();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -80,6 +82,12 @@ public class LibraryFragment extends Fragment implements ClickCallback {
|
|||||||
activity.setBottomNavigationBarVisibility(true);
|
activity.setBottomNavigationBarVisibility(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
refreshPlaylistView();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
@@ -222,7 +230,7 @@ public class LibraryFragment extends Fragment implements ClickCallback {
|
|||||||
genreSnapHelper.attachToRecyclerView(bind.genreRecyclerView);
|
genreSnapHelper.attachToRecyclerView(bind.genreRecyclerView);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initPlaylistSlideView() {
|
private void initPlaylistView() {
|
||||||
bind.playlistRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
|
bind.playlistRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
|
||||||
bind.playlistRecyclerView.setHasFixedSize(true);
|
bind.playlistRecyclerView.setHasFixedSize(true);
|
||||||
|
|
||||||
@@ -244,6 +252,12 @@ public class LibraryFragment extends Fragment implements ClickCallback {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void refreshPlaylistView() {
|
||||||
|
final Handler handler = new Handler();
|
||||||
|
final Runnable runnable = () -> libraryViewModel.refreshPlaylistSample(getViewLifecycleOwner());
|
||||||
|
handler.postDelayed(runnable, 100);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAlbumClick(Bundle bundle) {
|
public void onAlbumClick(Bundle bundle) {
|
||||||
Navigation.findNavController(requireView()).navigate(R.id.albumPageFragment, bundle);
|
Navigation.findNavController(requireView()).navigate(R.id.albumPageFragment, bundle);
|
||||||
@@ -276,7 +290,13 @@ public class LibraryFragment extends Fragment implements ClickCallback {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPlaylistLongClick(Bundle bundle) {
|
public void onPlaylistLongClick(Bundle bundle) {
|
||||||
PlaylistEditorDialog dialog = new PlaylistEditorDialog();
|
PlaylistEditorDialog dialog = new PlaylistEditorDialog(new PlaylistCallback() {
|
||||||
|
@Override
|
||||||
|
public void onDismiss() {
|
||||||
|
refreshPlaylistView();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
dialog.setArguments(bundle);
|
dialog.setArguments(bundle);
|
||||||
dialog.show(activity.getSupportFragmentManager(), null);
|
dialog.show(activity.getSupportFragmentManager(), null);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ public class PlayerBottomSheetFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void customizeBottomSheetBackground() {
|
private void customizeBottomSheetBackground() {
|
||||||
bind.playerHeaderLayout.getRoot().setBackgroundColor(SurfaceColors.getColorForElevation(requireContext(), 2));
|
bind.playerHeaderLayout.getRoot().setBackgroundColor(SurfaceColors.getColorForElevation(requireContext(), 8));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void customizeBottomSheetAction() {
|
private void customizeBottomSheetAction() {
|
||||||
@@ -240,6 +240,10 @@ public class PlayerBottomSheetFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void goToQueuePage() {
|
||||||
|
bind.playerBodyLayout.playerBodyBottomSheetViewPager.setCurrentItem(1, true);
|
||||||
|
}
|
||||||
|
|
||||||
private void defineProgressBarHandler(MediaBrowser mediaBrowser) {
|
private void defineProgressBarHandler(MediaBrowser mediaBrowser) {
|
||||||
progressBarHandler = new Handler();
|
progressBarHandler = new Handler();
|
||||||
progressBarRunnable = () -> {
|
progressBarRunnable = () -> {
|
||||||
|
|||||||
@@ -6,11 +6,12 @@ import android.view.LayoutInflater;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageButton;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.ToggleButton;
|
import android.widget.ToggleButton;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
import androidx.media3.common.MediaMetadata;
|
import androidx.media3.common.MediaMetadata;
|
||||||
@@ -28,12 +29,14 @@ import com.cappielloantonio.tempo.databinding.InnerFragmentPlayerControllerBindi
|
|||||||
import com.cappielloantonio.tempo.service.MediaService;
|
import com.cappielloantonio.tempo.service.MediaService;
|
||||||
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
||||||
import com.cappielloantonio.tempo.ui.dialog.RatingDialog;
|
import com.cappielloantonio.tempo.ui.dialog.RatingDialog;
|
||||||
|
import com.cappielloantonio.tempo.ui.dialog.TrackInfoDialog;
|
||||||
import com.cappielloantonio.tempo.ui.fragment.pager.PlayerControllerHorizontalPager;
|
import com.cappielloantonio.tempo.ui.fragment.pager.PlayerControllerHorizontalPager;
|
||||||
import com.cappielloantonio.tempo.util.Constants;
|
import com.cappielloantonio.tempo.util.Constants;
|
||||||
import com.cappielloantonio.tempo.util.MusicUtil;
|
import com.cappielloantonio.tempo.util.MusicUtil;
|
||||||
import com.cappielloantonio.tempo.util.Preferences;
|
import com.cappielloantonio.tempo.util.Preferences;
|
||||||
import com.cappielloantonio.tempo.viewmodel.PlayerBottomSheetViewModel;
|
import com.cappielloantonio.tempo.viewmodel.PlayerBottomSheetViewModel;
|
||||||
import com.google.android.material.chip.Chip;
|
import com.google.android.material.chip.Chip;
|
||||||
|
import com.google.android.material.elevation.SurfaceColors;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
import com.google.common.util.concurrent.MoreExecutors;
|
import com.google.common.util.concurrent.MoreExecutors;
|
||||||
|
|
||||||
@@ -50,9 +53,8 @@ public class PlayerControllerFragment extends Fragment {
|
|||||||
private ToggleButton skipSilenceToggleButton;
|
private ToggleButton skipSilenceToggleButton;
|
||||||
private Chip playerMediaExtension;
|
private Chip playerMediaExtension;
|
||||||
private TextView playerMediaBitrate;
|
private TextView playerMediaBitrate;
|
||||||
private ImageView playerMediaTranscodingIcon;
|
private ConstraintLayout playerQuickActionView;
|
||||||
private Chip playerMediaTranscodedExtension;
|
private ImageButton playerTrackInfo;
|
||||||
private TextView playerMediaTranscodedBitrate;
|
|
||||||
|
|
||||||
private MainActivity activity;
|
private MainActivity activity;
|
||||||
private PlayerBottomSheetViewModel playerBottomSheetViewModel;
|
private PlayerBottomSheetViewModel playerBottomSheetViewModel;
|
||||||
@@ -68,6 +70,7 @@ public class PlayerControllerFragment extends Fragment {
|
|||||||
playerBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(PlayerBottomSheetViewModel.class);
|
playerBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(PlayerBottomSheetViewModel.class);
|
||||||
|
|
||||||
init();
|
init();
|
||||||
|
initQuickActionView();
|
||||||
initCoverLyricsSlideView();
|
initCoverLyricsSlideView();
|
||||||
initMediaListenable();
|
initMediaListenable();
|
||||||
initArtistLabelButton();
|
initArtistLabelButton();
|
||||||
@@ -103,9 +106,19 @@ public class PlayerControllerFragment extends Fragment {
|
|||||||
skipSilenceToggleButton = bind.getRoot().findViewById(R.id.player_skip_silence_toggle_button);
|
skipSilenceToggleButton = bind.getRoot().findViewById(R.id.player_skip_silence_toggle_button);
|
||||||
playerMediaExtension = bind.getRoot().findViewById(R.id.player_media_extension);
|
playerMediaExtension = bind.getRoot().findViewById(R.id.player_media_extension);
|
||||||
playerMediaBitrate = bind.getRoot().findViewById(R.id.player_media_bitrate);
|
playerMediaBitrate = bind.getRoot().findViewById(R.id.player_media_bitrate);
|
||||||
playerMediaTranscodingIcon = bind.getRoot().findViewById(R.id.player_media_transcoding_audio);
|
playerQuickActionView = bind.getRoot().findViewById(R.id.player_quick_action_view);
|
||||||
playerMediaTranscodedExtension = bind.getRoot().findViewById(R.id.player_media_transcoded_extension);
|
playerTrackInfo = bind.getRoot().findViewById(R.id.player_info_track);
|
||||||
playerMediaTranscodedBitrate = bind.getRoot().findViewById(R.id.player_media_transcoded_bitrate);
|
}
|
||||||
|
|
||||||
|
private void initQuickActionView() {
|
||||||
|
playerQuickActionView.setBackgroundColor(SurfaceColors.getColorForElevation(requireContext(), 8));
|
||||||
|
|
||||||
|
playerQuickActionView.setOnClickListener(view -> {
|
||||||
|
PlayerBottomSheetFragment playerBottomSheetFragment = (PlayerBottomSheetFragment) requireActivity().getSupportFragmentManager().findFragmentByTag("PlayerBottomSheet");
|
||||||
|
if (playerBottomSheetFragment != null) {
|
||||||
|
playerBottomSheetFragment.goToQueuePage();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeBrowser() {
|
private void initializeBrowser() {
|
||||||
@@ -156,9 +169,7 @@ public class PlayerControllerFragment extends Fragment {
|
|||||||
private void setMediaInfo(MediaMetadata mediaMetadata) {
|
private void setMediaInfo(MediaMetadata mediaMetadata) {
|
||||||
if (mediaMetadata.extras != null) {
|
if (mediaMetadata.extras != null) {
|
||||||
String extension = mediaMetadata.extras.getString("suffix", "Unknown format");
|
String extension = mediaMetadata.extras.getString("suffix", "Unknown format");
|
||||||
String bitrate = mediaMetadata.extras.getInt("bitrate", 0) != 0
|
String bitrate = mediaMetadata.extras.getInt("bitrate", 0) != 0 ? mediaMetadata.extras.getInt("bitrate", 0) + "kbps" : "Original";
|
||||||
? mediaMetadata.extras.getInt("bitrate", 0) + "kbps"
|
|
||||||
: "Original";
|
|
||||||
|
|
||||||
playerMediaExtension.setText(extension);
|
playerMediaExtension.setText(extension);
|
||||||
|
|
||||||
@@ -166,34 +177,14 @@ public class PlayerControllerFragment extends Fragment {
|
|||||||
playerMediaBitrate.setVisibility(View.GONE);
|
playerMediaBitrate.setVisibility(View.GONE);
|
||||||
} else {
|
} else {
|
||||||
playerMediaBitrate.setVisibility(View.VISIBLE);
|
playerMediaBitrate.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
playerMediaBitrate.setText(bitrate);
|
playerMediaBitrate.setText(bitrate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String transcodingExtension = MusicUtil.getTranscodingFormatPreference();
|
playerTrackInfo.setOnClickListener(view -> {
|
||||||
String transcodingBitrate = Integer.parseInt(MusicUtil.getBitratePreference()) != 0
|
TrackInfoDialog dialog = new TrackInfoDialog(mediaMetadata);
|
||||||
? Integer.parseInt(MusicUtil.getBitratePreference()) + "kbps"
|
dialog.show(activity.getSupportFragmentManager(), null);
|
||||||
: "Original";
|
});
|
||||||
|
|
||||||
if (transcodingExtension.equals("raw") && transcodingBitrate.equals("Original")) {
|
|
||||||
playerMediaTranscodingIcon.setVisibility(View.GONE);
|
|
||||||
playerMediaTranscodedBitrate.setVisibility(View.GONE);
|
|
||||||
playerMediaTranscodedExtension.setVisibility(View.GONE);
|
|
||||||
} else {
|
|
||||||
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)) {
|
|
||||||
playerMediaTranscodingIcon.setVisibility(View.GONE);
|
|
||||||
playerMediaTranscodedBitrate.setVisibility(View.GONE);
|
|
||||||
playerMediaTranscodedExtension.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setMediaControllerUI(MediaBrowser mediaBrowser) {
|
private void setMediaControllerUI(MediaBrowser mediaBrowser) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.cappielloantonio.tempo.ui.fragment;
|
|||||||
|
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
import android.transition.Fade;
|
import android.transition.Fade;
|
||||||
import android.transition.Transition;
|
import android.transition.Transition;
|
||||||
import android.transition.TransitionManager;
|
import android.transition.TransitionManager;
|
||||||
@@ -39,6 +40,8 @@ public class PlayerCoverFragment extends Fragment {
|
|||||||
private InnerFragmentPlayerCoverBinding bind;
|
private InnerFragmentPlayerCoverBinding bind;
|
||||||
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
||||||
|
|
||||||
|
private final Handler handler = new Handler();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
bind = InnerFragmentPlayerCoverBinding.inflate(inflater, container, false);
|
bind = InnerFragmentPlayerCoverBinding.inflate(inflater, container, false);
|
||||||
@@ -72,9 +75,19 @@ public class PlayerCoverFragment extends Fragment {
|
|||||||
bind = null;
|
bind = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void initTapButtonHideTransition() {
|
||||||
|
bind.nowPlayingTapButton.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
handler.removeCallbacksAndMessages(null);
|
||||||
|
|
||||||
|
final Runnable runnable = () -> bind.nowPlayingTapButton.setVisibility(View.GONE);
|
||||||
|
handler.postDelayed(runnable, 10000);
|
||||||
|
}
|
||||||
|
|
||||||
private void initOverlay() {
|
private void initOverlay() {
|
||||||
bind.nowPlayingSongCoverImageView.setOnClickListener(view -> toggleOverlayVisibility(true));
|
bind.nowPlayingSongCoverImageView.setOnClickListener(view -> toggleOverlayVisibility(true));
|
||||||
bind.nowPlayingSongCoverButtonGroup.setOnClickListener(view -> toggleOverlayVisibility(false));
|
bind.nowPlayingSongCoverButtonGroup.setOnClickListener(view -> toggleOverlayVisibility(false));
|
||||||
|
bind.nowPlayingTapButton.setOnClickListener(view -> toggleOverlayVisibility(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void toggleOverlayVisibility(boolean isVisible) {
|
private void toggleOverlayVisibility(boolean isVisible) {
|
||||||
@@ -84,9 +97,12 @@ public class PlayerCoverFragment extends Fragment {
|
|||||||
|
|
||||||
TransitionManager.beginDelayedTransition(bind.getRoot(), transition);
|
TransitionManager.beginDelayedTransition(bind.getRoot(), transition);
|
||||||
bind.nowPlayingSongCoverButtonGroup.setVisibility(isVisible ? View.VISIBLE : View.GONE);
|
bind.nowPlayingSongCoverButtonGroup.setVisibility(isVisible ? View.VISIBLE : View.GONE);
|
||||||
|
bind.nowPlayingTapButton.setVisibility(isVisible ? View.GONE : View.VISIBLE);
|
||||||
|
|
||||||
bind.innerButtonBottomRight.setVisibility(Preferences.isSyncronizationEnabled() ? View.VISIBLE : View.GONE);
|
bind.innerButtonBottomRight.setVisibility(Preferences.isSyncronizationEnabled() ? View.VISIBLE : View.GONE);
|
||||||
bind.innerButtonBottomRightAlternative.setVisibility(Preferences.isSyncronizationEnabled() ? View.GONE : View.VISIBLE);
|
bind.innerButtonBottomRightAlternative.setVisibility(Preferences.isSyncronizationEnabled() ? View.GONE : View.VISIBLE);
|
||||||
|
|
||||||
|
if (!isVisible) initTapButtonHideTransition();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initInnerButton() {
|
private void initInnerButton() {
|
||||||
|
|||||||
@@ -25,12 +25,16 @@ import com.cappielloantonio.tempo.ui.adapter.PlayerSongQueueAdapter;
|
|||||||
import com.cappielloantonio.tempo.util.Constants;
|
import com.cappielloantonio.tempo.util.Constants;
|
||||||
import com.cappielloantonio.tempo.viewmodel.PlayerBottomSheetViewModel;
|
import com.cappielloantonio.tempo.viewmodel.PlayerBottomSheetViewModel;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
import com.google.common.util.concurrent.MoreExecutors;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
public class PlayerQueueFragment extends Fragment implements ClickCallback {
|
public class PlayerQueueFragment extends Fragment implements ClickCallback {
|
||||||
|
private static final String TAG = "PlayerQueueFragment";
|
||||||
|
|
||||||
private InnerFragmentPlayerQueueBinding bind;
|
private InnerFragmentPlayerQueueBinding bind;
|
||||||
|
|
||||||
private PlayerBottomSheetViewModel playerBottomSheetViewModel;
|
private PlayerBottomSheetViewModel playerBottomSheetViewModel;
|
||||||
@@ -54,6 +58,7 @@ public class PlayerQueueFragment extends Fragment implements ClickCallback {
|
|||||||
public void onStart() {
|
public void onStart() {
|
||||||
super.onStart();
|
super.onStart();
|
||||||
initializeBrowser();
|
initializeBrowser();
|
||||||
|
bindMediaController();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -83,6 +88,17 @@ public class PlayerQueueFragment extends Fragment implements ClickCallback {
|
|||||||
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
|
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void bindMediaController() {
|
||||||
|
mediaBrowserListenableFuture.addListener(() -> {
|
||||||
|
try {
|
||||||
|
MediaBrowser mediaBrowser = mediaBrowserListenableFuture.get();
|
||||||
|
initShuffleButton(mediaBrowser);
|
||||||
|
} catch (Exception exception) {
|
||||||
|
exception.printStackTrace();
|
||||||
|
}
|
||||||
|
}, MoreExecutors.directExecutor());
|
||||||
|
}
|
||||||
|
|
||||||
private void setMediaBrowserListenableFuture() {
|
private void setMediaBrowserListenableFuture() {
|
||||||
playerSongQueueAdapter.setMediaBrowserListenableFuture(mediaBrowserListenableFuture);
|
playerSongQueueAdapter.setMediaBrowserListenableFuture(mediaBrowserListenableFuture);
|
||||||
}
|
}
|
||||||
@@ -151,6 +167,36 @@ public class PlayerQueueFragment extends Fragment implements ClickCallback {
|
|||||||
}).attachToRecyclerView(bind.playerQueueRecyclerView);
|
}).attachToRecyclerView(bind.playerQueueRecyclerView);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void initShuffleButton(MediaBrowser mediaBrowser) {
|
||||||
|
bind.playerShuffleQueueFab.setOnClickListener(view -> {
|
||||||
|
int startPosition = mediaBrowser.getCurrentMediaItemIndex() + 1;
|
||||||
|
int endPosition = playerSongQueueAdapter.getItems().size() - 1;
|
||||||
|
|
||||||
|
if (startPosition < endPosition) {
|
||||||
|
ArrayList<Integer> pool = new ArrayList<>();
|
||||||
|
|
||||||
|
for (int i = startPosition; i <= endPosition; i++) {
|
||||||
|
pool.add(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (pool.size() >= 2) {
|
||||||
|
int fromPosition = (int) (Math.random() * (pool.size()));
|
||||||
|
int positionA = pool.get(fromPosition);
|
||||||
|
pool.remove(fromPosition);
|
||||||
|
|
||||||
|
int toPosition = (int) (Math.random() * (pool.size()));
|
||||||
|
int positionB = pool.get(toPosition);
|
||||||
|
pool.remove(toPosition);
|
||||||
|
|
||||||
|
Collections.swap(playerSongQueueAdapter.getItems(), positionA, positionB);
|
||||||
|
bind.playerQueueRecyclerView.getAdapter().notifyItemMoved(positionA, positionB);
|
||||||
|
}
|
||||||
|
|
||||||
|
MediaManager.shuffle(mediaBrowserListenableFuture, playerSongQueueAdapter.getItems(), startPosition, endPosition);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void updateNowPlayingItem() {
|
private void updateNowPlayingItem() {
|
||||||
playerSongQueueAdapter.notifyDataSetChanged();
|
playerSongQueueAdapter.notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -178,7 +178,7 @@ public class PlaylistCatalogueFragment extends Fragment implements ClickCallback
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPlaylistLongClick(Bundle bundle) {
|
public void onPlaylistLongClick(Bundle bundle) {
|
||||||
PlaylistEditorDialog dialog = new PlaylistEditorDialog();
|
PlaylistEditorDialog dialog = new PlaylistEditorDialog(null);
|
||||||
dialog.setArguments(bundle);
|
dialog.setArguments(bundle);
|
||||||
dialog.show(activity.getSupportFragmentManager(), null);
|
dialog.show(activity.getSupportFragmentManager(), null);
|
||||||
hideKeyboard(requireView());
|
hideKeyboard(requireView());
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import androidx.media3.session.SessionToken;
|
|||||||
import androidx.navigation.Navigation;
|
import androidx.navigation.Navigation;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
|
||||||
|
import com.bumptech.glide.load.resource.bitmap.GranularRoundedCorners;
|
||||||
import com.cappielloantonio.tempo.R;
|
import com.cappielloantonio.tempo.R;
|
||||||
import com.cappielloantonio.tempo.databinding.FragmentPlaylistPageBinding;
|
import com.cappielloantonio.tempo.databinding.FragmentPlaylistPageBinding;
|
||||||
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
||||||
@@ -146,13 +147,13 @@ public class PlaylistPageFragment extends Fragment implements ClickCallback {
|
|||||||
playlistPageViewModel.getPlaylistSongLiveList().observe(getViewLifecycleOwner(), songs -> {
|
playlistPageViewModel.getPlaylistSongLiveList().observe(getViewLifecycleOwner(), songs -> {
|
||||||
if (bind != null) {
|
if (bind != null) {
|
||||||
bind.playlistPagePlayButton.setOnClickListener(v -> {
|
bind.playlistPagePlayButton.setOnClickListener(v -> {
|
||||||
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
|
MediaManager.startQueue(mediaBrowserListenableFuture, songs.subList(0, Math.min(100, songs.size())), 0);
|
||||||
activity.setBottomSheetInPeek(true);
|
activity.setBottomSheetInPeek(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
bind.playlistPageShuffleButton.setOnClickListener(v -> {
|
bind.playlistPageShuffleButton.setOnClickListener(v -> {
|
||||||
Collections.shuffle(songs);
|
Collections.shuffle(songs);
|
||||||
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
|
MediaManager.startQueue(mediaBrowserListenableFuture, songs.subList(0, Math.min(100, songs.size())), 0);
|
||||||
activity.setBottomSheetInPeek(true);
|
activity.setBottomSheetInPeek(true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -160,17 +161,46 @@ public class PlaylistPageFragment extends Fragment implements ClickCallback {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void initBackCover() {
|
private void initBackCover() {
|
||||||
|
playlistPageViewModel.getPlaylistSongLiveList().observe(requireActivity(), songs -> {
|
||||||
|
if (bind != null && songs != null && !songs.isEmpty()) {
|
||||||
|
Collections.shuffle(songs);
|
||||||
|
|
||||||
|
// Pic top-left
|
||||||
CustomGlideRequest.Builder
|
CustomGlideRequest.Builder
|
||||||
.from(requireContext(), playlistPageViewModel.getPlaylist().getCoverArtId())
|
.from(requireContext(), songs.size() > 0 ? songs.get(0).getCoverArtId() : playlistPageViewModel.getPlaylist().getCoverArtId())
|
||||||
.build()
|
.build()
|
||||||
.into(bind.playlistCoverImageView);
|
.transform(new GranularRoundedCorners(CustomGlideRequest.CORNER_RADIUS, 0, 0, 0))
|
||||||
|
.into(bind.playlistCoverImageViewTopLeft);
|
||||||
|
|
||||||
|
// Pic top-right
|
||||||
|
CustomGlideRequest.Builder
|
||||||
|
.from(requireContext(), songs.size() > 1 ? songs.get(1).getCoverArtId() : playlistPageViewModel.getPlaylist().getCoverArtId())
|
||||||
|
.build()
|
||||||
|
.transform(new GranularRoundedCorners(0, CustomGlideRequest.CORNER_RADIUS, 0, 0))
|
||||||
|
.into(bind.playlistCoverImageViewTopRight);
|
||||||
|
|
||||||
|
// Pic bottom-left
|
||||||
|
CustomGlideRequest.Builder
|
||||||
|
.from(requireContext(), songs.size() > 2 ? songs.get(2).getCoverArtId() : playlistPageViewModel.getPlaylist().getCoverArtId())
|
||||||
|
.build()
|
||||||
|
.transform(new GranularRoundedCorners(0, 0, 0, CustomGlideRequest.CORNER_RADIUS))
|
||||||
|
.into(bind.playlistCoverImageViewBottomLeft);
|
||||||
|
|
||||||
|
// Pic bottom-right
|
||||||
|
CustomGlideRequest.Builder
|
||||||
|
.from(requireContext(), songs.size() > 3 ? songs.get(3).getCoverArtId() : playlistPageViewModel.getPlaylist().getCoverArtId())
|
||||||
|
.build()
|
||||||
|
.transform(new GranularRoundedCorners(0, 0, CustomGlideRequest.CORNER_RADIUS, 0))
|
||||||
|
.into(bind.playlistCoverImageViewBottomRight);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initSongsView() {
|
private void initSongsView() {
|
||||||
bind.songRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
|
bind.songRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
|
||||||
bind.songRecyclerView.setHasFixedSize(true);
|
bind.songRecyclerView.setHasFixedSize(true);
|
||||||
|
|
||||||
songHorizontalAdapter = new SongHorizontalAdapter(this, true);
|
songHorizontalAdapter = new SongHorizontalAdapter(this, true, false);
|
||||||
bind.songRecyclerView.setAdapter(songHorizontalAdapter);
|
bind.songRecyclerView.setAdapter(songHorizontalAdapter);
|
||||||
|
|
||||||
playlistPageViewModel.getPlaylistSongLiveList().observe(getViewLifecycleOwner(), songs -> songHorizontalAdapter.setItems(songs));
|
playlistPageViewModel.getPlaylistSongLiveList().observe(getViewLifecycleOwner(), songs -> songHorizontalAdapter.setItems(songs));
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import android.os.Bundle;
|
|||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.PopupMenu;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
@@ -17,7 +18,6 @@ import androidx.recyclerview.widget.LinearLayoutManager;
|
|||||||
|
|
||||||
import com.cappielloantonio.tempo.R;
|
import com.cappielloantonio.tempo.R;
|
||||||
import com.cappielloantonio.tempo.databinding.FragmentPodcastChannelPageBinding;
|
import com.cappielloantonio.tempo.databinding.FragmentPodcastChannelPageBinding;
|
||||||
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
|
||||||
import com.cappielloantonio.tempo.interfaces.ClickCallback;
|
import com.cappielloantonio.tempo.interfaces.ClickCallback;
|
||||||
import com.cappielloantonio.tempo.service.MediaManager;
|
import com.cappielloantonio.tempo.service.MediaManager;
|
||||||
import com.cappielloantonio.tempo.service.MediaService;
|
import com.cappielloantonio.tempo.service.MediaService;
|
||||||
@@ -28,11 +28,10 @@ import com.cappielloantonio.tempo.util.Constants;
|
|||||||
import com.cappielloantonio.tempo.util.MusicUtil;
|
import com.cappielloantonio.tempo.util.MusicUtil;
|
||||||
import com.cappielloantonio.tempo.util.UIUtil;
|
import com.cappielloantonio.tempo.util.UIUtil;
|
||||||
import com.cappielloantonio.tempo.viewmodel.PodcastChannelPageViewModel;
|
import com.cappielloantonio.tempo.viewmodel.PodcastChannelPageViewModel;
|
||||||
|
import com.google.android.material.snackbar.Snackbar;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
public class PodcastChannelPageFragment extends Fragment implements ClickCallback {
|
public class PodcastChannelPageFragment extends Fragment implements ClickCallback {
|
||||||
@@ -84,28 +83,26 @@ public class PodcastChannelPageFragment extends Fragment implements ClickCallbac
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void initAppBar() {
|
private void initAppBar() {
|
||||||
activity.setSupportActionBar(bind.animToolbar);
|
activity.setSupportActionBar(bind.toolbar);
|
||||||
if (activity.getSupportActionBar() != null)
|
|
||||||
activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
|
||||||
|
|
||||||
bind.collapsingToolbar.setTitle(MusicUtil.getReadableString(podcastChannelPageViewModel.getPodcastChannel().getTitle()));
|
if (activity.getSupportActionBar() != null) {
|
||||||
bind.animToolbar.setNavigationOnClickListener(v -> activity.navController.navigateUp());
|
activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
bind.collapsingToolbar.setExpandedTitleColor(getResources().getColor(R.color.white, null));
|
activity.getSupportActionBar().setDisplayShowHomeEnabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
bind.toolbar.setTitle(MusicUtil.getReadableString(podcastChannelPageViewModel.getPodcastChannel().getTitle()));
|
||||||
|
bind.toolbar.setNavigationOnClickListener(v -> activity.navController.navigateUp());
|
||||||
|
bind.toolbar.setTitle(MusicUtil.getReadableString(podcastChannelPageViewModel.getPodcastChannel().getTitle()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initPodcastChannelInfo() {
|
private void initPodcastChannelInfo() {
|
||||||
String normalizePodcastChannelDescription = MusicUtil.forceReadableString(podcastChannelPageViewModel.getPodcastChannel().getDescription());
|
String normalizePodcastChannelDescription = MusicUtil.forceReadableString(podcastChannelPageViewModel.getPodcastChannel().getDescription());
|
||||||
|
|
||||||
if (bind != null)
|
if (bind != null) {
|
||||||
bind.podcastChannelDescriptionTextView.setVisibility(!normalizePodcastChannelDescription.trim().isEmpty() ? View.VISIBLE : View.GONE);
|
bind.podcastChannelDescriptionTextView.setVisibility(!normalizePodcastChannelDescription.trim().isEmpty() ? View.VISIBLE : View.GONE);
|
||||||
|
|
||||||
if (getContext() != null && bind != null) CustomGlideRequest.Builder
|
|
||||||
.from(requireContext(), podcastChannelPageViewModel.getPodcastChannel().getCoverArtId())
|
|
||||||
.build()
|
|
||||||
.into(bind.podcastChannelBackdropImageView);
|
|
||||||
|
|
||||||
if (bind != null)
|
|
||||||
bind.podcastChannelDescriptionTextView.setText(normalizePodcastChannelDescription);
|
bind.podcastChannelDescriptionTextView.setText(normalizePodcastChannelDescription);
|
||||||
|
bind.podcastEpisodesFilterImageView.setOnClickListener(view -> showPopupMenu(view, R.menu.filter_podcast_episode_popup_menu));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initPodcastChannelEpisodesView() {
|
private void initPodcastChannelEpisodesView() {
|
||||||
@@ -116,24 +113,23 @@ public class PodcastChannelPageFragment extends Fragment implements ClickCallbac
|
|||||||
bind.podcastEpisodesRecyclerView.setAdapter(podcastEpisodeAdapter);
|
bind.podcastEpisodesRecyclerView.setAdapter(podcastEpisodeAdapter);
|
||||||
podcastChannelPageViewModel.getPodcastChannelEpisodes().observe(getViewLifecycleOwner(), channels -> {
|
podcastChannelPageViewModel.getPodcastChannelEpisodes().observe(getViewLifecycleOwner(), channels -> {
|
||||||
if (channels == null) {
|
if (channels == null) {
|
||||||
if (bind != null)
|
|
||||||
bind.podcastChannelPageEpisodesPlaceholder.placeholder.setVisibility(View.VISIBLE);
|
|
||||||
if (bind != null) bind.podcastChannelPageEpisodesSector.setVisibility(View.GONE);
|
|
||||||
} else {
|
|
||||||
if (bind != null)
|
|
||||||
bind.podcastChannelPageEpisodesPlaceholder.placeholder.setVisibility(View.GONE);
|
|
||||||
|
|
||||||
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());
|
|
||||||
|
|
||||||
if (bind != null) {
|
if (bind != null) {
|
||||||
bind.podcastEpisodesRecyclerView.setVisibility(availableEpisode.isEmpty() ? View.GONE : View.VISIBLE);
|
bind.podcastEpisodesRecyclerView.setVisibility(View.GONE);
|
||||||
bind.podcastEpisodesAvailabilityTextView.setVisibility(availableEpisode.isEmpty() ? View.VISIBLE : View.GONE);
|
}
|
||||||
|
} else {
|
||||||
|
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();
|
||||||
|
|
||||||
|
if (bind != null && availableEpisode != null) {
|
||||||
|
bind.podcastEpisodesRecyclerView.setVisibility(availableEpisode.isEmpty() ? View.GONE : View.VISIBLE);
|
||||||
podcastEpisodeAdapter.setItems(availableEpisode);
|
podcastEpisodeAdapter.setItems(availableEpisode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,6 +141,25 @@ public class PodcastChannelPageFragment extends Fragment implements ClickCallbac
|
|||||||
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
|
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void showPopupMenu(View view, int menuResource) {
|
||||||
|
PopupMenu popup = new PopupMenu(requireContext(), view);
|
||||||
|
popup.getMenuInflater().inflate(menuResource, popup.getMenu());
|
||||||
|
|
||||||
|
popup.setOnMenuItemClickListener(menuItem -> {
|
||||||
|
if (menuItem.getItemId() == R.id.menu_podcast_filter_download) {
|
||||||
|
podcastEpisodeAdapter.sort(Constants.PODCAST_FILTER_BY_DOWNLOAD);
|
||||||
|
return true;
|
||||||
|
} else if (menuItem.getItemId() == R.id.menu_podcast_filter_all) {
|
||||||
|
podcastEpisodeAdapter.sort(Constants.PODCAST_FILTER_BY_ALL);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
popup.show();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPodcastEpisodeClick(Bundle bundle) {
|
public void onPodcastEpisodeClick(Bundle bundle) {
|
||||||
MediaManager.startPodcast(mediaBrowserListenableFuture, bundle.getParcelable(Constants.PODCAST_OBJECT));
|
MediaManager.startPodcast(mediaBrowserListenableFuture, bundle.getParcelable(Constants.PODCAST_OBJECT));
|
||||||
@@ -155,4 +170,14 @@ public class PodcastChannelPageFragment extends Fragment implements ClickCallbac
|
|||||||
public void onPodcastEpisodeLongClick(Bundle bundle) {
|
public void onPodcastEpisodeLongClick(Bundle bundle) {
|
||||||
Navigation.findNavController(requireView()).navigate(R.id.podcastEpisodeBottomSheetDialog, bundle);
|
Navigation.findNavController(requireView()).navigate(R.id.podcastEpisodeBottomSheetDialog, bundle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPodcastEpisodeAltClick(Bundle bundle) {
|
||||||
|
PodcastEpisode episode = bundle.getParcelable(Constants.PODCAST_OBJECT);
|
||||||
|
podcastChannelPageViewModel.requestPodcastEpisodeDownload(episode);
|
||||||
|
|
||||||
|
Snackbar.make(requireView(), R.string.podcast_episode_download_request_snackbar, Snackbar.LENGTH_SHORT)
|
||||||
|
.setAnchorView(activity.bind.bottomNavigation)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,7 @@ import android.content.ComponentName;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
import android.text.TextWatcher;
|
import android.text.TextWatcher;
|
||||||
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
@@ -111,7 +112,7 @@ public class SearchFragment extends Fragment implements ClickCallback {
|
|||||||
bind.searchResultTracksRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
|
bind.searchResultTracksRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
|
||||||
bind.searchResultTracksRecyclerView.setHasFixedSize(true);
|
bind.searchResultTracksRecyclerView.setHasFixedSize(true);
|
||||||
|
|
||||||
songHorizontalAdapter = new SongHorizontalAdapter(this, true);
|
songHorizontalAdapter = new SongHorizontalAdapter(this, true, false);
|
||||||
bind.searchResultTracksRecyclerView.setAdapter(songHorizontalAdapter);
|
bind.searchResultTracksRecyclerView.setAdapter(songHorizontalAdapter);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,17 +122,11 @@ public class SearchFragment extends Fragment implements ClickCallback {
|
|||||||
bind.searchView
|
bind.searchView
|
||||||
.getEditText()
|
.getEditText()
|
||||||
.setOnEditorActionListener((textView, actionId, keyEvent) -> {
|
.setOnEditorActionListener((textView, actionId, keyEvent) -> {
|
||||||
|
|
||||||
String query = bind.searchView.getText().toString();
|
String query = bind.searchView.getText().toString();
|
||||||
|
|
||||||
if (actionId == EditorInfo.IME_ACTION_DONE) {
|
|
||||||
if (isQueryValid(query)) {
|
if (isQueryValid(query)) {
|
||||||
search(bind.searchView.getText().toString());
|
search(query);
|
||||||
return true;
|
return true;
|
||||||
} else {
|
|
||||||
Toast.makeText(requireContext(), getString(R.string.search_info_minimum_characters), Toast.LENGTH_SHORT).show();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@@ -147,7 +142,7 @@ public class SearchFragment extends Fragment implements ClickCallback {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTextChanged(CharSequence charSequence, int start, int before, int count) {
|
public void onTextChanged(CharSequence charSequence, int start, int before, int count) {
|
||||||
if (count > 1) {
|
if (start + count > 1) {
|
||||||
setSearchSuggestions(charSequence.toString());
|
setSearchSuggestions(charSequence.toString());
|
||||||
} else {
|
} else {
|
||||||
setRecentSuggestions();
|
setRecentSuggestions();
|
||||||
@@ -247,6 +242,7 @@ public class SearchFragment extends Fragment implements ClickCallback {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean isQueryValid(String query) {
|
private boolean isQueryValid(String query) {
|
||||||
|
Log.d(TAG, "isQueryValid()");
|
||||||
return !query.equals("") && query.trim().length() > 2;
|
return !query.equals("") && query.trim().length() > 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,20 +12,30 @@ import androidx.activity.result.contract.ActivityResultContracts;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.OptIn;
|
import androidx.annotation.OptIn;
|
||||||
|
import androidx.appcompat.app.AppCompatDelegate;
|
||||||
|
import androidx.core.os.LocaleListCompat;
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
import androidx.preference.ListPreference;
|
import androidx.preference.ListPreference;
|
||||||
|
import androidx.preference.Preference;
|
||||||
import androidx.preference.PreferenceFragmentCompat;
|
import androidx.preference.PreferenceFragmentCompat;
|
||||||
|
|
||||||
import com.cappielloantonio.tempo.BuildConfig;
|
import com.cappielloantonio.tempo.BuildConfig;
|
||||||
import com.cappielloantonio.tempo.R;
|
import com.cappielloantonio.tempo.R;
|
||||||
import com.cappielloantonio.tempo.helper.ThemeHelper;
|
import com.cappielloantonio.tempo.helper.ThemeHelper;
|
||||||
|
import com.cappielloantonio.tempo.interfaces.DialogClickCallback;
|
||||||
import com.cappielloantonio.tempo.interfaces.ScanCallback;
|
import com.cappielloantonio.tempo.interfaces.ScanCallback;
|
||||||
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
||||||
|
import com.cappielloantonio.tempo.ui.dialog.DeleteDownloadStorageDialog;
|
||||||
|
import com.cappielloantonio.tempo.ui.dialog.DownloadStorageDialog;
|
||||||
import com.cappielloantonio.tempo.ui.dialog.StarredSyncDialog;
|
import com.cappielloantonio.tempo.ui.dialog.StarredSyncDialog;
|
||||||
import com.cappielloantonio.tempo.util.Preferences;
|
import com.cappielloantonio.tempo.util.Preferences;
|
||||||
|
import com.cappielloantonio.tempo.util.UIUtil;
|
||||||
import com.cappielloantonio.tempo.viewmodel.SettingViewModel;
|
import com.cappielloantonio.tempo.viewmodel.SettingViewModel;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
@OptIn(markerClass = UnstableApi.class)
|
@OptIn(markerClass = UnstableApi.class)
|
||||||
public class SettingsFragment extends PreferenceFragmentCompat {
|
public class SettingsFragment extends PreferenceFragmentCompat {
|
||||||
private static final String TAG = "SettingsFragment";
|
private static final String TAG = "SettingsFragment";
|
||||||
@@ -70,44 +80,17 @@ public class SettingsFragment extends PreferenceFragmentCompat {
|
|||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
|
|
||||||
findPreference("version").setSummary(BuildConfig.VERSION_NAME);
|
checkEqualizer();
|
||||||
|
checkStorage();
|
||||||
|
|
||||||
findPreference("logout").setOnPreferenceClickListener(preference -> {
|
setAppLanguage();
|
||||||
activity.quit();
|
setVersion();
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
findPreference("scan_library").setOnPreferenceClickListener(preference -> {
|
actionLogout();
|
||||||
settingViewModel.launchScan(new ScanCallback() {
|
actionScan();
|
||||||
@Override
|
actionSyncStarredTracks();
|
||||||
public void onError(Exception exception) {
|
actionChangeDownloadStorage();
|
||||||
findPreference("scan_library").setSummary(exception.getMessage());
|
actionDeleteDownloadStorage();
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSuccess(boolean isScanning, long count) {
|
|
||||||
getScanStatus();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
findPreference("equalizer").setOnPreferenceClickListener(preference -> {
|
|
||||||
Intent intent = new Intent(AudioEffect.ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL);
|
|
||||||
someActivityResultLauncher.launch(intent);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
findPreference("sync_starred_tracks_for_offline_use").setOnPreferenceChangeListener((preference, newValue) -> {
|
|
||||||
if (newValue instanceof Boolean) {
|
|
||||||
if ((Boolean) newValue) {
|
|
||||||
StarredSyncDialog dialog = new StarredSyncDialog();
|
|
||||||
dialog.show(activity.getSupportFragmentManager(), null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -130,6 +113,127 @@ public class SettingsFragment extends PreferenceFragmentCompat {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void checkEqualizer() {
|
||||||
|
Preference equalizer = findPreference("equalizer");
|
||||||
|
|
||||||
|
if (equalizer == null) return;
|
||||||
|
|
||||||
|
Intent intent = new Intent(AudioEffect.ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL);
|
||||||
|
|
||||||
|
if ((intent.resolveActivity(requireActivity().getPackageManager()) != null)) {
|
||||||
|
equalizer.setOnPreferenceClickListener(preference -> {
|
||||||
|
someActivityResultLauncher.launch(intent);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
equalizer.setVisible(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkStorage() {
|
||||||
|
Preference storage = findPreference("download_storage");
|
||||||
|
|
||||||
|
if (storage == null) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (requireContext().getExternalFilesDirs(null)[1] == null) {
|
||||||
|
storage.setVisible(false);
|
||||||
|
} else {
|
||||||
|
storage.setSummary(Preferences.getDownloadStoragePreference() == 0 ? R.string.download_storage_internal_dialog_negative_button : R.string.download_storage_external_dialog_positive_button);
|
||||||
|
}
|
||||||
|
} catch (Exception exception) {
|
||||||
|
storage.setVisible(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setAppLanguage() {
|
||||||
|
ListPreference localePref = (ListPreference) findPreference("language");
|
||||||
|
|
||||||
|
Map<String, String> locales = UIUtil.getLangPreferenceDropdownEntries(requireContext());
|
||||||
|
|
||||||
|
CharSequence[] entries = locales.keySet().toArray(new CharSequence[locales.size()]);
|
||||||
|
CharSequence[] entryValues = locales.values().toArray(new CharSequence[locales.size()]);
|
||||||
|
|
||||||
|
localePref.setEntries(entries);
|
||||||
|
localePref.setEntryValues(entryValues);
|
||||||
|
|
||||||
|
localePref.setDefaultValue(entryValues[0]);
|
||||||
|
localePref.setSummary(Locale.forLanguageTag(localePref.getValue()).getDisplayLanguage());
|
||||||
|
|
||||||
|
localePref.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||||
|
LocaleListCompat appLocale = LocaleListCompat.forLanguageTags((String) newValue);
|
||||||
|
AppCompatDelegate.setApplicationLocales(appLocale);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setVersion() {
|
||||||
|
findPreference("version").setSummary(BuildConfig.VERSION_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void actionLogout() {
|
||||||
|
findPreference("logout").setOnPreferenceClickListener(preference -> {
|
||||||
|
activity.quit();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void actionScan() {
|
||||||
|
findPreference("scan_library").setOnPreferenceClickListener(preference -> {
|
||||||
|
settingViewModel.launchScan(new ScanCallback() {
|
||||||
|
@Override
|
||||||
|
public void onError(Exception exception) {
|
||||||
|
findPreference("scan_library").setSummary(exception.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(boolean isScanning, long count) {
|
||||||
|
getScanStatus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void actionSyncStarredTracks() {
|
||||||
|
findPreference("sync_starred_tracks_for_offline_use").setOnPreferenceChangeListener((preference, newValue) -> {
|
||||||
|
if (newValue instanceof Boolean) {
|
||||||
|
if ((Boolean) newValue) {
|
||||||
|
StarredSyncDialog dialog = new StarredSyncDialog();
|
||||||
|
dialog.show(activity.getSupportFragmentManager(), null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void actionChangeDownloadStorage() {
|
||||||
|
findPreference("download_storage").setOnPreferenceClickListener(preference -> {
|
||||||
|
DownloadStorageDialog dialog = new DownloadStorageDialog(new DialogClickCallback() {
|
||||||
|
@Override
|
||||||
|
public void onPositiveClick() {
|
||||||
|
findPreference("download_storage").setSummary(R.string.download_storage_external_dialog_positive_button);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNegativeClick() {
|
||||||
|
findPreference("download_storage").setSummary(R.string.download_storage_internal_dialog_negative_button);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
dialog.show(activity.getSupportFragmentManager(), null);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void actionDeleteDownloadStorage() {
|
||||||
|
findPreference("delete_download_storage").setOnPreferenceClickListener(preference -> {
|
||||||
|
DeleteDownloadStorageDialog dialog = new DeleteDownloadStorageDialog();
|
||||||
|
dialog.show(activity.getSupportFragmentManager(), null);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void getScanStatus() {
|
private void getScanStatus() {
|
||||||
settingViewModel.getScanStatus(new ScanCallback() {
|
settingViewModel.getScanStatus(new ScanCallback() {
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -166,7 +166,7 @@ public class SongListPageFragment extends Fragment implements ClickCallback {
|
|||||||
bind.songListRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
|
bind.songListRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
|
||||||
bind.songListRecyclerView.setHasFixedSize(true);
|
bind.songListRecyclerView.setHasFixedSize(true);
|
||||||
|
|
||||||
songHorizontalAdapter = new SongHorizontalAdapter(this, true);
|
songHorizontalAdapter = new SongHorizontalAdapter(this, true, false);
|
||||||
bind.songListRecyclerView.setAdapter(songHorizontalAdapter);
|
bind.songListRecyclerView.setAdapter(songHorizontalAdapter);
|
||||||
songListPageViewModel.getSongList().observe(getViewLifecycleOwner(), songs -> {
|
songListPageViewModel.getSongList().observe(getViewLifecycleOwner(), songs -> {
|
||||||
isLoading = false;
|
isLoading = false;
|
||||||
|
|||||||
@@ -91,10 +91,9 @@ public class AlbumBottomSheetDialog extends BottomSheetDialogFragment implements
|
|||||||
artistAlbum.setText(MusicUtil.getReadableString(albumBottomSheetViewModel.getAlbum().getArtist()));
|
artistAlbum.setText(MusicUtil.getReadableString(albumBottomSheetViewModel.getAlbum().getArtist()));
|
||||||
|
|
||||||
ToggleButton favoriteToggle = view.findViewById(R.id.button_favorite);
|
ToggleButton favoriteToggle = view.findViewById(R.id.button_favorite);
|
||||||
favoriteToggle.setChecked(Boolean.TRUE.equals(albumBottomSheetViewModel.getAlbum().getStarred()));
|
favoriteToggle.setChecked(albumBottomSheetViewModel.getAlbum().getStarred() != null);
|
||||||
favoriteToggle.setOnClickListener(v -> {
|
favoriteToggle.setOnClickListener(v -> {
|
||||||
albumBottomSheetViewModel.setFavorite();
|
albumBottomSheetViewModel.setFavorite();
|
||||||
dismissBottomSheet();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
TextView playRadio = view.findViewById(R.id.play_radio_text_view);
|
TextView playRadio = view.findViewById(R.id.play_radio_text_view);
|
||||||
|
|||||||
@@ -79,10 +79,9 @@ public class ArtistBottomSheetDialog extends BottomSheetDialogFragment implement
|
|||||||
nameArtist.setSelected(true);
|
nameArtist.setSelected(true);
|
||||||
|
|
||||||
ToggleButton favoriteToggle = view.findViewById(R.id.button_favorite);
|
ToggleButton favoriteToggle = view.findViewById(R.id.button_favorite);
|
||||||
favoriteToggle.setChecked(Boolean.TRUE.equals(artistBottomSheetViewModel.getArtist().getStarred()));
|
favoriteToggle.setChecked(artistBottomSheetViewModel.getArtist().getStarred() != null);
|
||||||
favoriteToggle.setOnClickListener(v -> {
|
favoriteToggle.setOnClickListener(v -> {
|
||||||
artistBottomSheetViewModel.setFavorite();
|
artistBottomSheetViewModel.setFavorite();
|
||||||
dismissBottomSheet();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
TextView playRadio = view.findViewById(R.id.play_radio_text_view);
|
TextView playRadio = view.findViewById(R.id.play_radio_text_view);
|
||||||
|
|||||||
@@ -87,10 +87,9 @@ public class SongBottomSheetDialog extends BottomSheetDialogFragment implements
|
|||||||
artistSong.setText(MusicUtil.getReadableString(songBottomSheetViewModel.getSong().getArtist()));
|
artistSong.setText(MusicUtil.getReadableString(songBottomSheetViewModel.getSong().getArtist()));
|
||||||
|
|
||||||
ToggleButton favoriteToggle = view.findViewById(R.id.button_favorite);
|
ToggleButton favoriteToggle = view.findViewById(R.id.button_favorite);
|
||||||
favoriteToggle.setChecked(Boolean.TRUE.equals(songBottomSheetViewModel.getSong().getStarred()));
|
favoriteToggle.setChecked(songBottomSheetViewModel.getSong().getStarred() != null);
|
||||||
favoriteToggle.setOnClickListener(v -> {
|
favoriteToggle.setOnClickListener(v -> {
|
||||||
songBottomSheetViewModel.setFavorite(requireContext());
|
songBottomSheetViewModel.setFavorite(requireContext());
|
||||||
dismissBottomSheet();
|
|
||||||
});
|
});
|
||||||
favoriteToggle.setOnLongClickListener(v -> {
|
favoriteToggle.setOnLongClickListener(v -> {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
@@ -216,7 +215,6 @@ public class SongBottomSheetDialog extends BottomSheetDialogFragment implements
|
|||||||
|
|
||||||
private void initDownloadUI(TextView download, TextView remove) {
|
private void initDownloadUI(TextView download, TextView remove) {
|
||||||
if (DownloadUtil.getDownloadTracker(requireContext()).isDownloaded(song.getId())) {
|
if (DownloadUtil.getDownloadTracker(requireContext()).isDownloaded(song.getId())) {
|
||||||
download.setVisibility(View.GONE);
|
|
||||||
remove.setVisibility(View.VISIBLE);
|
remove.setVisibility(View.VISIBLE);
|
||||||
} else {
|
} else {
|
||||||
download.setVisibility(View.VISIBLE);
|
download.setVisibility(View.VISIBLE);
|
||||||
|
|||||||
@@ -44,6 +44,9 @@ object Constants {
|
|||||||
const val PLAYLIST_ORDER_BY_NAME = "ORDER_BY_NAME"
|
const val PLAYLIST_ORDER_BY_NAME = "ORDER_BY_NAME"
|
||||||
const val PLAYLIST_ORDER_BY_RANDOM = "ORDER_BY_RANDOM"
|
const val PLAYLIST_ORDER_BY_RANDOM = "ORDER_BY_RANDOM"
|
||||||
|
|
||||||
|
const val PODCAST_FILTER_BY_DOWNLOAD = "PODCAST_FILTER_BY_DOWNLOAD"
|
||||||
|
const val PODCAST_FILTER_BY_ALL = "PODCAST_FILTER_BY_ALL"
|
||||||
|
|
||||||
const val MEDIA_TYPE_MUSIC = "music"
|
const val MEDIA_TYPE_MUSIC = "music"
|
||||||
const val MEDIA_TYPE_PODCAST = "podcast"
|
const val MEDIA_TYPE_PODCAST = "podcast"
|
||||||
const val MEDIA_TYPE_AUDIOBOOK = "audiobook"
|
const val MEDIA_TYPE_AUDIOBOOK = "audiobook"
|
||||||
@@ -73,6 +76,12 @@ object Constants {
|
|||||||
|
|
||||||
const val DOWNLOAD_URI = "rest/download"
|
const val DOWNLOAD_URI = "rest/download"
|
||||||
|
|
||||||
|
const val DOWNLOAD_TYPE_TRACK = "download_type_track"
|
||||||
|
const val DOWNLOAD_TYPE_ALBUM = "download_type_album"
|
||||||
|
const val DOWNLOAD_TYPE_ARTIST = "download_type_artist"
|
||||||
|
const val DOWNLOAD_TYPE_GENRE = "download_type_genre"
|
||||||
|
const val DOWNLOAD_TYPE_YEAR = "download_type_year"
|
||||||
|
|
||||||
const val PLAYABLE_MEDIA_LIMIT = 100
|
const val PLAYABLE_MEDIA_LIMIT = 100
|
||||||
const val PRE_PLAYABLE_MEDIA = 15
|
const val PRE_PLAYABLE_MEDIA = 15
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,7 @@ package com.cappielloantonio.tempo.util;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
||||||
|
import androidx.media3.common.util.Log;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
import androidx.media3.database.DatabaseProvider;
|
import androidx.media3.database.DatabaseProvider;
|
||||||
import androidx.media3.database.StandaloneDatabaseProvider;
|
import androidx.media3.database.StandaloneDatabaseProvider;
|
||||||
@@ -23,6 +24,8 @@ import java.io.File;
|
|||||||
import java.net.CookieHandler;
|
import java.net.CookieHandler;
|
||||||
import java.net.CookieManager;
|
import java.net.CookieManager;
|
||||||
import java.net.CookiePolicy;
|
import java.net.CookiePolicy;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@@ -127,10 +130,20 @@ public final class DownloadUtil {
|
|||||||
|
|
||||||
private static synchronized File getDownloadDirectory(Context context) {
|
private static synchronized File getDownloadDirectory(Context context) {
|
||||||
if (downloadDirectory == null) {
|
if (downloadDirectory == null) {
|
||||||
downloadDirectory = context.getExternalFilesDir(null);
|
if (Preferences.getDownloadStoragePreference() == 0) {
|
||||||
|
downloadDirectory = context.getExternalFilesDirs(null)[0];
|
||||||
if (downloadDirectory == null) {
|
if (downloadDirectory == null) {
|
||||||
downloadDirectory = context.getFilesDir();
|
downloadDirectory = context.getFilesDir();
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
downloadDirectory = context.getExternalFilesDirs(null)[1];
|
||||||
|
} catch (Exception exception) {
|
||||||
|
downloadDirectory = context.getExternalFilesDirs(null)[0];
|
||||||
|
Preferences.setDownloadStoragePreference(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return downloadDirectory;
|
return downloadDirectory;
|
||||||
@@ -143,4 +156,32 @@ public final class DownloadUtil {
|
|||||||
.setCacheWriteDataSinkFactory(null)
|
.setCacheWriteDataSinkFactory(null)
|
||||||
.setFlags(CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR);
|
.setFlags(CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static synchronized void eraseDownloadFolder(Context context) {
|
||||||
|
File directory = getDownloadDirectory(context);
|
||||||
|
|
||||||
|
ArrayList<File> files = listFiles(directory, new ArrayList<>());
|
||||||
|
|
||||||
|
for (File file : files) {
|
||||||
|
file.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static synchronized ArrayList<File> listFiles(File directory, ArrayList<File> files) {
|
||||||
|
if (directory.isDirectory()) {
|
||||||
|
File[] list = directory.listFiles();
|
||||||
|
|
||||||
|
if (list != null) {
|
||||||
|
for (File file : list) {
|
||||||
|
if (file.isFile() && file.getName().toLowerCase().endsWith(".exo")) {
|
||||||
|
files.add(file);
|
||||||
|
} else if (file.isDirectory()) {
|
||||||
|
listFiles(file, files);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return files;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ import androidx.media3.common.MimeTypes;
|
|||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
|
||||||
import com.cappielloantonio.tempo.App;
|
import com.cappielloantonio.tempo.App;
|
||||||
|
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
||||||
|
import com.cappielloantonio.tempo.model.Download;
|
||||||
|
import com.cappielloantonio.tempo.repository.DownloadRepository;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.InternetRadioStation;
|
import com.cappielloantonio.tempo.subsonic.models.InternetRadioStation;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.PodcastEpisode;
|
import com.cappielloantonio.tempo.subsonic.models.PodcastEpisode;
|
||||||
@@ -31,6 +34,7 @@ public class MappingUtil {
|
|||||||
|
|
||||||
public static MediaItem mapMediaItem(Child media) {
|
public static MediaItem mapMediaItem(Child media) {
|
||||||
Uri uri = getUri(media);
|
Uri uri = getUri(media);
|
||||||
|
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(media.getCoverArtId(), Preferences.getImageSize()));
|
||||||
|
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putString("id", media.getId());
|
bundle.putString("id", media.getId());
|
||||||
@@ -54,8 +58,8 @@ public class MappingUtil {
|
|||||||
bundle.putBoolean("isVideo", media.isVideo());
|
bundle.putBoolean("isVideo", media.isVideo());
|
||||||
bundle.putInt("userRating", media.getUserRating() != null ? media.getUserRating() : 0);
|
bundle.putInt("userRating", media.getUserRating() != null ? media.getUserRating() : 0);
|
||||||
bundle.putDouble("averageRating", media.getAverageRating() != null ? media.getAverageRating() : 0);
|
bundle.putDouble("averageRating", media.getAverageRating() != null ? media.getAverageRating() : 0);
|
||||||
bundle.putLong("playCount", media.getPlayCount() != null ? media.getTrack() : 0);
|
bundle.putLong("playCount", media.getPlayCount() != null ? media.getPlayCount() : 0);
|
||||||
bundle.putInt("discNumber", media.getDiscNumber() != null ? media.getTrack() : 0);
|
bundle.putInt("discNumber", media.getDiscNumber() != null ? media.getDiscNumber() : 0);
|
||||||
bundle.putLong("created", media.getCreated() != null ? media.getCreated().getTime() : 0);
|
bundle.putLong("created", media.getCreated() != null ? media.getCreated().getTime() : 0);
|
||||||
bundle.putLong("starred", media.getStarred() != null ? media.getStarred().getTime() : 0);
|
bundle.putLong("starred", media.getStarred() != null ? media.getStarred().getTime() : 0);
|
||||||
bundle.putString("albumId", media.getAlbumId());
|
bundle.putString("albumId", media.getAlbumId());
|
||||||
@@ -71,11 +75,12 @@ public class MappingUtil {
|
|||||||
.setMediaMetadata(
|
.setMediaMetadata(
|
||||||
new MediaMetadata.Builder()
|
new MediaMetadata.Builder()
|
||||||
.setTitle(MusicUtil.getReadableString(media.getTitle()))
|
.setTitle(MusicUtil.getReadableString(media.getTitle()))
|
||||||
.setTrackNumber(media.getTrack())
|
.setTrackNumber(media.getTrack() != null ? media.getTrack() : 0)
|
||||||
.setDiscNumber(media.getDiscNumber())
|
.setDiscNumber(media.getDiscNumber() != null ? media.getDiscNumber() : 0)
|
||||||
.setReleaseYear(media.getYear())
|
.setReleaseYear(media.getYear() != null ? media.getYear() : 0)
|
||||||
.setAlbumTitle(MusicUtil.getReadableString(media.getAlbum()))
|
.setAlbumTitle(MusicUtil.getReadableString(media.getAlbum()))
|
||||||
.setArtist(MusicUtil.getReadableString(media.getArtist()))
|
.setArtist(MusicUtil.getReadableString(media.getArtist()))
|
||||||
|
.setArtworkUri(artworkUri)
|
||||||
.setExtras(bundle)
|
.setExtras(bundle)
|
||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
@@ -106,20 +111,20 @@ public class MappingUtil {
|
|||||||
.setMediaMetadata(
|
.setMediaMetadata(
|
||||||
new MediaMetadata.Builder()
|
new MediaMetadata.Builder()
|
||||||
.setTitle(MusicUtil.getReadableString(media.getTitle()))
|
.setTitle(MusicUtil.getReadableString(media.getTitle()))
|
||||||
.setTrackNumber(media.getTrack())
|
.setTrackNumber(media.getTrack() != null ? media.getTrack() : 0)
|
||||||
.setDiscNumber(media.getDiscNumber())
|
.setDiscNumber(media.getDiscNumber() != null ? media.getDiscNumber() : 0)
|
||||||
.setReleaseYear(media.getYear())
|
.setReleaseYear(media.getYear() != null ? media.getYear() : 0)
|
||||||
.setAlbumTitle(MusicUtil.getReadableString(media.getAlbum()))
|
.setAlbumTitle(MusicUtil.getReadableString(media.getAlbum()))
|
||||||
.setArtist(MusicUtil.getReadableString(media.getArtist()))
|
.setArtist(MusicUtil.getReadableString(media.getArtist()))
|
||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
.setRequestMetadata(
|
.setRequestMetadata(
|
||||||
new MediaItem.RequestMetadata.Builder()
|
new MediaItem.RequestMetadata.Builder()
|
||||||
.setMediaUri(MusicUtil.getDownloadUri(media.getId()))
|
.setMediaUri(Preferences.preferTranscodedDownload() ? MusicUtil.getTranscodedDownloadUri(media.getId()) : MusicUtil.getDownloadUri(media.getId()))
|
||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
.setMimeType(MimeTypes.BASE_TYPE_AUDIO)
|
.setMimeType(MimeTypes.BASE_TYPE_AUDIO)
|
||||||
.setUri(MusicUtil.getDownloadUri(media.getId()))
|
.setUri(Preferences.preferTranscodedDownload() ? MusicUtil.getTranscodedDownloadUri(media.getId()) : MusicUtil.getDownloadUri(media.getId()))
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,6 +160,7 @@ public class MappingUtil {
|
|||||||
|
|
||||||
public static MediaItem mapMediaItem(PodcastEpisode podcastEpisode) {
|
public static MediaItem mapMediaItem(PodcastEpisode podcastEpisode) {
|
||||||
Uri uri = getUri(podcastEpisode);
|
Uri uri = getUri(podcastEpisode);
|
||||||
|
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(podcastEpisode.getCoverArtId(), Preferences.getImageSize()));
|
||||||
|
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putString("id", podcastEpisode.getId());
|
bundle.putString("id", podcastEpisode.getId());
|
||||||
@@ -178,8 +184,8 @@ public class MappingUtil {
|
|||||||
bundle.putBoolean("isVideo", podcastEpisode.isVideo());
|
bundle.putBoolean("isVideo", podcastEpisode.isVideo());
|
||||||
bundle.putInt("userRating", podcastEpisode.getUserRating() != null ? podcastEpisode.getUserRating() : 0);
|
bundle.putInt("userRating", podcastEpisode.getUserRating() != null ? podcastEpisode.getUserRating() : 0);
|
||||||
bundle.putDouble("averageRating", podcastEpisode.getAverageRating() != null ? podcastEpisode.getAverageRating() : 0);
|
bundle.putDouble("averageRating", podcastEpisode.getAverageRating() != null ? podcastEpisode.getAverageRating() : 0);
|
||||||
bundle.putLong("playCount", podcastEpisode.getPlayCount() != null ? podcastEpisode.getTrack() : 0);
|
bundle.putLong("playCount", podcastEpisode.getPlayCount() != null ? podcastEpisode.getPlayCount() : 0);
|
||||||
bundle.putInt("discNumber", podcastEpisode.getDiscNumber() != null ? podcastEpisode.getTrack() : 0);
|
bundle.putInt("discNumber", podcastEpisode.getDiscNumber() != null ? podcastEpisode.getDiscNumber() : 0);
|
||||||
bundle.putLong("created", podcastEpisode.getCreated() != null ? podcastEpisode.getCreated().getTime() : 0);
|
bundle.putLong("created", podcastEpisode.getCreated() != null ? podcastEpisode.getCreated().getTime() : 0);
|
||||||
bundle.putLong("starred", podcastEpisode.getStarred() != null ? podcastEpisode.getStarred().getTime() : 0);
|
bundle.putLong("starred", podcastEpisode.getStarred() != null ? podcastEpisode.getStarred().getTime() : 0);
|
||||||
bundle.putString("albumId", podcastEpisode.getAlbumId());
|
bundle.putString("albumId", podcastEpisode.getAlbumId());
|
||||||
@@ -195,11 +201,12 @@ public class MappingUtil {
|
|||||||
.setMediaMetadata(
|
.setMediaMetadata(
|
||||||
new MediaMetadata.Builder()
|
new MediaMetadata.Builder()
|
||||||
.setTitle(MusicUtil.getReadableString(podcastEpisode.getTitle()))
|
.setTitle(MusicUtil.getReadableString(podcastEpisode.getTitle()))
|
||||||
.setTrackNumber(podcastEpisode.getTrack())
|
.setTrackNumber(podcastEpisode.getTrack() != null ? podcastEpisode.getTrack() : 0)
|
||||||
.setDiscNumber(podcastEpisode.getDiscNumber())
|
.setDiscNumber(podcastEpisode.getDiscNumber() != null ? podcastEpisode.getDiscNumber() : 0)
|
||||||
.setReleaseYear(podcastEpisode.getYear())
|
.setReleaseYear(podcastEpisode.getYear() != null ? podcastEpisode.getYear() : 0)
|
||||||
.setAlbumTitle(MusicUtil.getReadableString(podcastEpisode.getAlbum()))
|
.setAlbumTitle(MusicUtil.getReadableString(podcastEpisode.getAlbum()))
|
||||||
.setArtist(MusicUtil.getReadableString(podcastEpisode.getArtist()))
|
.setArtist(MusicUtil.getReadableString(podcastEpisode.getArtist()))
|
||||||
|
.setArtworkUri(artworkUri)
|
||||||
.setExtras(bundle)
|
.setExtras(bundle)
|
||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
@@ -216,13 +223,18 @@ public class MappingUtil {
|
|||||||
|
|
||||||
private static Uri getUri(Child media) {
|
private static Uri getUri(Child media) {
|
||||||
return DownloadUtil.getDownloadTracker(App.getContext()).isDownloaded(media.getId())
|
return DownloadUtil.getDownloadTracker(App.getContext()).isDownloaded(media.getId())
|
||||||
? MusicUtil.getDownloadUri(media.getId())
|
? getDownloadUri(media.getId())
|
||||||
: MusicUtil.getStreamUri(media.getId());
|
: MusicUtil.getStreamUri(media.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Uri getUri(PodcastEpisode podcastEpisode) {
|
private static Uri getUri(PodcastEpisode podcastEpisode) {
|
||||||
return DownloadUtil.getDownloadTracker(App.getContext()).isDownloaded(podcastEpisode.getId())
|
return DownloadUtil.getDownloadTracker(App.getContext()).isDownloaded(podcastEpisode.getStreamId())
|
||||||
? MusicUtil.getDownloadUri(podcastEpisode.getId())
|
? getDownloadUri(podcastEpisode.getStreamId())
|
||||||
: MusicUtil.getStreamUri(podcastEpisode.getId());
|
: MusicUtil.getStreamUri(podcastEpisode.getStreamId());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Uri getDownloadUri(String id) {
|
||||||
|
Download download = new DownloadRepository().getDownload(id);
|
||||||
|
return download != null && !download.getDownloadUri().isEmpty() ? Uri.parse(download.getDownloadUri()) : MusicUtil.getDownloadUri(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,13 @@ import android.text.Html;
|
|||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.cappielloantonio.tempo.App;
|
import com.cappielloantonio.tempo.App;
|
||||||
|
import com.cappielloantonio.tempo.R;
|
||||||
|
import com.cappielloantonio.tempo.model.Download;
|
||||||
|
import com.cappielloantonio.tempo.repository.DownloadRepository;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||||
|
|
||||||
|
import java.text.CharacterIterator;
|
||||||
|
import java.text.StringCharacterIterator;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
@@ -29,7 +34,7 @@ public class MusicUtil {
|
|||||||
uri.append("stream");
|
uri.append("stream");
|
||||||
|
|
||||||
if (params.containsKey("u") && params.get("u") != null)
|
if (params.containsKey("u") && params.get("u") != null)
|
||||||
uri.append("?u=").append(params.get("u"));
|
uri.append("?u=").append(Util.encode(params.get("u")));
|
||||||
if (params.containsKey("p") && params.get("p") != null)
|
if (params.containsKey("p") && params.get("p") != null)
|
||||||
uri.append("&p=").append(params.get("p"));
|
uri.append("&p=").append(params.get("p"));
|
||||||
if (params.containsKey("s") && params.get("s") != null)
|
if (params.containsKey("s") && params.get("s") != null)
|
||||||
@@ -41,8 +46,11 @@ public class MusicUtil {
|
|||||||
if (params.containsKey("c") && params.get("c") != null)
|
if (params.containsKey("c") && params.get("c") != null)
|
||||||
uri.append("&c=").append(params.get("c"));
|
uri.append("&c=").append(params.get("c"));
|
||||||
|
|
||||||
|
if (!Preferences.isServerPrioritized())
|
||||||
uri.append("&maxBitRate=").append(getBitratePreference());
|
uri.append("&maxBitRate=").append(getBitratePreference());
|
||||||
|
if (!Preferences.isServerPrioritized())
|
||||||
uri.append("&format=").append(getTranscodingFormatPreference());
|
uri.append("&format=").append(getTranscodingFormatPreference());
|
||||||
|
|
||||||
uri.append("&id=").append(id);
|
uri.append("&id=").append(id);
|
||||||
|
|
||||||
Log.d(TAG, "getStreamUri: " + uri);
|
Log.d(TAG, "getStreamUri: " + uri);
|
||||||
@@ -51,15 +59,18 @@ public class MusicUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static Uri getDownloadUri(String id) {
|
public static Uri getDownloadUri(String id) {
|
||||||
Map<String, String> params = App.getSubsonicClientInstance(false).getParams();
|
|
||||||
|
|
||||||
StringBuilder uri = new StringBuilder();
|
StringBuilder uri = new StringBuilder();
|
||||||
|
|
||||||
|
Download download = new DownloadRepository().getDownload(id);
|
||||||
|
|
||||||
|
if (download == null || download.getDownloadUri().isEmpty()) {
|
||||||
|
Map<String, String> params = App.getSubsonicClientInstance(false).getParams();
|
||||||
|
|
||||||
uri.append(App.getSubsonicClientInstance(false).getUrl());
|
uri.append(App.getSubsonicClientInstance(false).getUrl());
|
||||||
uri.append("download");
|
uri.append("download");
|
||||||
|
|
||||||
if (params.containsKey("u") && params.get("u") != null)
|
if (params.containsKey("u") && params.get("u") != null)
|
||||||
uri.append("?u=").append(params.get("u"));
|
uri.append("?u=").append(Util.encode(params.get("u")));
|
||||||
if (params.containsKey("p") && params.get("p") != null)
|
if (params.containsKey("p") && params.get("p") != null)
|
||||||
uri.append("&p=").append(params.get("p"));
|
uri.append("&p=").append(params.get("p"));
|
||||||
if (params.containsKey("s") && params.get("s") != null)
|
if (params.containsKey("s") && params.get("s") != null)
|
||||||
@@ -72,12 +83,49 @@ public class MusicUtil {
|
|||||||
uri.append("&c=").append(params.get("c"));
|
uri.append("&c=").append(params.get("c"));
|
||||||
|
|
||||||
uri.append("&id=").append(id);
|
uri.append("&id=").append(id);
|
||||||
|
} else {
|
||||||
|
uri.append(download.getDownloadUri());
|
||||||
|
}
|
||||||
|
|
||||||
Log.d(TAG, "getDownloadUri: " + uri);
|
Log.d(TAG, "getDownloadUri: " + uri);
|
||||||
|
|
||||||
return Uri.parse(uri.toString());
|
return Uri.parse(uri.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Uri getTranscodedDownloadUri(String id) {
|
||||||
|
Map<String, String> params = App.getSubsonicClientInstance(false).getParams();
|
||||||
|
|
||||||
|
StringBuilder uri = new StringBuilder();
|
||||||
|
|
||||||
|
uri.append(App.getSubsonicClientInstance(false).getUrl());
|
||||||
|
uri.append("stream");
|
||||||
|
|
||||||
|
if (params.containsKey("u") && params.get("u") != null)
|
||||||
|
uri.append("?u=").append(Util.encode(params.get("u")));
|
||||||
|
if (params.containsKey("p") && params.get("p") != null)
|
||||||
|
uri.append("&p=").append(params.get("p"));
|
||||||
|
if (params.containsKey("s") && params.get("s") != null)
|
||||||
|
uri.append("&s=").append(params.get("s"));
|
||||||
|
if (params.containsKey("t") && params.get("t") != null)
|
||||||
|
uri.append("&t=").append(params.get("t"));
|
||||||
|
if (params.containsKey("v") && params.get("v") != null)
|
||||||
|
uri.append("&v=").append(params.get("v"));
|
||||||
|
if (params.containsKey("c") && params.get("c") != null)
|
||||||
|
uri.append("&c=").append(params.get("c"));
|
||||||
|
|
||||||
|
if (!Preferences.isServerPrioritizedInTranscodedDownload())
|
||||||
|
uri.append("&maxBitRate=").append(getBitratePreferenceForDownload());
|
||||||
|
if (!Preferences.isServerPrioritizedInTranscodedDownload())
|
||||||
|
uri.append("&format=").append(getTranscodingFormatPreferenceForDownload());
|
||||||
|
|
||||||
|
uri.append("&id=").append(id);
|
||||||
|
|
||||||
|
Log.d(TAG, "getTranscodedDownloadUri: " + uri);
|
||||||
|
|
||||||
|
return Uri.parse(uri.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public static String getReadableDurationString(long duration, boolean millis) {
|
public static String getReadableDurationString(long duration, boolean millis) {
|
||||||
long minutes;
|
long minutes;
|
||||||
long seconds;
|
long seconds;
|
||||||
@@ -119,6 +167,14 @@ public class MusicUtil {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getReadableTrackNumber(Context context, Integer trackNumber) {
|
||||||
|
if (trackNumber != null) {
|
||||||
|
return String.valueOf(trackNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
return context.getString(R.string.label_placeholder);
|
||||||
|
}
|
||||||
|
|
||||||
public static String forceReadableString(String string) {
|
public static String forceReadableString(String string) {
|
||||||
if (string != null) {
|
if (string != null) {
|
||||||
return getReadableString(string)
|
return getReadableString(string)
|
||||||
@@ -157,6 +213,27 @@ public class MusicUtil {
|
|||||||
return readableStrings;
|
return readableStrings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getReadableByteCount(long bytes) {
|
||||||
|
long absB = bytes == Long.MIN_VALUE ? Long.MAX_VALUE : Math.abs(bytes);
|
||||||
|
|
||||||
|
if (absB < 1024) {
|
||||||
|
return bytes + " B";
|
||||||
|
}
|
||||||
|
|
||||||
|
long value = absB;
|
||||||
|
|
||||||
|
CharacterIterator ci = new StringCharacterIterator("KMGTPE");
|
||||||
|
|
||||||
|
for (int i = 40; i >= 0 && absB > 0xfffccccccccccccL >> i; i -= 10) {
|
||||||
|
value >>= 10;
|
||||||
|
ci.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
value *= Long.signum(bytes);
|
||||||
|
|
||||||
|
return String.format("%.1f %ciB", value / 1024.0, ci.current());
|
||||||
|
}
|
||||||
|
|
||||||
public static String passwordHexEncoding(String plainPassword) {
|
public static String passwordHexEncoding(String plainPassword) {
|
||||||
return "enc:" + plainPassword.chars().mapToObj(Integer::toHexString).collect(Collectors.joining());
|
return "enc:" + plainPassword.chars().mapToObj(Integer::toHexString).collect(Collectors.joining());
|
||||||
}
|
}
|
||||||
@@ -193,6 +270,19 @@ public class MusicUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getBitratePreferenceForDownload() {
|
||||||
|
String audioTranscodeFormat = getTranscodingFormatPreferenceForDownload();
|
||||||
|
|
||||||
|
if (audioTranscodeFormat.equals("raw"))
|
||||||
|
return "0";
|
||||||
|
|
||||||
|
return Preferences.getBitrateTranscodedDownload();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getTranscodingFormatPreferenceForDownload() {
|
||||||
|
return Preferences.getAudioTranscodeFormatTranscodedDownload();
|
||||||
|
}
|
||||||
|
|
||||||
public static List<Child> limitPlayableMedia(List<Child> toLimit, int position) {
|
public static List<Child> limitPlayableMedia(List<Child> toLimit, int position) {
|
||||||
if (!toLimit.isEmpty() && toLimit.size() > Constants.PLAYABLE_MEDIA_LIMIT) {
|
if (!toLimit.isEmpty() && toLimit.size() > Constants.PLAYABLE_MEDIA_LIMIT) {
|
||||||
int from = position < Constants.PRE_PLAYABLE_MEDIA ? 0 : position - Constants.PRE_PLAYABLE_MEDIA;
|
int from = position < Constants.PRE_PLAYABLE_MEDIA ? 0 : position - Constants.PRE_PLAYABLE_MEDIA;
|
||||||
@@ -204,8 +294,12 @@ public class MusicUtil {
|
|||||||
return toLimit;
|
return toLimit;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getPlayableMediaPosition(int initialPosition) {
|
public static int getPlayableMediaPosition(List<Child> toLimit, int position) {
|
||||||
return initialPosition > Constants.PLAYABLE_MEDIA_LIMIT ? Constants.PRE_PLAYABLE_MEDIA : initialPosition;
|
if (!toLimit.isEmpty() && toLimit.size() > Constants.PLAYABLE_MEDIA_LIMIT) {
|
||||||
|
return Math.min(position, Constants.PRE_PLAYABLE_MEDIA);
|
||||||
|
}
|
||||||
|
|
||||||
|
return position;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ConnectivityManager getConnectivityManager() {
|
private static ConnectivityManager getConnectivityManager() {
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.cappielloantonio.tempo.util;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.net.ConnectivityManager;
|
||||||
|
import android.net.NetworkInfo;
|
||||||
|
|
||||||
|
import com.cappielloantonio.tempo.App;
|
||||||
|
|
||||||
|
public class NetworkUtil {
|
||||||
|
public static boolean isOffline() {
|
||||||
|
ConnectivityManager connectivityManager = (ConnectivityManager) App.getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||||
|
|
||||||
|
if (connectivityManager != null) {
|
||||||
|
NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
|
||||||
|
|
||||||
|
return networkInfo == null || !networkInfo.isConnected();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,6 +32,13 @@ object Preferences {
|
|||||||
private const val RADIO_SECTION_VISIBILITY = "radio_section_visibility"
|
private const val RADIO_SECTION_VISIBILITY = "radio_section_visibility"
|
||||||
private const val MUSIC_DIRECTORY_SECTION_VISIBILITY = "music_directory_section_visibility"
|
private const val MUSIC_DIRECTORY_SECTION_VISIBILITY = "music_directory_section_visibility"
|
||||||
private const val REPLAY_GAIN_MODE = "replay_gain_mode"
|
private const val REPLAY_GAIN_MODE = "replay_gain_mode"
|
||||||
|
private const val AUDIO_TRANSCODE_PRIORITY = "audio_transcode_priority"
|
||||||
|
private const val DOWNLOAD_STORAGE = "download_storage"
|
||||||
|
private const val DEFAULT_DOWNLOAD_VIEW_TYPE = "default_download_view_type"
|
||||||
|
private const val AUDIO_TRANSCODE_DOWNLOAD = "audio_transcode_download"
|
||||||
|
private const val AUDIO_TRANSCODE_DOWNLOAD_PRIORITY = "audio_transcode_download_priority"
|
||||||
|
private const val MAX_BITRATE_DOWNLOAD = "max_bitrate_download"
|
||||||
|
private const val AUDIO_TRANSCODE_FORMAT_DOWNLOAD = "audio_transcode_format_download"
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun getServer(): String? {
|
fun getServer(): String? {
|
||||||
@@ -104,7 +111,7 @@ object Preferences {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun askForOptimization(): Boolean? {
|
fun askForOptimization(): Boolean {
|
||||||
return App.getInstance().preferences.getBoolean(BATTERY_OPTIMIZATION, true)
|
return App.getInstance().preferences.getBoolean(BATTERY_OPTIMIZATION, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,4 +259,58 @@ object Preferences {
|
|||||||
fun getReplayGainMode(): String? {
|
fun getReplayGainMode(): String? {
|
||||||
return App.getInstance().preferences.getString(REPLAY_GAIN_MODE, "disabled")
|
return App.getInstance().preferences.getString(REPLAY_GAIN_MODE, "disabled")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun isServerPrioritized(): Boolean {
|
||||||
|
return App.getInstance().preferences.getBoolean(AUDIO_TRANSCODE_PRIORITY, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun getDownloadStoragePreference(): Int {
|
||||||
|
return App.getInstance().preferences.getString(DOWNLOAD_STORAGE, "0")!!.toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun setDownloadStoragePreference(storagePreference: Int) {
|
||||||
|
return App.getInstance().preferences.edit().putString(
|
||||||
|
DOWNLOAD_STORAGE,
|
||||||
|
storagePreference.toString()
|
||||||
|
).apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun getDefaultDownloadViewType(): String {
|
||||||
|
return App.getInstance().preferences.getString(
|
||||||
|
DEFAULT_DOWNLOAD_VIEW_TYPE,
|
||||||
|
Constants.DOWNLOAD_TYPE_TRACK
|
||||||
|
)!!
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun setDefaultDownloadViewType(viewType: String) {
|
||||||
|
return App.getInstance().preferences.edit().putString(
|
||||||
|
DEFAULT_DOWNLOAD_VIEW_TYPE,
|
||||||
|
viewType
|
||||||
|
).apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun preferTranscodedDownload(): Boolean {
|
||||||
|
return App.getInstance().preferences.getBoolean(AUDIO_TRANSCODE_DOWNLOAD, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun isServerPrioritizedInTranscodedDownload(): Boolean {
|
||||||
|
return App.getInstance().preferences.getBoolean(AUDIO_TRANSCODE_DOWNLOAD_PRIORITY, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun getBitrateTranscodedDownload(): String {
|
||||||
|
return App.getInstance().preferences.getString(MAX_BITRATE_DOWNLOAD, "0")!!
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun getAudioTranscodeFormatTranscodedDownload(): String {
|
||||||
|
return App.getInstance().preferences.getString(AUDIO_TRANSCODE_FORMAT_DOWNLOAD, "raw")!!
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
package com.cappielloantonio.tempo.util;
|
package com.cappielloantonio.tempo.util;
|
||||||
|
|
||||||
|
import androidx.annotation.OptIn;
|
||||||
import androidx.media3.common.Metadata;
|
import androidx.media3.common.Metadata;
|
||||||
import androidx.media3.common.Tracks;
|
import androidx.media3.common.Tracks;
|
||||||
|
import androidx.media3.common.util.UnstableApi;
|
||||||
import androidx.media3.exoplayer.ExoPlayer;
|
import androidx.media3.exoplayer.ExoPlayer;
|
||||||
|
|
||||||
import com.cappielloantonio.tempo.model.ReplayGain;
|
import com.cappielloantonio.tempo.model.ReplayGain;
|
||||||
@@ -10,6 +12,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@OptIn(markerClass = UnstableApi.class)
|
||||||
public class ReplayGainUtil {
|
public class ReplayGainUtil {
|
||||||
private static final String[] tags = {"REPLAYGAIN_TRACK_GAIN", "REPLAYGAIN_ALBUM_GAIN", "R128_TRACK_GAIN", "R128_ALBUM_GAIN"};
|
private static final String[] tags = {"REPLAYGAIN_TRACK_GAIN", "REPLAYGAIN_ALBUM_GAIN", "R128_TRACK_GAIN", "R128_ALBUM_GAIN"};
|
||||||
|
|
||||||
@@ -23,13 +26,17 @@ public class ReplayGainUtil {
|
|||||||
private static List<Metadata> getMetadata(Tracks tracks) {
|
private static List<Metadata> getMetadata(Tracks tracks) {
|
||||||
List<Metadata> metadata = new ArrayList<>();
|
List<Metadata> metadata = new ArrayList<>();
|
||||||
|
|
||||||
|
if (tracks != null && !tracks.getGroups().isEmpty()) {
|
||||||
for (int i = 0; i < tracks.getGroups().size(); i++) {
|
for (int i = 0; i < tracks.getGroups().size(); i++) {
|
||||||
Tracks.Group group = tracks.getGroups().get(i);
|
Tracks.Group group = tracks.getGroups().get(i);
|
||||||
|
|
||||||
|
if (group != null && group.getMediaTrackGroup() != null) {
|
||||||
for (int j = 0; j < group.getMediaTrackGroup().length; j++) {
|
for (int j = 0; j < group.getMediaTrackGroup().length; j++) {
|
||||||
metadata.add(group.getTrackFormat(j).metadata);
|
metadata.add(group.getTrackFormat(j).metadata);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return metadata;
|
return metadata;
|
||||||
}
|
}
|
||||||
@@ -37,9 +44,13 @@ public class ReplayGainUtil {
|
|||||||
private static List<ReplayGain> getReplayGains(List<Metadata> metadata) {
|
private static List<ReplayGain> getReplayGains(List<Metadata> metadata) {
|
||||||
List<ReplayGain> gains = new ArrayList<>();
|
List<ReplayGain> gains = new ArrayList<>();
|
||||||
|
|
||||||
|
if (metadata != null) {
|
||||||
for (int i = 0; i < metadata.size(); i++) {
|
for (int i = 0; i < metadata.size(); i++) {
|
||||||
for (int j = 0; j < metadata.get(i).length(); j++) {
|
Metadata singleMetadata = metadata.get(i);
|
||||||
Metadata.Entry entry = metadata.get(i).get(j);
|
|
||||||
|
if (singleMetadata != null) {
|
||||||
|
for (int j = 0; j < singleMetadata.length(); j++) {
|
||||||
|
Metadata.Entry entry = singleMetadata.get(j);
|
||||||
|
|
||||||
if (checkReplayGain(entry)) {
|
if (checkReplayGain(entry)) {
|
||||||
ReplayGain replayGain = setReplayGains(entry);
|
ReplayGain replayGain = setReplayGains(entry);
|
||||||
@@ -47,6 +58,8 @@ public class ReplayGainUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return gains;
|
return gains;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,21 @@ import android.content.res.TypedArray;
|
|||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.graphics.drawable.InsetDrawable;
|
import android.graphics.drawable.InsetDrawable;
|
||||||
|
|
||||||
|
import androidx.core.os.LocaleListCompat;
|
||||||
import androidx.recyclerview.widget.DividerItemDecoration;
|
import androidx.recyclerview.widget.DividerItemDecoration;
|
||||||
|
|
||||||
|
import com.cappielloantonio.tempo.R;
|
||||||
|
|
||||||
|
import org.xmlpull.v1.XmlPullParser;
|
||||||
|
import org.xmlpull.v1.XmlPullParserException;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public class UIUtil {
|
public class UIUtil {
|
||||||
public static int getSpanCount(int itemCount, int maxSpan) {
|
public static int getSpanCount(int itemCount, int maxSpan) {
|
||||||
int itemSize = itemCount == 0 ? 1 : itemCount;
|
int itemSize = itemCount == 0 ? 1 : itemCount;
|
||||||
@@ -31,4 +44,44 @@ public class UIUtil {
|
|||||||
|
|
||||||
return itemDecoration;
|
return itemDecoration;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static LocaleListCompat getLocalesFromResources(Context context) {
|
||||||
|
final List<String> tagsList = new ArrayList<>();
|
||||||
|
|
||||||
|
XmlPullParser xpp = context.getResources().getXml(R.xml.locale_config);
|
||||||
|
|
||||||
|
try {
|
||||||
|
while (xpp.getEventType() != XmlPullParser.END_DOCUMENT) {
|
||||||
|
String tagName = xpp.getName();
|
||||||
|
|
||||||
|
if (xpp.getEventType() == XmlPullParser.START_TAG) {
|
||||||
|
if ("locale".equals(tagName) && xpp.getAttributeCount() > 0 && xpp.getAttributeName(0).equals("name")) {
|
||||||
|
tagsList.add(xpp.getAttributeValue(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
xpp.next();
|
||||||
|
}
|
||||||
|
} catch (XmlPullParserException | IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return LocaleListCompat.forLanguageTags(String.join(",", tagsList));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Map<String, String> getLangPreferenceDropdownEntries(Context context) {
|
||||||
|
LocaleListCompat localeList = getLocalesFromResources(context);
|
||||||
|
|
||||||
|
Map<String, String> map = new HashMap<>();
|
||||||
|
|
||||||
|
for (int i = 0; i < localeList.size(); i++) {
|
||||||
|
Locale locale = localeList.get(i);
|
||||||
|
|
||||||
|
if (locale != null) {
|
||||||
|
map.put(Util.toPascalCase(locale.getDisplayName()), locale.toLanguageTag());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
64
app/src/main/java/com/cappielloantonio/tempo/util/Util.java
Normal file
64
app/src/main/java/com/cappielloantonio/tempo/util/Util.java
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
package com.cappielloantonio.tempo.util;
|
||||||
|
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
public class Util {
|
||||||
|
public static <T> Predicate<T> distinctByKey(Function<? super T, Object> keyExtractor) {
|
||||||
|
try {
|
||||||
|
Map<Object, Boolean> uniqueMap = new ConcurrentHashMap<>();
|
||||||
|
return t -> uniqueMap.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
|
||||||
|
} catch (NullPointerException exception) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String toPascalCase(String name) {
|
||||||
|
if (name == null || name.isEmpty()) {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder pascalCase = new StringBuilder();
|
||||||
|
|
||||||
|
char newChar;
|
||||||
|
boolean toUpper = false;
|
||||||
|
char[] charArray = name.toCharArray();
|
||||||
|
|
||||||
|
for (int ctr = 0; ctr <= charArray.length - 1; ctr++) {
|
||||||
|
if (ctr == 0) {
|
||||||
|
newChar = Character.toUpperCase(charArray[ctr]);
|
||||||
|
pascalCase = new StringBuilder(Character.toString(newChar));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (charArray[ctr] == '_') {
|
||||||
|
toUpper = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toUpper) {
|
||||||
|
newChar = Character.toUpperCase(charArray[ctr]);
|
||||||
|
pascalCase.append(newChar);
|
||||||
|
toUpper = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
pascalCase.append(charArray[ctr]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return pascalCase.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String encode(String value) {
|
||||||
|
try {
|
||||||
|
return URLEncoder.encode(value, StandardCharsets.UTF_8.toString());
|
||||||
|
} catch (UnsupportedEncodingException ex) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,11 +7,14 @@ import androidx.lifecycle.AndroidViewModel;
|
|||||||
import androidx.lifecycle.LiveData;
|
import androidx.lifecycle.LiveData;
|
||||||
import androidx.lifecycle.MutableLiveData;
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
|
||||||
|
import com.cappielloantonio.tempo.interfaces.StarCallback;
|
||||||
import com.cappielloantonio.tempo.repository.AlbumRepository;
|
import com.cappielloantonio.tempo.repository.AlbumRepository;
|
||||||
import com.cappielloantonio.tempo.repository.ArtistRepository;
|
import com.cappielloantonio.tempo.repository.ArtistRepository;
|
||||||
|
import com.cappielloantonio.tempo.repository.FavoriteRepository;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
|
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||||
|
import com.cappielloantonio.tempo.util.NetworkUtil;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -19,6 +22,7 @@ import java.util.List;
|
|||||||
public class AlbumBottomSheetViewModel extends AndroidViewModel {
|
public class AlbumBottomSheetViewModel extends AndroidViewModel {
|
||||||
private final AlbumRepository albumRepository;
|
private final AlbumRepository albumRepository;
|
||||||
private final ArtistRepository artistRepository;
|
private final ArtistRepository artistRepository;
|
||||||
|
private final FavoriteRepository favoriteRepository;
|
||||||
|
|
||||||
private AlbumID3 album;
|
private AlbumID3 album;
|
||||||
|
|
||||||
@@ -27,6 +31,7 @@ public class AlbumBottomSheetViewModel extends AndroidViewModel {
|
|||||||
|
|
||||||
albumRepository = new AlbumRepository();
|
albumRepository = new AlbumRepository();
|
||||||
artistRepository = new ArtistRepository();
|
artistRepository = new ArtistRepository();
|
||||||
|
favoriteRepository = new FavoriteRepository();
|
||||||
}
|
}
|
||||||
|
|
||||||
public AlbumID3 getAlbum() {
|
public AlbumID3 getAlbum() {
|
||||||
@@ -47,11 +52,51 @@ public class AlbumBottomSheetViewModel extends AndroidViewModel {
|
|||||||
|
|
||||||
public void setFavorite() {
|
public void setFavorite() {
|
||||||
if (album.getStarred() != null) {
|
if (album.getStarred() != null) {
|
||||||
artistRepository.unstar(album.getId());
|
if (NetworkUtil.isOffline()) {
|
||||||
album.setStarred(null);
|
removeFavoriteOffline();
|
||||||
} else {
|
} else {
|
||||||
artistRepository.star(album.getId());
|
removeFavoriteOnline();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
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());
|
album.setStarred(new Date());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|||||||
@@ -5,20 +5,25 @@ import android.app.Application;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.lifecycle.AndroidViewModel;
|
import androidx.lifecycle.AndroidViewModel;
|
||||||
|
|
||||||
import com.cappielloantonio.tempo.repository.AlbumRepository;
|
import com.cappielloantonio.tempo.interfaces.StarCallback;
|
||||||
|
import com.cappielloantonio.tempo.repository.ArtistRepository;
|
||||||
|
import com.cappielloantonio.tempo.repository.FavoriteRepository;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
||||||
|
import com.cappielloantonio.tempo.util.NetworkUtil;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
public class ArtistBottomSheetViewModel extends AndroidViewModel {
|
public class ArtistBottomSheetViewModel extends AndroidViewModel {
|
||||||
private final AlbumRepository albumRepository;
|
private final ArtistRepository artistRepository;
|
||||||
|
private final FavoriteRepository favoriteRepository;
|
||||||
|
|
||||||
private ArtistID3 artist;
|
private ArtistID3 artist;
|
||||||
|
|
||||||
public ArtistBottomSheetViewModel(@NonNull Application application) {
|
public ArtistBottomSheetViewModel(@NonNull Application application) {
|
||||||
super(application);
|
super(application);
|
||||||
|
|
||||||
albumRepository = new AlbumRepository();
|
artistRepository = new ArtistRepository();
|
||||||
|
favoriteRepository = new FavoriteRepository();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ArtistID3 getArtist() {
|
public ArtistID3 getArtist() {
|
||||||
@@ -31,11 +36,51 @@ public class ArtistBottomSheetViewModel extends AndroidViewModel {
|
|||||||
|
|
||||||
public void setFavorite() {
|
public void setFavorite() {
|
||||||
if (artist.getStarred() != null) {
|
if (artist.getStarred() != null) {
|
||||||
albumRepository.unstar(artist.getId());
|
if (NetworkUtil.isOffline()) {
|
||||||
artist.setStarred(null);
|
removeFavoriteOffline();
|
||||||
} else {
|
} else {
|
||||||
albumRepository.star(artist.getId());
|
removeFavoriteOnline();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
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());
|
artist.setStarred(new Date());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|||||||
@@ -8,27 +8,56 @@ import androidx.lifecycle.LifecycleOwner;
|
|||||||
import androidx.lifecycle.LiveData;
|
import androidx.lifecycle.LiveData;
|
||||||
import androidx.lifecycle.MutableLiveData;
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
|
||||||
|
import com.cappielloantonio.tempo.model.DownloadStack;
|
||||||
import com.cappielloantonio.tempo.repository.DownloadRepository;
|
import com.cappielloantonio.tempo.repository.DownloadRepository;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||||
|
import com.cappielloantonio.tempo.util.Constants;
|
||||||
|
import com.cappielloantonio.tempo.util.Preferences;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class DownloadViewModel extends AndroidViewModel {
|
public class DownloadViewModel extends AndroidViewModel {
|
||||||
private static final String TAG = "HomeViewModel";
|
private static final String TAG = "DownloadViewModel";
|
||||||
|
|
||||||
private final DownloadRepository downloadRepository;
|
private final DownloadRepository downloadRepository;
|
||||||
|
|
||||||
private final MutableLiveData<List<Child>> downloadedTrackSample = new MutableLiveData<>(null);
|
private final MutableLiveData<List<Child>> downloadedTrackSample = new MutableLiveData<>(null);
|
||||||
|
private final MutableLiveData<ArrayList<DownloadStack>> viewStack = new MutableLiveData<>(null);
|
||||||
|
|
||||||
public DownloadViewModel(@NonNull Application application) {
|
public DownloadViewModel(@NonNull Application application) {
|
||||||
super(application);
|
super(application);
|
||||||
|
|
||||||
downloadRepository = new DownloadRepository();
|
downloadRepository = new DownloadRepository();
|
||||||
|
|
||||||
|
initViewStack(new DownloadStack(Preferences.getDefaultDownloadViewType(), null));
|
||||||
}
|
}
|
||||||
|
|
||||||
public LiveData<List<Child>> getDownloadedTracks(LifecycleOwner owner) {
|
public LiveData<List<Child>> getDownloadedTracks(LifecycleOwner owner) {
|
||||||
downloadRepository.getLiveDownload().observe(owner, downloads -> downloadedTrackSample.postValue(downloads.stream().map(download -> (Child) download).collect(Collectors.toList())));
|
downloadRepository.getLiveDownload().observe(owner, downloads -> downloadedTrackSample.postValue(downloads.stream().map(download -> (Child) download).collect(Collectors.toList())));
|
||||||
return downloadedTrackSample;
|
return downloadedTrackSample;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public LiveData<ArrayList<DownloadStack>> getViewStack() {
|
||||||
|
return viewStack;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void initViewStack(DownloadStack level) {
|
||||||
|
ArrayList<DownloadStack> stack = new ArrayList<>();
|
||||||
|
stack.add(level);
|
||||||
|
viewStack.setValue(stack);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void pushViewStack(DownloadStack level) {
|
||||||
|
ArrayList<DownloadStack> stack = viewStack.getValue();
|
||||||
|
stack.add(level);
|
||||||
|
viewStack.setValue(stack);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void popViewStack() {
|
||||||
|
ArrayList<DownloadStack> stack = viewStack.getValue();
|
||||||
|
stack.remove(stack.size() - 1);
|
||||||
|
viewStack.setValue(stack);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,20 +8,24 @@ import androidx.lifecycle.LifecycleOwner;
|
|||||||
import androidx.lifecycle.LiveData;
|
import androidx.lifecycle.LiveData;
|
||||||
import androidx.lifecycle.MutableLiveData;
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
|
||||||
|
import com.cappielloantonio.tempo.interfaces.StarCallback;
|
||||||
import com.cappielloantonio.tempo.model.Chronology;
|
import com.cappielloantonio.tempo.model.Chronology;
|
||||||
|
import com.cappielloantonio.tempo.model.Favorite;
|
||||||
import com.cappielloantonio.tempo.repository.AlbumRepository;
|
import com.cappielloantonio.tempo.repository.AlbumRepository;
|
||||||
import com.cappielloantonio.tempo.repository.ArtistRepository;
|
import com.cappielloantonio.tempo.repository.ArtistRepository;
|
||||||
import com.cappielloantonio.tempo.repository.ChronologyRepository;
|
import com.cappielloantonio.tempo.repository.ChronologyRepository;
|
||||||
import com.cappielloantonio.tempo.repository.PodcastRepository;
|
import com.cappielloantonio.tempo.repository.FavoriteRepository;
|
||||||
import com.cappielloantonio.tempo.repository.SongRepository;
|
import com.cappielloantonio.tempo.repository.SongRepository;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
|
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||||
import com.cappielloantonio.tempo.util.Preferences;
|
import com.cappielloantonio.tempo.util.Preferences;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class HomeViewModel extends AndroidViewModel {
|
public class HomeViewModel extends AndroidViewModel {
|
||||||
@@ -31,6 +35,7 @@ public class HomeViewModel extends AndroidViewModel {
|
|||||||
private final AlbumRepository albumRepository;
|
private final AlbumRepository albumRepository;
|
||||||
private final ArtistRepository artistRepository;
|
private final ArtistRepository artistRepository;
|
||||||
private final ChronologyRepository chronologyRepository;
|
private final ChronologyRepository chronologyRepository;
|
||||||
|
private final FavoriteRepository favoriteRepository;
|
||||||
|
|
||||||
private final MutableLiveData<List<Child>> dicoverSongSample = new MutableLiveData<>(null);
|
private final MutableLiveData<List<Child>> dicoverSongSample = new MutableLiveData<>(null);
|
||||||
private final MutableLiveData<List<AlbumID3>> newReleasedAlbum = new MutableLiveData<>(null);
|
private final MutableLiveData<List<AlbumID3>> newReleasedAlbum = new MutableLiveData<>(null);
|
||||||
@@ -57,6 +62,9 @@ public class HomeViewModel extends AndroidViewModel {
|
|||||||
albumRepository = new AlbumRepository();
|
albumRepository = new AlbumRepository();
|
||||||
artistRepository = new ArtistRepository();
|
artistRepository = new ArtistRepository();
|
||||||
chronologyRepository = new ChronologyRepository();
|
chronologyRepository = new ChronologyRepository();
|
||||||
|
favoriteRepository = new FavoriteRepository();
|
||||||
|
|
||||||
|
setOfflineFavorite();
|
||||||
}
|
}
|
||||||
|
|
||||||
public LiveData<List<Child>> getDiscoverSongSample(LifecycleOwner owner) {
|
public LiveData<List<Child>> getDiscoverSongSample(LifecycleOwner owner) {
|
||||||
@@ -239,4 +247,109 @@ public class HomeViewModel extends AndroidViewModel {
|
|||||||
public void refreshRecentlyPlayedAlbumList(LifecycleOwner owner) {
|
public void refreshRecentlyPlayedAlbumList(LifecycleOwner owner) {
|
||||||
albumRepository.getAlbums("recent", 20, null, null).observe(owner, recentlyPlayedAlbumSample::postValue);
|
albumRepository.getAlbums("recent", 20, null, null).observe(owner, recentlyPlayedAlbumSample::postValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setOfflineFavorite() {
|
||||||
|
ArrayList<Favorite> favorites = getFavorites();
|
||||||
|
ArrayList<Favorite> favoritesToSave = getFavoritesToSave(favorites);
|
||||||
|
ArrayList<Favorite> favoritesToDelete = getFavoritesToDelete(favorites, favoritesToSave);
|
||||||
|
|
||||||
|
manageFavoriteToSave(favoritesToSave);
|
||||||
|
manageFavoriteToDelete(favoritesToDelete);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ArrayList<Favorite> getFavorites() {
|
||||||
|
return new ArrayList<>(favoriteRepository.getFavorites());
|
||||||
|
}
|
||||||
|
|
||||||
|
private ArrayList<Favorite> getFavoritesToSave(ArrayList<Favorite> favorites) {
|
||||||
|
HashMap<String, Favorite> filteredMap = new HashMap<>();
|
||||||
|
|
||||||
|
for (Favorite favorite : favorites) {
|
||||||
|
String key = favorite.toString();
|
||||||
|
|
||||||
|
if (!filteredMap.containsKey(key) || favorite.getTimestamp() > filteredMap.get(key).getTimestamp()) {
|
||||||
|
filteredMap.put(key, favorite);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ArrayList<>(filteredMap.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
private ArrayList<Favorite> getFavoritesToDelete(ArrayList<Favorite> favorites, ArrayList<Favorite> favoritesToSave) {
|
||||||
|
ArrayList<Favorite> favoritesToDelete = new ArrayList<>();
|
||||||
|
|
||||||
|
for (Favorite favorite : favorites) {
|
||||||
|
if (!favoritesToSave.contains(favorite)) {
|
||||||
|
favoritesToDelete.add(favorite);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return favoritesToDelete;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void manageFavoriteToSave(ArrayList<Favorite> favoritesToSave) {
|
||||||
|
for (Favorite favorite : favoritesToSave) {
|
||||||
|
if (favorite.getToStar()) {
|
||||||
|
favoriteToStar(favorite);
|
||||||
|
} else {
|
||||||
|
favoriteToUnstar(favorite);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void manageFavoriteToDelete(ArrayList<Favorite> favoritesToDelete) {
|
||||||
|
for (Favorite favorite : favoritesToDelete) {
|
||||||
|
favoriteRepository.delete(favorite);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void favoriteToStar(Favorite favorite) {
|
||||||
|
if (favorite.getSongId() != null) {
|
||||||
|
favoriteRepository.star(favorite.getSongId(), null, null, new StarCallback() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess() {
|
||||||
|
favoriteRepository.delete(favorite);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (favorite.getAlbumId() != null) {
|
||||||
|
favoriteRepository.star(null, favorite.getAlbumId(), null, new StarCallback() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess() {
|
||||||
|
favoriteRepository.delete(favorite);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (favorite.getArtistId() != null) {
|
||||||
|
favoriteRepository.star(null, null, favorite.getArtistId(), new StarCallback() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess() {
|
||||||
|
favoriteRepository.delete(favorite);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void favoriteToUnstar(Favorite favorite) {
|
||||||
|
if (favorite.getSongId() != null) {
|
||||||
|
favoriteRepository.unstar(favorite.getSongId(), null, null, new StarCallback() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess() {
|
||||||
|
favoriteRepository.delete(favorite);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (favorite.getAlbumId() != null) {
|
||||||
|
favoriteRepository.unstar(null, favorite.getAlbumId(), null, new StarCallback() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess() {
|
||||||
|
favoriteRepository.delete(favorite);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (favorite.getArtistId() != null) {
|
||||||
|
favoriteRepository.unstar(null, null, favorite.getArtistId(), new StarCallback() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess() {
|
||||||
|
favoriteRepository.delete(favorite);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,8 +23,8 @@ public class IndexViewModel extends AndroidViewModel {
|
|||||||
directoryRepository = new DirectoryRepository();
|
directoryRepository = new DirectoryRepository();
|
||||||
}
|
}
|
||||||
|
|
||||||
public MutableLiveData<Indexes> getIndexes() {
|
public MutableLiveData<Indexes> getIndexes(String musicFolderId) {
|
||||||
return directoryRepository.getIndexes(null, null);
|
return directoryRepository.getIndexes(musicFolderId, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getMusicFolderName() {
|
public String getMusicFolderName() {
|
||||||
|
|||||||
@@ -11,9 +11,11 @@ import androidx.lifecycle.LiveData;
|
|||||||
import androidx.lifecycle.MutableLiveData;
|
import androidx.lifecycle.MutableLiveData;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
|
||||||
|
import com.cappielloantonio.tempo.interfaces.StarCallback;
|
||||||
import com.cappielloantonio.tempo.model.Download;
|
import com.cappielloantonio.tempo.model.Download;
|
||||||
import com.cappielloantonio.tempo.model.Queue;
|
import com.cappielloantonio.tempo.model.Queue;
|
||||||
import com.cappielloantonio.tempo.repository.ArtistRepository;
|
import com.cappielloantonio.tempo.repository.ArtistRepository;
|
||||||
|
import com.cappielloantonio.tempo.repository.FavoriteRepository;
|
||||||
import com.cappielloantonio.tempo.repository.QueueRepository;
|
import com.cappielloantonio.tempo.repository.QueueRepository;
|
||||||
import com.cappielloantonio.tempo.repository.SongRepository;
|
import com.cappielloantonio.tempo.repository.SongRepository;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
||||||
@@ -22,6 +24,7 @@ import com.cappielloantonio.tempo.subsonic.models.PlayQueue;
|
|||||||
import com.cappielloantonio.tempo.util.Constants;
|
import com.cappielloantonio.tempo.util.Constants;
|
||||||
import com.cappielloantonio.tempo.util.DownloadUtil;
|
import com.cappielloantonio.tempo.util.DownloadUtil;
|
||||||
import com.cappielloantonio.tempo.util.MappingUtil;
|
import com.cappielloantonio.tempo.util.MappingUtil;
|
||||||
|
import com.cappielloantonio.tempo.util.NetworkUtil;
|
||||||
import com.cappielloantonio.tempo.util.Preferences;
|
import com.cappielloantonio.tempo.util.Preferences;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -36,6 +39,7 @@ public class PlayerBottomSheetViewModel extends AndroidViewModel {
|
|||||||
private final SongRepository songRepository;
|
private final SongRepository songRepository;
|
||||||
private final ArtistRepository artistRepository;
|
private final ArtistRepository artistRepository;
|
||||||
private final QueueRepository queueRepository;
|
private final QueueRepository queueRepository;
|
||||||
|
private final FavoriteRepository favoriteRepository;
|
||||||
|
|
||||||
private final MutableLiveData<String> lyricsLiveData = new MutableLiveData<>(null);
|
private final MutableLiveData<String> lyricsLiveData = new MutableLiveData<>(null);
|
||||||
|
|
||||||
@@ -50,6 +54,7 @@ public class PlayerBottomSheetViewModel extends AndroidViewModel {
|
|||||||
songRepository = new SongRepository();
|
songRepository = new SongRepository();
|
||||||
artistRepository = new ArtistRepository();
|
artistRepository = new ArtistRepository();
|
||||||
queueRepository = new QueueRepository();
|
queueRepository = new QueueRepository();
|
||||||
|
favoriteRepository = new FavoriteRepository();
|
||||||
}
|
}
|
||||||
|
|
||||||
public LiveData<List<Queue>> getQueueSong() {
|
public LiveData<List<Queue>> getQueueSong() {
|
||||||
@@ -59,10 +64,52 @@ public class PlayerBottomSheetViewModel extends AndroidViewModel {
|
|||||||
public void setFavorite(Context context, Child media) {
|
public void setFavorite(Context context, Child media) {
|
||||||
if (media != null) {
|
if (media != null) {
|
||||||
if (media.getStarred() != null) {
|
if (media.getStarred() != null) {
|
||||||
songRepository.unstar(media.getId());
|
if (NetworkUtil.isOffline()) {
|
||||||
media.setStarred(null);
|
removeFavoriteOffline(media);
|
||||||
} else {
|
} else {
|
||||||
songRepository.star(media.getId());
|
removeFavoriteOnline(media);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
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());
|
media.setStarred(new Date());
|
||||||
|
|
||||||
if (Preferences.isStarredSyncEnabled()) {
|
if (Preferences.isStarredSyncEnabled()) {
|
||||||
@@ -72,8 +119,6 @@ public class PlayerBottomSheetViewModel extends AndroidViewModel {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public LiveData<String> getLiveLyrics() {
|
public LiveData<String> getLiveLyrics() {
|
||||||
return lyricsLiveData;
|
return lyricsLiveData;
|
||||||
|
|||||||
@@ -37,8 +37,7 @@ public class PlaylistEditorViewModel extends AndroidViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void updatePlaylist(String name) {
|
public void updatePlaylist(String name) {
|
||||||
playlistRepository.deletePlaylist(toEdit.getId());
|
playlistRepository.updatePlaylist(toEdit.getId(), name, getPlaylistSongIds());
|
||||||
playlistRepository.createPlaylist(toEdit.getId(), name, getPlaylistSongIds());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void deletePlaylist() {
|
public void deletePlaylist() {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import androidx.lifecycle.LiveData;
|
|||||||
|
|
||||||
import com.cappielloantonio.tempo.repository.PodcastRepository;
|
import com.cappielloantonio.tempo.repository.PodcastRepository;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.PodcastChannel;
|
import com.cappielloantonio.tempo.subsonic.models.PodcastChannel;
|
||||||
|
import com.cappielloantonio.tempo.subsonic.models.PodcastEpisode;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -33,4 +34,8 @@ public class PodcastChannelPageViewModel extends AndroidViewModel {
|
|||||||
public void setPodcastChannel(PodcastChannel podcastChannel) {
|
public void setPodcastChannel(PodcastChannel podcastChannel) {
|
||||||
this.podcastChannel = podcastChannel;
|
this.podcastChannel = podcastChannel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void requestPodcastEpisodeDownload(PodcastEpisode podcastEpisode) {
|
||||||
|
podcastRepository.downloadPodcastEpisode(podcastEpisode.getId());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,15 +10,18 @@ import androidx.lifecycle.LiveData;
|
|||||||
import androidx.lifecycle.MutableLiveData;
|
import androidx.lifecycle.MutableLiveData;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
|
||||||
|
import com.cappielloantonio.tempo.interfaces.StarCallback;
|
||||||
import com.cappielloantonio.tempo.model.Download;
|
import com.cappielloantonio.tempo.model.Download;
|
||||||
import com.cappielloantonio.tempo.repository.AlbumRepository;
|
import com.cappielloantonio.tempo.repository.AlbumRepository;
|
||||||
import com.cappielloantonio.tempo.repository.ArtistRepository;
|
import com.cappielloantonio.tempo.repository.ArtistRepository;
|
||||||
|
import com.cappielloantonio.tempo.repository.FavoriteRepository;
|
||||||
import com.cappielloantonio.tempo.repository.SongRepository;
|
import com.cappielloantonio.tempo.repository.SongRepository;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
|
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||||
import com.cappielloantonio.tempo.util.DownloadUtil;
|
import com.cappielloantonio.tempo.util.DownloadUtil;
|
||||||
import com.cappielloantonio.tempo.util.MappingUtil;
|
import com.cappielloantonio.tempo.util.MappingUtil;
|
||||||
|
import com.cappielloantonio.tempo.util.NetworkUtil;
|
||||||
import com.cappielloantonio.tempo.util.Preferences;
|
import com.cappielloantonio.tempo.util.Preferences;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -30,6 +33,7 @@ public class SongBottomSheetViewModel extends AndroidViewModel {
|
|||||||
private final SongRepository songRepository;
|
private final SongRepository songRepository;
|
||||||
private final AlbumRepository albumRepository;
|
private final AlbumRepository albumRepository;
|
||||||
private final ArtistRepository artistRepository;
|
private final ArtistRepository artistRepository;
|
||||||
|
private final FavoriteRepository favoriteRepository;
|
||||||
|
|
||||||
private Child song;
|
private Child song;
|
||||||
|
|
||||||
@@ -41,6 +45,7 @@ public class SongBottomSheetViewModel extends AndroidViewModel {
|
|||||||
songRepository = new SongRepository();
|
songRepository = new SongRepository();
|
||||||
albumRepository = new AlbumRepository();
|
albumRepository = new AlbumRepository();
|
||||||
artistRepository = new ArtistRepository();
|
artistRepository = new ArtistRepository();
|
||||||
|
favoriteRepository = new FavoriteRepository();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Child getSong() {
|
public Child getSong() {
|
||||||
@@ -53,20 +58,60 @@ public class SongBottomSheetViewModel extends AndroidViewModel {
|
|||||||
|
|
||||||
public void setFavorite(Context context) {
|
public void setFavorite(Context context) {
|
||||||
if (song.getStarred() != null) {
|
if (song.getStarred() != null) {
|
||||||
songRepository.unstar(song.getId());
|
if (NetworkUtil.isOffline()) {
|
||||||
song.setStarred(null);
|
removeFavoriteOffline(song);
|
||||||
} else {
|
} else {
|
||||||
songRepository.star(song.getId());
|
removeFavoriteOnline(song);
|
||||||
song.setStarred(new Date());
|
}
|
||||||
|
} 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()) {
|
if (Preferences.isStarredSyncEnabled()) {
|
||||||
DownloadUtil.getDownloadTracker(context).download(
|
DownloadUtil.getDownloadTracker(context).download(
|
||||||
MappingUtil.mapDownload(song),
|
MappingUtil.mapDownload(media),
|
||||||
new Download(song)
|
new Download(media)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public LiveData<AlbumID3> getAlbum() {
|
public LiveData<AlbumID3> getAlbum() {
|
||||||
return albumRepository.getAlbum(song.getAlbumId());
|
return albumRepository.getAlbum(song.getAlbumId());
|
||||||
|
|||||||
@@ -71,11 +71,17 @@ public class SongListPageViewModel extends AndroidViewModel {
|
|||||||
public void getSongsByPage(LifecycleOwner owner) {
|
public void getSongsByPage(LifecycleOwner owner) {
|
||||||
switch (title) {
|
switch (title) {
|
||||||
case Constants.MEDIA_BY_GENRE:
|
case Constants.MEDIA_BY_GENRE:
|
||||||
int page = (songList.getValue() != null ? songList.getValue().size() : 0) / 100;
|
int songCount = songList.getValue() != null ? songList.getValue().size() : 0;
|
||||||
|
|
||||||
|
if (songCount > 0 && songCount % 100 != 0) return;
|
||||||
|
|
||||||
|
int page = songCount / 100;
|
||||||
songRepository.getSongsByGenre(genre.getGenre(), page).observe(owner, children -> {
|
songRepository.getSongsByGenre(genre.getGenre(), page).observe(owner, children -> {
|
||||||
|
if (children != null && !children.isEmpty()) {
|
||||||
List<Child> currentMedia = songList.getValue();
|
List<Child> currentMedia = songList.getValue();
|
||||||
currentMedia.addAll(children);
|
currentMedia.addAll(children);
|
||||||
songList.setValue(currentMedia);
|
songList.setValue(currentMedia);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case Constants.MEDIA_BY_ARTIST:
|
case Constants.MEDIA_BY_ARTIST:
|
||||||
|
|||||||
15
app/src/main/res/drawable/fast_scrollbar_bubble.xml
Normal file
15
app/src/main/res/drawable/fast_scrollbar_bubble.xml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
|
||||||
|
<corners
|
||||||
|
android:bottomLeftRadius="44dp"
|
||||||
|
android:bottomRightRadius="0px"
|
||||||
|
android:topLeftRadius="44dp"
|
||||||
|
android:topRightRadius="44dp" />
|
||||||
|
|
||||||
|
<solid android:color="?attr/colorPrimary" />
|
||||||
|
<size
|
||||||
|
android:width="88dp"
|
||||||
|
android:height="88dp" />
|
||||||
|
</shape>
|
||||||
17
app/src/main/res/drawable/fast_scrollbar_handle.xml
Normal file
17
app/src/main/res/drawable/fast_scrollbar_handle.xml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:state_selected="true">
|
||||||
|
<shape android:shape="rectangle">
|
||||||
|
<corners android:radius="2dp" />
|
||||||
|
<solid android:color="?attr/colorPrimary" />
|
||||||
|
<size android:width="4dp" android:height="32dp" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<shape android:shape="rectangle">
|
||||||
|
<corners android:radius="2dp" />
|
||||||
|
<solid android:color="?attr/colorPrimaryContainer" />
|
||||||
|
<size android:width="4dp" android:height="32dp" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</selector>
|
||||||
9
app/src/main/res/drawable/ic_info_stream.xml
Normal file
9
app/src/main/res/drawable/ic_info_stream.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="M440,680L520,680L520,440L440,440L440,680ZM480,360Q497,360 508.5,348.5Q520,337 520,320Q520,303 508.5,291.5Q497,280 480,280Q463,280 451.5,291.5Q440,303 440,320Q440,337 451.5,348.5Q463,360 480,360ZM480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880ZM480,800Q614,800 707,707Q800,614 800,480Q800,346 707,253Q614,160 480,160Q346,160 253,253Q160,346 160,480Q160,614 253,707Q346,800 480,800ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Z"/>
|
||||||
|
</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>
|
||||||
10
app/src/main/res/drawable/ic_queue.xml
Normal file
10
app/src/main/res/drawable/ic_queue.xml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="20dp"
|
||||||
|
android:height="20dp"
|
||||||
|
android:viewportWidth="960"
|
||||||
|
android:viewportHeight="960"
|
||||||
|
android:autoMirrored="true">
|
||||||
|
<path
|
||||||
|
android:fillColor="@color/titleTextColor"
|
||||||
|
android:pathData="M624,720Q574,720 539,685Q504,650 504,600Q504,550 539,515Q574,480 624,480Q636,480 648.5,482.5Q661,485 672,490L672,192L864,192L864,264L744,264L744,600Q744,650 709,685Q674,720 624,720ZM144,552L144,480L432,480L432,552L144,552ZM144,408L144,336L576,336L576,408L144,408ZM144,264L144,192L576,192L576,264L144,264Z"/>
|
||||||
|
</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>
|
||||||
9
app/src/main/res/drawable/ic_tap.xml
Normal file
9
app/src/main/res/drawable/ic_tap.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="M419,880Q391,880 366.5,868Q342,856 325,834L107,557L126,537Q146,516 174,512Q202,508 226,523L300,568L300,240Q300,223 311.5,211.5Q323,200 340,200Q357,200 369,211.5Q381,223 381,240L381,712L284,652L388,785Q394,792 402,796Q410,800 419,800L640,800Q673,800 696.5,776.5Q720,753 720,720L720,560Q720,543 708.5,531.5Q697,520 680,520L461,520L461,440L680,440Q730,440 765,475Q800,510 800,560L800,720Q800,786 753,833Q706,880 640,880L419,880ZM167,340Q154,318 147,292.5Q140,267 140,240Q140,157 198.5,98.5Q257,40 340,40Q423,40 481.5,98.5Q540,157 540,240Q540,267 533,292.5Q526,318 513,340L444,300Q452,286 456,271.5Q460,257 460,240Q460,190 425,155Q390,120 340,120Q290,120 255,155Q220,190 220,240Q220,257 224,271.5Q228,286 236,300L167,340ZM502,620L502,620L502,620L502,620Q502,620 502,620Q502,620 502,620L502,620Q502,620 502,620Q502,620 502,620L502,620Q502,620 502,620Q502,620 502,620L502,620L502,620L502,620Z"/>
|
||||||
|
</vector>
|
||||||
15
app/src/main/res/layout/dialog_delete_download_storage.xml
Normal file
15
app/src/main/res/layout/dialog_delete_download_storage.xml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<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: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/delete_download_storage_dialog_summary" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
23
app/src/main/res/layout/dialog_download_storage.xml
Normal file
23
app/src/main/res/layout/dialog_download_storage.xml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<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: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/download_storage_dialog_summary" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="24dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginEnd="24dp"
|
||||||
|
android:layout_marginBottom="4dp"
|
||||||
|
android:text="@string/download_storage_dialog_sub_summary" />
|
||||||
|
</LinearLayout>
|
||||||
447
app/src/main/res/layout/dialog_track_info.xml
Normal file
447
app/src/main/res/layout/dialog_track_info.xml
Normal file
@@ -0,0 +1,447 @@
|
|||||||
|
<androidx.core.widget.NestedScrollView 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">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="24dp"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="24dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/track_cover_info_image_view"
|
||||||
|
android:layout_width="52dp"
|
||||||
|
android:layout_height="52dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/trak_title_info_text_view"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/trak_title_info_text_view"
|
||||||
|
style="@style/LabelMedium"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/label_placeholder"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/trak_artist_info_text_view"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/track_cover_info_image_view"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/track_cover_info_image_view"
|
||||||
|
app:layout_constraintVertical_chainStyle="packed" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/trak_artist_info_text_view"
|
||||||
|
style="@style/LabelSmall"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/label_placeholder"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/track_cover_info_image_view"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="@+id/trak_title_info_text_view"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/trak_title_info_text_view" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/trak_transcoding_info_text_view"
|
||||||
|
style="@style/TitleMedium"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/title_info_sector"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/title_key_sector"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="4"
|
||||||
|
android:paddingEnd="8dp"
|
||||||
|
android:text="@string/track_info_title" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/title_value_sector"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="7"
|
||||||
|
android:text="@string/label_placeholder" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
style="@style/Divider"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_marginVertical="8dp" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/album_info_sector"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/album_key_sector"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="4"
|
||||||
|
android:paddingEnd="8dp"
|
||||||
|
android:text="@string/track_info_album" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/album_value_sector"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="7"
|
||||||
|
android:text="@string/label_placeholder" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
style="@style/Divider"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_marginVertical="8dp" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/artist_info_sector"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/artist_key_sector"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="4"
|
||||||
|
android:paddingEnd="8dp"
|
||||||
|
android:text="@string/track_info_artist" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/artist_value_sector"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="7"
|
||||||
|
android:text="@string/label_placeholder" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
style="@style/Divider"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_marginVertical="8dp" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/track_number_info_sector"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/track_number_key_sector"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="4"
|
||||||
|
android:paddingEnd="8dp"
|
||||||
|
android:text="@string/track_info_track_number" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/track_number_value_sector"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="7"
|
||||||
|
android:text="@string/label_placeholder" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
style="@style/Divider"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_marginVertical="8dp" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/year_info_sector"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/year_key_sector"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="4"
|
||||||
|
android:paddingEnd="8dp"
|
||||||
|
android:text="@string/track_info_year" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/year_value_sector"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="7"
|
||||||
|
android:text="@string/label_placeholder" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
style="@style/Divider"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_marginVertical="8dp" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/genre_info_sector"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/genre_key_sector"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="4"
|
||||||
|
android:paddingEnd="8dp"
|
||||||
|
android:text="@string/track_info_genre" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/genre_value_sector"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="7"
|
||||||
|
android:text="@string/label_placeholder" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
style="@style/Divider"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_marginVertical="8dp" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/size_info_sector"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/size_key_sector"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="4"
|
||||||
|
android:paddingEnd="8dp"
|
||||||
|
android:text="@string/track_info_size" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/size_value_sector"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="7"
|
||||||
|
android:text="@string/label_placeholder" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
style="@style/Divider"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_marginVertical="8dp" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/content_type_info_sector"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/content_type_key_sector"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="4"
|
||||||
|
android:paddingEnd="8dp"
|
||||||
|
android:text="@string/track_info_content_type" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/content_type_value_sector"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="7"
|
||||||
|
android:text="@string/label_placeholder" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
style="@style/Divider"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_marginVertical="8dp" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/suffix_info_sector"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/suffix_key_sector"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="4"
|
||||||
|
android:paddingEnd="8dp"
|
||||||
|
android:text="@string/track_info_suffix" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/suffix_value_sector"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="7"
|
||||||
|
android:text="@string/label_placeholder" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
style="@style/Divider"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_marginVertical="8dp" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/transcoded_content_type_info_sector"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/transcoded_content_type_key_sector"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="4"
|
||||||
|
android:paddingEnd="8dp"
|
||||||
|
android:text="@string/track_info_transcoded_content_type" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/transcoded_content_type_value_sector"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="7"
|
||||||
|
android:text="@string/label_placeholder" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
style="@style/Divider"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_marginVertical="8dp" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/transcoded_suffix_info_sector"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/transcoded_suffix_key_sector"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="4"
|
||||||
|
android:paddingEnd="8dp"
|
||||||
|
android:text="@string/track_info_transcoded_suffix" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/transcoded_suffix_value_sector"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="7"
|
||||||
|
android:text="@string/label_placeholder" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
style="@style/Divider"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_marginVertical="8dp" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/duration_info_sector"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/duration_key_sector"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="4"
|
||||||
|
android:paddingEnd="8dp"
|
||||||
|
android:text="@string/track_info_duration" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/duration_value_sector"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="7"
|
||||||
|
android:text="@string/label_placeholder" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
style="@style/Divider"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_marginVertical="8dp" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/bitrate_info_sector"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/bitrate_key_sector"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="4"
|
||||||
|
android:paddingEnd="8dp"
|
||||||
|
android:text="@string/track_info_bitrate" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/bitrate_value_sector"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="7"
|
||||||
|
android:text="@string/label_placeholder" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
style="@style/Divider"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_marginVertical="8dp" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/path_info_sector"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/path_key_sector"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="4"
|
||||||
|
android:paddingEnd="8dp"
|
||||||
|
android:text="@string/track_info_path" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/path_value_sector"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="7"
|
||||||
|
android:text="@string/label_placeholder" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
style="@style/Divider"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_marginVertical="8dp" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/disc_number_info_sector"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/disc_number_key_sector"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="4"
|
||||||
|
android:paddingEnd="8dp"
|
||||||
|
android:text="@string/track_info_disc_number" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/disc_number_value_sector"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="7"
|
||||||
|
android:text="@string/label_placeholder" />
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.core.widget.NestedScrollView>
|
||||||
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user