mirror of
https://github.com/CappielloAntonio/tempo.git
synced 2026-01-30 22:32:07 +00:00
Compare commits
173 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
73b368d202 | ||
|
|
f293a0116b | ||
|
|
ff5bec30c0 | ||
|
|
5c66bed477 | ||
|
|
d2068106e4 | ||
|
|
b7b02854d5 | ||
|
|
0edafc2d8e | ||
|
|
279302737d | ||
|
|
634de67d74 | ||
|
|
cd44368d66 | ||
|
|
ae8aa56602 | ||
|
|
34d6692dae | ||
|
|
8d2f0edbab | ||
|
|
d4d7aaba2b | ||
|
|
d690df86d8 | ||
|
|
e8f3cdbb48 | ||
|
|
33aa38e885 | ||
|
|
2faba71df0 | ||
|
|
1d3a32be5d | ||
|
|
5b8e7d1404 | ||
|
|
85a5d01e72 | ||
|
|
c7b17f2214 | ||
|
|
d8c8a783de | ||
|
|
293b0f71c8 | ||
|
|
e6e0e399e0 | ||
|
|
3223de5d03 | ||
|
|
c6d08d6a3f | ||
|
|
375501f282 | ||
|
|
0a62f0d81e | ||
|
|
112e468c7d | ||
|
|
0ccc601f1b | ||
|
|
ae82bcd7bf | ||
|
|
68512b7e12 | ||
|
|
d6cc4fc028 | ||
|
|
e8c7c065e2 | ||
|
|
427394a105 | ||
|
|
75fc2cbafa | ||
|
|
b9a3393b39 | ||
|
|
ed60677608 | ||
|
|
1a407341ec | ||
|
|
567350771c | ||
|
|
47bef77723 | ||
|
|
12f09b2201 | ||
|
|
77d0b4182e | ||
|
|
caf64e0fa4 | ||
|
|
ac90b89099 | ||
|
|
4523bb8e49 | ||
|
|
06f4898892 | ||
|
|
5bbab10485 | ||
|
|
8fe66d058e | ||
|
|
6a893ac424 | ||
|
|
f184ace301 | ||
|
|
121c2b33da | ||
|
|
215f1021d6 | ||
|
|
ce6159ad93 | ||
|
|
746ea93dbe | ||
|
|
612c05fabc | ||
|
|
dfc246d079 | ||
|
|
0a5983e32b | ||
|
|
9fde629233 | ||
|
|
e089c5e466 | ||
|
|
94b0572958 | ||
|
|
133b5e4794 | ||
|
|
6b0ba573de | ||
|
|
6ae7fc2172 | ||
|
|
9cabfb0e2c | ||
|
|
17f05cb3d8 | ||
|
|
f2839f4ff9 | ||
|
|
9aadcb91fb | ||
|
|
5d4dfe1ac7 | ||
|
|
d3d76dd435 | ||
|
|
0c3086d68b | ||
|
|
9b3b9771e6 | ||
|
|
a92403bda4 | ||
|
|
67905affd3 | ||
|
|
c20cadff94 | ||
|
|
0cc45b2b12 | ||
|
|
b6063f6b20 | ||
|
|
678c61e569 | ||
|
|
e88d6d7844 | ||
|
|
bae43cbfe9 | ||
|
|
68c77cefff | ||
|
|
75980ee18b | ||
|
|
ed30198c8d | ||
|
|
185a671f82 | ||
|
|
42905819a7 | ||
|
|
93d6faa392 | ||
|
|
64337775d6 | ||
|
|
d690cf37fb | ||
|
|
32c8ef3b25 | ||
|
|
607c68967d | ||
|
|
7fb9f63d1f | ||
|
|
a04a2072f7 | ||
|
|
72bd71dea2 | ||
|
|
f87328f874 | ||
|
|
4d9b71d48e | ||
|
|
06f8bc771a | ||
|
|
24747207b8 | ||
|
|
e762c91ff3 | ||
|
|
0fe4636fe1 | ||
|
|
8ab011781e | ||
|
|
7f820bd5a6 | ||
|
|
26b8f3f65c | ||
|
|
f172a00fb7 | ||
|
|
077d22167c | ||
|
|
ebd1582d1c | ||
|
|
5e486d4794 | ||
|
|
537d0c6d8f | ||
|
|
4fc75f5c46 | ||
|
|
baf1bc48b0 | ||
|
|
f26a123646 | ||
|
|
7160e3f4b9 | ||
|
|
24b3161a8e | ||
|
|
383fbd1c49 | ||
|
|
c0f5abdfae | ||
|
|
a50e50d797 | ||
|
|
1f9dac9e3a | ||
|
|
f57ac6d624 | ||
|
|
0b96fe5605 | ||
|
|
16561be854 | ||
|
|
570ad1c984 | ||
|
|
736bcdb994 | ||
|
|
e71472f498 | ||
|
|
fb328f26b8 | ||
|
|
c26aba8b2d | ||
|
|
a17de1de8d | ||
|
|
7ea159cb10 | ||
|
|
8ed98bbedc | ||
|
|
43a1b5b93a | ||
|
|
977754f02c | ||
|
|
10aae5fa15 | ||
|
|
dee60e5e8f | ||
|
|
0c05b77849 | ||
|
|
223954e9ca | ||
|
|
fdc8c50d25 | ||
|
|
aa7872d030 | ||
|
|
9ed7744421 | ||
|
|
c19db362d9 | ||
|
|
6a505eea4e | ||
|
|
99be2764d0 | ||
|
|
b656ad9e7f | ||
|
|
75e5756d7e | ||
|
|
13d8bbf877 | ||
|
|
f43d7fb394 | ||
|
|
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 |
35
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
35
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: "[BUG] - "
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## Describe the bug
|
||||
<!-- A clear and concise description of what the bug is. -->
|
||||
|
||||
## Expected behavior
|
||||
<!-- A clear and concise description of what you expected to happen. -->
|
||||
|
||||
## To Reproduce
|
||||
<!--
|
||||
Outline the steps required to reproduce the bug, including any specific actions, inputs, or conditions:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
-->
|
||||
|
||||
## Environment
|
||||
- Android device: [Device Model]
|
||||
- Android OS version: [Android Version]
|
||||
- App version: [App Version]
|
||||
- Other relevant details: [e.g., specific network conditions, external dependencies]
|
||||
|
||||
## Logs or Screenshots
|
||||
<!-- If applicable, add screenshots to help explain your problem. -->
|
||||
|
||||
## Additional context
|
||||
<!-- Add any other context about the problem here. -->
|
||||
41
.github/ISSUE_TEMPLATE/crash-report.md
vendored
Normal file
41
.github/ISSUE_TEMPLATE/crash-report.md
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
---
|
||||
name: Crash report
|
||||
about: Tell us how to replicate the crash
|
||||
title: "[CRASH] - "
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## Description
|
||||
<!-- Provide a clear and concise description of the crash you encountered. -->
|
||||
|
||||
## Steps to Reproduce
|
||||
<!--
|
||||
Please provide the steps to reproduce the crash:
|
||||
1. Go to '...'
|
||||
2. Click on '...'
|
||||
3. Scroll down to '...'
|
||||
4. See the crash
|
||||
-->
|
||||
|
||||
## Environment
|
||||
- Android device: [Device Model]
|
||||
- Android OS version: [Android Version]
|
||||
- App version: [App Version]
|
||||
- Other relevant details: [e.g., specific network conditions, external dependencies]
|
||||
|
||||
## Crash Logs/Stack trace
|
||||
<!-- If available, please provide the crash log or stack trace related to the crash. Include it inside a code block (surround with triple backticks ```). Please use the unsigned apk (app-tempo-debug.apk), as the logs would be illegible and therefore useless for this purpose. -->
|
||||
|
||||
## Screenshots
|
||||
<!-- If applicable, add screenshots to help explain the problem. -->
|
||||
|
||||
## Additional Context
|
||||
<!-- Add any other context about the problem here. -->
|
||||
|
||||
## Reproducibility
|
||||
<!-- Mention the frequency of the crash occurrence (e.g., always, sometimes, occasionally). -->
|
||||
|
||||
## Additional Notes
|
||||
<!-- Include any other notes or details that could be helpful for troubleshooting the crash. -->
|
||||
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: "[Feature Request] - "
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
<!-- Provide a concise summary of the feature you are requesting. -->
|
||||
|
||||
## Description
|
||||
<!-- Please describe in detail the feature you would like to see implemented. -->
|
||||
|
||||
## Use Case
|
||||
<!-- Explain why this feature is important and how it would improve the user experience. -->
|
||||
|
||||
## Additional context
|
||||
<!-- Include any additional information, screenshots, or examples that could be helpful in understanding or implementing the feature. -->
|
||||
50
.github/workflows/github_release.yml
vendored
50
.github/workflows/github_release.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Setup JDK 17
|
||||
uses: actions/setup-java@v2
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: '17'
|
||||
@@ -28,6 +28,13 @@ jobs:
|
||||
- name: Make gradlew executable
|
||||
run: chmod +x ./gradlew
|
||||
|
||||
- name: Setup build tool version variable
|
||||
shell: bash
|
||||
run: |
|
||||
BUILD_TOOL_VERSION=$(ls /usr/local/lib/android/sdk/build-tools/ | tail -n 1)
|
||||
echo "BUILD_TOOL_VERSION=$BUILD_TOOL_VERSION" >> $GITHUB_ENV
|
||||
echo Last build tool version is: $BUILD_TOOL_VERSION
|
||||
|
||||
- name: Build APK
|
||||
id: build
|
||||
run: bash ./gradlew assembleTempoRelease
|
||||
@@ -41,6 +48,8 @@ jobs:
|
||||
alias: ${{ secrets.KEY_ALIAS_GITHUB }}
|
||||
keyStorePassword: ${{ secrets.KEYSTORE_PASSWORD }}
|
||||
keyPassword: ${{ secrets.KEY_PASSWORD_GITHUB }}
|
||||
env:
|
||||
BUILD_TOOLS_VERSION: ${{ env.BUILD_TOOL_VERSION }}
|
||||
|
||||
- name: Make artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
@@ -48,34 +57,6 @@ jobs:
|
||||
name: app-release-signed
|
||||
path: ${{steps.sign_apk.outputs.signedReleaseFile}}
|
||||
|
||||
# - name: Build AAB
|
||||
# run: bash ./gradlew bundleRelease
|
||||
|
||||
# - name: Sign AAB
|
||||
# id: sign_aab
|
||||
# uses: r0adkll/sign-android-release@v1
|
||||
# with:
|
||||
# releaseDirectory: app/build/outputs/bundle/release
|
||||
# signingKeyBase64: ${{ secrets.KEYSTORE_BASE64 }}
|
||||
# alias: ${{ secrets.KEY_ALIAS_GITHUB }}
|
||||
# keyStorePassword: ${{ secrets.KEYSTORE_PASSWORD }}
|
||||
# keyPassword: ${{ secrets.KEY_PASSWORD_GITHUB }}
|
||||
|
||||
# - name: Make artifact
|
||||
# uses: actions/upload-artifact@v2
|
||||
# with:
|
||||
# name: app-release-signed
|
||||
# path: ${{steps.sign_aab.outputs.signedReleaseFile}}
|
||||
|
||||
# - name: Build Changelog
|
||||
# id: changelog
|
||||
# uses: ardalanamini/auto-changelog@v3
|
||||
# with:
|
||||
# mention-authors: false
|
||||
# mention-new-contributors: false
|
||||
# include-compare: false
|
||||
# semver: false
|
||||
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
uses: actions/create-release@v1
|
||||
@@ -83,7 +64,6 @@ jobs:
|
||||
tag_name: ${{ github.ref }}
|
||||
release_name: Release v${{ github.ref }}
|
||||
body: '> Changelog coming soon'
|
||||
# body: ${{ steps.changelog.outputs.changelog }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
|
||||
@@ -96,13 +76,3 @@ jobs:
|
||||
asset_path: ${{steps.sign_apk.outputs.signedReleaseFile}}
|
||||
asset_name: app-tempo-release.apk
|
||||
asset_content_type: application/zip
|
||||
|
||||
# - name: Upload AAB
|
||||
# uses: actions/upload-release-asset@v1
|
||||
# env:
|
||||
# GITHUB_TOKEN: ${{ github.token }}
|
||||
# with:
|
||||
# upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
# asset_path: ${{steps.sign_aab.outputs.signedReleaseFile}}
|
||||
# asset_name: app-release.aab
|
||||
# asset_content_type: application/zip
|
||||
|
||||
5
.idea/gradle.xml
generated
5
.idea/gradle.xml
generated
@@ -4,16 +4,15 @@
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
<option name="testRunner" value="GRADLE" />
|
||||
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="gradleJvm" value="17" />
|
||||
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
<option value="$PROJECT_DIR$/app" />
|
||||
</set>
|
||||
</option>
|
||||
<option name="resolveExternalAnnotations" value="false" />
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
|
||||
@@ -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/7_screenshot.png" width=200>
|
||||
<img src="mockup/feat/8_screenshot.png" width=200>
|
||||
<img src="mockup/feat/9_screenshot.png" width=200>
|
||||
</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
|
||||
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,13 +3,16 @@ apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-parcelize'
|
||||
|
||||
android {
|
||||
compileSdk 34
|
||||
buildToolsVersion "34.0.0"
|
||||
compileSdk = 34
|
||||
buildToolsVersion = "34.0.0"
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 24
|
||||
targetSdkVersion 34
|
||||
|
||||
versionCode 24
|
||||
versionName '3.7.0'
|
||||
|
||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||
|
||||
javaCompileOptions {
|
||||
@@ -22,21 +25,17 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
flavorDimensions "default"
|
||||
flavorDimensions += "default"
|
||||
|
||||
productFlavors {
|
||||
tempo {
|
||||
dimension "default"
|
||||
dimension = "default"
|
||||
applicationId 'com.cappielloantonio.tempo'
|
||||
versionCode 16
|
||||
versionName '3.5.0'
|
||||
}
|
||||
|
||||
notquitemy {
|
||||
dimension "default"
|
||||
dimension = "default"
|
||||
applicationId "com.cappielloantonio.notquitemy.tempo"
|
||||
versionCode 1
|
||||
versionName "1.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,38 +65,36 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
|
||||
// AndroidX
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.2.0'
|
||||
implementation 'androidx.preference:preference-ktx:1.2.1'
|
||||
implementation 'androidx.navigation:navigation-fragment-ktx:2.7.0'
|
||||
implementation 'androidx.navigation:navigation-ui-ktx:2.7.0'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.3.1'
|
||||
implementation 'androidx.room:room-runtime:2.5.2'
|
||||
implementation 'androidx.navigation:navigation-fragment-ktx:2.7.6'
|
||||
implementation 'androidx.navigation:navigation-ui-ktx:2.7.6'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.3.2'
|
||||
implementation 'androidx.room:room-runtime:2.6.1'
|
||||
implementation 'androidx.core:core-splashscreen:1.0.1'
|
||||
implementation "androidx.appcompat:appcompat:1.6.1"
|
||||
|
||||
// Android Material
|
||||
implementation 'com.google.android.material:material:1.9.0'
|
||||
implementation 'com.google.android.material:material:1.10.0'
|
||||
|
||||
// Glide
|
||||
implementation 'com.github.bumptech.glide:glide:4.15.1'
|
||||
implementation 'com.github.bumptech.glide:annotations:4.15.1'
|
||||
implementation 'com.github.bumptech.glide:glide:4.16.0'
|
||||
implementation 'com.github.bumptech.glide:annotations:4.16.0'
|
||||
|
||||
// Media3
|
||||
implementation 'androidx.media3:media3-session:1.1.1'
|
||||
implementation 'androidx.media3:media3-common:1.1.1'
|
||||
implementation 'androidx.media3:media3-exoplayer:1.1.1'
|
||||
implementation 'androidx.media3:media3-ui:1.1.1'
|
||||
tempoImplementation 'androidx.media3:media3-cast:1.1.1'
|
||||
implementation 'androidx.media3:media3-session:1.2.1'
|
||||
implementation 'androidx.media3:media3-common:1.2.1'
|
||||
implementation 'androidx.media3:media3-exoplayer:1.2.1'
|
||||
implementation 'androidx.media3:media3-ui:1.2.1'
|
||||
tempoImplementation 'androidx.media3:media3-cast:1.2.1'
|
||||
|
||||
annotationProcessor 'com.github.bumptech.glide:compiler:4.15.1'
|
||||
annotationProcessor 'androidx.room:room-compiler:2.5.2'
|
||||
annotationProcessor 'com.github.bumptech.glide:compiler:4.16.0'
|
||||
annotationProcessor 'androidx.room:room-compiler:2.6.1'
|
||||
|
||||
// Retrofit
|
||||
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
|
||||
implementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.11'
|
||||
implementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.12'
|
||||
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
|
||||
}
|
||||
3
app/proguard-rules.pro
vendored
3
app/proguard-rules.pro
vendored
@@ -21,4 +21,5 @@
|
||||
#-renamesourcefileattribute SourceFile
|
||||
|
||||
-keepattributes SourceFile, LineNumberTable
|
||||
-keep public class * extends java.lang.Exception
|
||||
-keep public class * extends java.lang.Exception
|
||||
-keep class retrofit2.** { *; }
|
||||
@@ -0,0 +1,997 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 4,
|
||||
"identityHash": "528d037bee0f0575f8e0670ae1b04e00",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "queue",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `track_order` INTEGER NOT NULL, `last_play` INTEGER NOT NULL, `playing_changed` INTEGER NOT NULL, `stream_id` TEXT, `parent_id` TEXT, `is_dir` INTEGER NOT NULL, `title` TEXT, `album` TEXT, `artist` TEXT, `track` INTEGER, `year` INTEGER, `genre` TEXT, `cover_art_id` TEXT, `size` INTEGER, `content_type` TEXT, `suffix` TEXT, `transcoding_content_type` TEXT, `transcoded_suffix` TEXT, `duration` INTEGER, `bitrate` INTEGER, `path` TEXT, `is_video` INTEGER NOT NULL, `user_rating` INTEGER, `average_rating` REAL, `play_count` INTEGER, `disc_number` INTEGER, `created` INTEGER, `starred` INTEGER, `album_id` TEXT, `artist_id` TEXT, `type` TEXT, `bookmark_position` INTEGER, `original_width` INTEGER, `original_height` INTEGER, PRIMARY KEY(`track_order`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "trackOrder",
|
||||
"columnName": "track_order",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastPlay",
|
||||
"columnName": "last_play",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "playingChanged",
|
||||
"columnName": "playing_changed",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "streamId",
|
||||
"columnName": "stream_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "parentId",
|
||||
"columnName": "parent_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "isDir",
|
||||
"columnName": "is_dir",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "title",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "album",
|
||||
"columnName": "album",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "artist",
|
||||
"columnName": "artist",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "track",
|
||||
"columnName": "track",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "year",
|
||||
"columnName": "year",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "genre",
|
||||
"columnName": "genre",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "coverArtId",
|
||||
"columnName": "cover_art_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "size",
|
||||
"columnName": "size",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "contentType",
|
||||
"columnName": "content_type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "suffix",
|
||||
"columnName": "suffix",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "transcodedContentType",
|
||||
"columnName": "transcoding_content_type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "transcodedSuffix",
|
||||
"columnName": "transcoded_suffix",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "duration",
|
||||
"columnName": "duration",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "bitrate",
|
||||
"columnName": "bitrate",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "path",
|
||||
"columnName": "path",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "isVideo",
|
||||
"columnName": "is_video",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "userRating",
|
||||
"columnName": "user_rating",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "averageRating",
|
||||
"columnName": "average_rating",
|
||||
"affinity": "REAL",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "playCount",
|
||||
"columnName": "play_count",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "discNumber",
|
||||
"columnName": "disc_number",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "created",
|
||||
"columnName": "created",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "starred",
|
||||
"columnName": "starred",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "albumId",
|
||||
"columnName": "album_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "artistId",
|
||||
"columnName": "artist_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "type",
|
||||
"columnName": "type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "bookmarkPosition",
|
||||
"columnName": "bookmark_position",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "originalWidth",
|
||||
"columnName": "original_width",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "originalHeight",
|
||||
"columnName": "original_height",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"track_order"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "server",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `server_name` TEXT NOT NULL, `username` TEXT NOT NULL, `password` TEXT NOT NULL, `address` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, `low_security` INTEGER NOT NULL DEFAULT false, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "serverId",
|
||||
"columnName": "id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "serverName",
|
||||
"columnName": "server_name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "username",
|
||||
"columnName": "username",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "password",
|
||||
"columnName": "password",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "address",
|
||||
"columnName": "address",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "timestamp",
|
||||
"columnName": "timestamp",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isLowSecurity",
|
||||
"columnName": "low_security",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "recent_search",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`search` TEXT NOT NULL, PRIMARY KEY(`search`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "search",
|
||||
"columnName": "search",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"search"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "download",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `playlist_id` TEXT, `playlist_name` TEXT, `download_state` INTEGER NOT NULL DEFAULT 1, `download_uri` TEXT DEFAULT '', `parent_id` TEXT, `is_dir` INTEGER NOT NULL, `title` TEXT, `album` TEXT, `artist` TEXT, `track` INTEGER, `year` INTEGER, `genre` TEXT, `cover_art_id` TEXT, `size` INTEGER, `content_type` TEXT, `suffix` TEXT, `transcoding_content_type` TEXT, `transcoded_suffix` TEXT, `duration` INTEGER, `bitrate` INTEGER, `path` TEXT, `is_video` INTEGER NOT NULL, `user_rating` INTEGER, `average_rating` REAL, `play_count` INTEGER, `disc_number` INTEGER, `created` INTEGER, `starred` INTEGER, `album_id` TEXT, `artist_id` TEXT, `type` TEXT, `bookmark_position` INTEGER, `original_width` INTEGER, `original_height` INTEGER, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "playlistId",
|
||||
"columnName": "playlist_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "playlistName",
|
||||
"columnName": "playlist_name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "downloadState",
|
||||
"columnName": "download_state",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "1"
|
||||
},
|
||||
{
|
||||
"fieldPath": "downloadUri",
|
||||
"columnName": "download_uri",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "''"
|
||||
},
|
||||
{
|
||||
"fieldPath": "parentId",
|
||||
"columnName": "parent_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "isDir",
|
||||
"columnName": "is_dir",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "title",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "album",
|
||||
"columnName": "album",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "artist",
|
||||
"columnName": "artist",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "track",
|
||||
"columnName": "track",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "year",
|
||||
"columnName": "year",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "genre",
|
||||
"columnName": "genre",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "coverArtId",
|
||||
"columnName": "cover_art_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "size",
|
||||
"columnName": "size",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "contentType",
|
||||
"columnName": "content_type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "suffix",
|
||||
"columnName": "suffix",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "transcodedContentType",
|
||||
"columnName": "transcoding_content_type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "transcodedSuffix",
|
||||
"columnName": "transcoded_suffix",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "duration",
|
||||
"columnName": "duration",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "bitrate",
|
||||
"columnName": "bitrate",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "path",
|
||||
"columnName": "path",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "isVideo",
|
||||
"columnName": "is_video",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "userRating",
|
||||
"columnName": "user_rating",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "averageRating",
|
||||
"columnName": "average_rating",
|
||||
"affinity": "REAL",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "playCount",
|
||||
"columnName": "play_count",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "discNumber",
|
||||
"columnName": "disc_number",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "created",
|
||||
"columnName": "created",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "starred",
|
||||
"columnName": "starred",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "albumId",
|
||||
"columnName": "album_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "artistId",
|
||||
"columnName": "artist_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "type",
|
||||
"columnName": "type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "bookmarkPosition",
|
||||
"columnName": "bookmark_position",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "originalWidth",
|
||||
"columnName": "original_width",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "originalHeight",
|
||||
"columnName": "original_height",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "chronology",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, `server` TEXT, `parent_id` TEXT, `is_dir` INTEGER NOT NULL, `title` TEXT, `album` TEXT, `artist` TEXT, `track` INTEGER, `year` INTEGER, `genre` TEXT, `cover_art_id` TEXT, `size` INTEGER, `content_type` TEXT, `suffix` TEXT, `transcoding_content_type` TEXT, `transcoded_suffix` TEXT, `duration` INTEGER, `bitrate` INTEGER, `path` TEXT, `is_video` INTEGER NOT NULL, `user_rating` INTEGER, `average_rating` REAL, `play_count` INTEGER, `disc_number` INTEGER, `created` INTEGER, `starred` INTEGER, `album_id` TEXT, `artist_id` TEXT, `type` TEXT, `bookmark_position` INTEGER, `original_width` INTEGER, `original_height` INTEGER, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "timestamp",
|
||||
"columnName": "timestamp",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "server",
|
||||
"columnName": "server",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "parentId",
|
||||
"columnName": "parent_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "isDir",
|
||||
"columnName": "is_dir",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "title",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "album",
|
||||
"columnName": "album",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "artist",
|
||||
"columnName": "artist",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "track",
|
||||
"columnName": "track",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "year",
|
||||
"columnName": "year",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "genre",
|
||||
"columnName": "genre",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "coverArtId",
|
||||
"columnName": "cover_art_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "size",
|
||||
"columnName": "size",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "contentType",
|
||||
"columnName": "content_type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "suffix",
|
||||
"columnName": "suffix",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "transcodedContentType",
|
||||
"columnName": "transcoding_content_type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "transcodedSuffix",
|
||||
"columnName": "transcoded_suffix",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "duration",
|
||||
"columnName": "duration",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "bitrate",
|
||||
"columnName": "bitrate",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "path",
|
||||
"columnName": "path",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "isVideo",
|
||||
"columnName": "is_video",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "userRating",
|
||||
"columnName": "user_rating",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "averageRating",
|
||||
"columnName": "average_rating",
|
||||
"affinity": "REAL",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "playCount",
|
||||
"columnName": "play_count",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "discNumber",
|
||||
"columnName": "disc_number",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "created",
|
||||
"columnName": "created",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "starred",
|
||||
"columnName": "starred",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "albumId",
|
||||
"columnName": "album_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "artistId",
|
||||
"columnName": "artist_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "type",
|
||||
"columnName": "type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "bookmarkPosition",
|
||||
"columnName": "bookmark_position",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "originalWidth",
|
||||
"columnName": "original_width",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "originalHeight",
|
||||
"columnName": "original_height",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "favorite",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`timestamp` INTEGER NOT NULL, `songId` TEXT, `albumId` TEXT, `artistId` TEXT, `toStar` INTEGER NOT NULL, PRIMARY KEY(`timestamp`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "timestamp",
|
||||
"columnName": "timestamp",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "songId",
|
||||
"columnName": "songId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "albumId",
|
||||
"columnName": "albumId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "artistId",
|
||||
"columnName": "artistId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "toStar",
|
||||
"columnName": "toStar",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"timestamp"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "session_media_item",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `parent_id` TEXT, `is_dir` INTEGER NOT NULL, `title` TEXT, `album` TEXT, `artist` TEXT, `track` INTEGER, `year` INTEGER, `genre` TEXT, `cover_art_id` TEXT, `size` INTEGER, `content_type` TEXT, `suffix` TEXT, `transcoding_content_type` TEXT, `transcoded_suffix` TEXT, `duration` INTEGER, `bitrate` INTEGER, `path` TEXT, `is_video` INTEGER NOT NULL, `user_rating` INTEGER, `average_rating` REAL, `play_count` INTEGER, `disc_number` INTEGER, `created` INTEGER, `starred` INTEGER, `album_id` TEXT, `artist_id` TEXT, `type` TEXT, `bookmark_position` INTEGER, `original_width` INTEGER, `original_height` INTEGER, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "parentId",
|
||||
"columnName": "parent_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "isDir",
|
||||
"columnName": "is_dir",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "title",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "album",
|
||||
"columnName": "album",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "artist",
|
||||
"columnName": "artist",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "track",
|
||||
"columnName": "track",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "year",
|
||||
"columnName": "year",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "genre",
|
||||
"columnName": "genre",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "coverArtId",
|
||||
"columnName": "cover_art_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "size",
|
||||
"columnName": "size",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "contentType",
|
||||
"columnName": "content_type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "suffix",
|
||||
"columnName": "suffix",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "transcodedContentType",
|
||||
"columnName": "transcoding_content_type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "transcodedSuffix",
|
||||
"columnName": "transcoded_suffix",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "duration",
|
||||
"columnName": "duration",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "bitrate",
|
||||
"columnName": "bitrate",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "path",
|
||||
"columnName": "path",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "isVideo",
|
||||
"columnName": "is_video",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "userRating",
|
||||
"columnName": "user_rating",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "averageRating",
|
||||
"columnName": "average_rating",
|
||||
"affinity": "REAL",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "playCount",
|
||||
"columnName": "play_count",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "discNumber",
|
||||
"columnName": "disc_number",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "created",
|
||||
"columnName": "created",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "starred",
|
||||
"columnName": "starred",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "albumId",
|
||||
"columnName": "album_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "artistId",
|
||||
"columnName": "artist_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "type",
|
||||
"columnName": "type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "bookmarkPosition",
|
||||
"columnName": "bookmark_position",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "originalWidth",
|
||||
"columnName": "original_width",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "originalHeight",
|
||||
"columnName": "original_height",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '528d037bee0f0575f8e0670ae1b04e00')"
|
||||
]
|
||||
}
|
||||
}
|
||||
1004
app/schemas/com.cappielloantonio.tempo.database.AppDatabase/5.json
Normal file
1004
app/schemas/com.cappielloantonio.tempo.database.AppDatabase/5.json
Normal file
File diff suppressed because it is too large
Load Diff
1016
app/schemas/com.cappielloantonio.tempo.database.AppDatabase/6.json
Normal file
1016
app/schemas/com.cappielloantonio.tempo.database.AppDatabase/6.json
Normal file
File diff suppressed because it is too large
Load Diff
1021
app/schemas/com.cappielloantonio.tempo.database.AppDatabase/7.json
Normal file
1021
app/schemas/com.cappielloantonio.tempo.database.AppDatabase/7.json
Normal file
File diff suppressed because it is too large
Load Diff
1021
app/schemas/com.cappielloantonio.tempo.database.AppDatabase/8.json
Normal file
1021
app/schemas/com.cappielloantonio.tempo.database.AppDatabase/8.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -16,11 +16,17 @@
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:localeConfig="@xml/locale_config"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:roundIcon="@mipmap/ic_launcher"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme.SplashScreen"
|
||||
android:usesCleartextTraffic="true">
|
||||
|
||||
<!-- Declare that this session demo supports Android Auto. -->
|
||||
<meta-data
|
||||
android:name="com.google.android.gms.car.application"
|
||||
android:resource="@xml/auto_app_desc" />
|
||||
|
||||
<meta-data
|
||||
android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
|
||||
android:value="androidx.media3.cast.DefaultCastOptionsProvider" />
|
||||
@@ -45,7 +51,8 @@
|
||||
android:foregroundServiceType="mediaPlayback">
|
||||
<intent-filter>
|
||||
<action android:name="androidx.media3.session.MediaLibraryService" />
|
||||
<action android:name="androidx.media3.session.MediaBrowserService" />
|
||||
<action android:name="android.media.browse.MediaBrowserService"/>
|
||||
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
|
||||
@@ -14,17 +14,19 @@ import com.cappielloantonio.tempo.database.dao.FavoriteDao;
|
||||
import com.cappielloantonio.tempo.database.dao.QueueDao;
|
||||
import com.cappielloantonio.tempo.database.dao.RecentSearchDao;
|
||||
import com.cappielloantonio.tempo.database.dao.ServerDao;
|
||||
import com.cappielloantonio.tempo.database.dao.SessionMediaItemDao;
|
||||
import com.cappielloantonio.tempo.model.Chronology;
|
||||
import com.cappielloantonio.tempo.model.Download;
|
||||
import com.cappielloantonio.tempo.model.Favorite;
|
||||
import com.cappielloantonio.tempo.model.Queue;
|
||||
import com.cappielloantonio.tempo.model.RecentSearch;
|
||||
import com.cappielloantonio.tempo.model.Server;
|
||||
import com.cappielloantonio.tempo.model.SessionMediaItem;
|
||||
|
||||
@Database(
|
||||
version = 3,
|
||||
entities = {Queue.class, Server.class, RecentSearch.class, Download.class, Chronology.class, Favorite.class},
|
||||
autoMigrations = {@AutoMigration(from = 2, to = 3)}
|
||||
version = 8,
|
||||
entities = {Queue.class, Server.class, RecentSearch.class, Download.class, Chronology.class, Favorite.class, SessionMediaItem.class},
|
||||
autoMigrations = {@AutoMigration(from = 7, to = 8)}
|
||||
)
|
||||
@TypeConverters({DateConverters.class})
|
||||
public abstract class AppDatabase extends RoomDatabase {
|
||||
@@ -52,4 +54,6 @@ public abstract class AppDatabase extends RoomDatabase {
|
||||
public abstract ChronologyDao chronologyDao();
|
||||
|
||||
public abstract FavoriteDao favoriteDao();
|
||||
|
||||
public abstract SessionMediaItemDao sessionMediaItemDao();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.cappielloantonio.tempo.database.dao;
|
||||
|
||||
import androidx.room.Dao;
|
||||
import androidx.room.Insert;
|
||||
import androidx.room.OnConflictStrategy;
|
||||
import androidx.room.Query;
|
||||
|
||||
import com.cappielloantonio.tempo.model.Queue;
|
||||
import com.cappielloantonio.tempo.model.SessionMediaItem;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Dao
|
||||
public interface SessionMediaItemDao {
|
||||
@Query("SELECT * FROM session_media_item WHERE id = :id")
|
||||
SessionMediaItem get(String id);
|
||||
|
||||
@Query("SELECT * FROM session_media_item WHERE timestamp = :timestamp")
|
||||
List<SessionMediaItem> get(long timestamp);
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||
void insert(SessionMediaItem sessionMediaItem);
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||
void insertAll(List<SessionMediaItem> sessionMediaItems);
|
||||
|
||||
@Query("DELETE FROM session_media_item")
|
||||
void deleteAll();
|
||||
}
|
||||
@@ -5,6 +5,9 @@ import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.content.res.AppCompatResources;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.RequestBuilder;
|
||||
import com.bumptech.glide.RequestManager;
|
||||
@@ -15,6 +18,7 @@ import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions;
|
||||
import com.bumptech.glide.request.RequestOptions;
|
||||
import com.bumptech.glide.signature.ObjectKey;
|
||||
import com.cappielloantonio.tempo.App;
|
||||
import com.cappielloantonio.tempo.R;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
import com.cappielloantonio.tempo.util.Util;
|
||||
import com.google.android.material.elevation.SurfaceColors;
|
||||
@@ -28,16 +32,53 @@ public class CustomGlideRequest {
|
||||
|
||||
public static final DiskCacheStrategy DEFAULT_DISK_CACHE_STRATEGY = DiskCacheStrategy.ALL;
|
||||
|
||||
public static RequestOptions createRequestOptions(Context context, String item) {
|
||||
public enum ResourceType {
|
||||
Unknown,
|
||||
Album,
|
||||
Artist,
|
||||
Folder,
|
||||
Directory,
|
||||
Playlist,
|
||||
Podcast,
|
||||
Radio,
|
||||
Song,
|
||||
}
|
||||
|
||||
public static RequestOptions createRequestOptions(Context context, String item, ResourceType type) {
|
||||
return new RequestOptions()
|
||||
.placeholder(new ColorDrawable(SurfaceColors.SURFACE_5.getColor(context)))
|
||||
.fallback(new ColorDrawable(SurfaceColors.SURFACE_5.getColor(context)))
|
||||
.error(new ColorDrawable(SurfaceColors.SURFACE_5.getColor(context)))
|
||||
.fallback(getPlaceholder(context, type))
|
||||
.error(getPlaceholder(context, type))
|
||||
.diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY)
|
||||
.signature(new ObjectKey(item != null ? item : 0))
|
||||
.transform(new CenterCrop(), new RoundedCorners(CustomGlideRequest.CORNER_RADIUS));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Drawable getPlaceholder(Context context, ResourceType type) {
|
||||
switch (type) {
|
||||
case Album:
|
||||
return AppCompatResources.getDrawable(context, R.drawable.ic_placeholder_album);
|
||||
case Artist:
|
||||
return AppCompatResources.getDrawable(context, R.drawable.ic_placeholder_artist);
|
||||
case Folder:
|
||||
return AppCompatResources.getDrawable(context, R.drawable.ic_placeholder_folder);
|
||||
case Directory:
|
||||
return AppCompatResources.getDrawable(context, R.drawable.ic_placeholder_directory);
|
||||
case Playlist:
|
||||
return AppCompatResources.getDrawable(context, R.drawable.ic_placeholder_playlist);
|
||||
case Podcast:
|
||||
return AppCompatResources.getDrawable(context, R.drawable.ic_placeholder_podcast);
|
||||
case Radio:
|
||||
return AppCompatResources.getDrawable(context, R.drawable.ic_placeholder_radio);
|
||||
case Song:
|
||||
return AppCompatResources.getDrawable(context, R.drawable.ic_placeholder_song);
|
||||
default:
|
||||
case Unknown:
|
||||
return new ColorDrawable(SurfaceColors.SURFACE_5.getColor(context));
|
||||
}
|
||||
}
|
||||
|
||||
public static String createUrl(String item, int size) {
|
||||
Map<String, String> params = App.getSubsonicClientInstance(false).getParams();
|
||||
|
||||
@@ -72,18 +113,18 @@ public class CustomGlideRequest {
|
||||
private final RequestManager requestManager;
|
||||
private Object item;
|
||||
|
||||
private Builder(Context context, String item) {
|
||||
private Builder(Context context, String item, ResourceType type) {
|
||||
this.requestManager = Glide.with(context);
|
||||
|
||||
if (item != null && !Preferences.isDataSavingMode()) {
|
||||
this.item = createUrl(item, Preferences.getImageSize());
|
||||
}
|
||||
|
||||
requestManager.applyDefaultRequestOptions(createRequestOptions(context, item));
|
||||
requestManager.applyDefaultRequestOptions(createRequestOptions(context, item, type));
|
||||
}
|
||||
|
||||
public static Builder from(Context context, String item) {
|
||||
return new Builder(context, item);
|
||||
public static Builder from(Context context, String item, ResourceType type) {
|
||||
return new Builder(context, item, type);
|
||||
}
|
||||
|
||||
public RequestBuilder<Drawable> build() {
|
||||
|
||||
@@ -29,4 +29,7 @@ public interface ClickCallback {
|
||||
default void onMusicFolderClick(Bundle bundle) {}
|
||||
default void onMusicDirectoryClick(Bundle bundle) {}
|
||||
default void onMusicIndexClick(Bundle bundle) {}
|
||||
default void onDownloadGroupLongClick(Bundle bundle) {}
|
||||
default void onShareClick(Bundle bundle) {}
|
||||
default void onShareLongClick(Bundle bundle) {}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.cappielloantonio.tempo.interfaces;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
|
||||
@Keep
|
||||
public interface PlaylistCallback {
|
||||
default void onDismiss() {}
|
||||
}
|
||||
@@ -0,0 +1,281 @@
|
||||
package com.cappielloantonio.tempo.model
|
||||
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import androidx.annotation.Keep
|
||||
import androidx.media3.common.MediaItem
|
||||
import androidx.media3.common.MediaItem.RequestMetadata
|
||||
import androidx.media3.common.MediaMetadata
|
||||
import androidx.media3.common.MimeTypes
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import com.cappielloantonio.tempo.glide.CustomGlideRequest
|
||||
import com.cappielloantonio.tempo.subsonic.models.Child
|
||||
import com.cappielloantonio.tempo.subsonic.models.InternetRadioStation
|
||||
import com.cappielloantonio.tempo.subsonic.models.PodcastEpisode
|
||||
import com.cappielloantonio.tempo.util.Constants
|
||||
import com.cappielloantonio.tempo.util.MusicUtil
|
||||
import com.cappielloantonio.tempo.util.Preferences.getImageSize
|
||||
import java.util.Date
|
||||
|
||||
@UnstableApi
|
||||
@Keep
|
||||
@Entity(tableName = "session_media_item")
|
||||
class SessionMediaItem() {
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
@ColumnInfo(name = "index")
|
||||
var index: Int = 0
|
||||
|
||||
@ColumnInfo(name = "id")
|
||||
var id: String? = null
|
||||
|
||||
@ColumnInfo(name = "parent_id")
|
||||
var parentId: String? = null
|
||||
|
||||
@ColumnInfo(name = "is_dir")
|
||||
var isDir: Boolean = false
|
||||
|
||||
@ColumnInfo
|
||||
var title: String? = null
|
||||
|
||||
@ColumnInfo
|
||||
var album: String? = null
|
||||
|
||||
@ColumnInfo
|
||||
var artist: String? = null
|
||||
|
||||
@ColumnInfo
|
||||
var track: Int? = null
|
||||
|
||||
@ColumnInfo
|
||||
var year: Int? = null
|
||||
|
||||
@ColumnInfo
|
||||
var genre: String? = null
|
||||
|
||||
@ColumnInfo(name = "cover_art_id")
|
||||
var coverArtId: String? = null
|
||||
|
||||
@ColumnInfo
|
||||
var size: Long? = null
|
||||
|
||||
@ColumnInfo(name = "content_type")
|
||||
var contentType: String? = null
|
||||
|
||||
@ColumnInfo
|
||||
var suffix: String? = null
|
||||
|
||||
@ColumnInfo("transcoding_content_type")
|
||||
var transcodedContentType: String? = null
|
||||
|
||||
@ColumnInfo(name = "transcoded_suffix")
|
||||
var transcodedSuffix: String? = null
|
||||
|
||||
@ColumnInfo
|
||||
var duration: Int? = null
|
||||
|
||||
@ColumnInfo("bitrate")
|
||||
var bitrate: Int? = null
|
||||
|
||||
@ColumnInfo
|
||||
var path: String? = null
|
||||
|
||||
@ColumnInfo(name = "is_video")
|
||||
var isVideo: Boolean = false
|
||||
|
||||
@ColumnInfo(name = "user_rating")
|
||||
var userRating: Int? = null
|
||||
|
||||
@ColumnInfo(name = "average_rating")
|
||||
var averageRating: Double? = null
|
||||
|
||||
@ColumnInfo(name = "play_count")
|
||||
var playCount: Long? = null
|
||||
|
||||
@ColumnInfo(name = "disc_number")
|
||||
var discNumber: Int? = null
|
||||
|
||||
@ColumnInfo
|
||||
var created: Date? = null
|
||||
|
||||
@ColumnInfo
|
||||
var starred: Date? = null
|
||||
|
||||
@ColumnInfo(name = "album_id")
|
||||
var albumId: String? = null
|
||||
|
||||
@ColumnInfo(name = "artist_id")
|
||||
var artistId: String? = null
|
||||
|
||||
@ColumnInfo
|
||||
var type: String? = null
|
||||
|
||||
@ColumnInfo(name = "bookmark_position")
|
||||
var bookmarkPosition: Long? = null
|
||||
|
||||
@ColumnInfo(name = "original_width")
|
||||
var originalWidth: Int? = null
|
||||
|
||||
@ColumnInfo(name = "original_height")
|
||||
var originalHeight: Int? = null
|
||||
|
||||
@ColumnInfo(name = "stream_id")
|
||||
var streamId: String? = null
|
||||
|
||||
@ColumnInfo(name = "stream_url")
|
||||
var streamUrl: String? = null
|
||||
|
||||
@ColumnInfo(name = "timestamp")
|
||||
var timestamp: Long? = null
|
||||
|
||||
constructor(child: Child) : this() {
|
||||
id = child.id
|
||||
parentId = child.parentId
|
||||
isDir = child.isDir
|
||||
title = child.title
|
||||
album = child.album
|
||||
artist = child.artist
|
||||
track = child.track
|
||||
year = child.year
|
||||
genre = child.genre
|
||||
coverArtId = child.coverArtId
|
||||
size = child.size
|
||||
contentType = child.contentType
|
||||
suffix = child.suffix
|
||||
transcodedContentType = child.transcodedContentType
|
||||
transcodedSuffix = child.transcodedSuffix
|
||||
duration = child.duration
|
||||
bitrate = child.bitrate
|
||||
path = child.path
|
||||
isVideo = child.isVideo
|
||||
userRating = child.userRating
|
||||
averageRating = child.averageRating
|
||||
playCount = child.playCount
|
||||
discNumber = child.discNumber
|
||||
created = child.created
|
||||
starred = child.starred
|
||||
albumId = child.albumId
|
||||
artistId = child.artistId
|
||||
type = Constants.MEDIA_TYPE_MUSIC
|
||||
bookmarkPosition = child.bookmarkPosition
|
||||
originalWidth = child.originalWidth
|
||||
originalHeight = child.originalHeight
|
||||
}
|
||||
|
||||
constructor(podcastEpisode: PodcastEpisode) : this() {
|
||||
id = podcastEpisode.id
|
||||
parentId = podcastEpisode.parentId
|
||||
isDir = podcastEpisode.isDir
|
||||
title = podcastEpisode.title
|
||||
album = podcastEpisode.album
|
||||
artist = podcastEpisode.artist
|
||||
year = podcastEpisode.year
|
||||
genre = podcastEpisode.genre
|
||||
coverArtId = podcastEpisode.coverArtId
|
||||
size = podcastEpisode.size
|
||||
contentType = podcastEpisode.contentType
|
||||
suffix = podcastEpisode.suffix
|
||||
duration = podcastEpisode.duration
|
||||
bitrate = podcastEpisode.bitrate
|
||||
path = podcastEpisode.path
|
||||
isVideo = podcastEpisode.isVideo
|
||||
created = podcastEpisode.created
|
||||
artistId = podcastEpisode.artistId
|
||||
streamId = podcastEpisode.streamId
|
||||
type = Constants.MEDIA_TYPE_PODCAST
|
||||
}
|
||||
|
||||
constructor(internetRadioStation: InternetRadioStation) : this() {
|
||||
id = internetRadioStation.id
|
||||
title = internetRadioStation.name
|
||||
streamUrl = internetRadioStation.streamUrl
|
||||
type = Constants.MEDIA_TYPE_RADIO
|
||||
}
|
||||
|
||||
fun getMediaItem(): MediaItem {
|
||||
val uri: Uri = getStreamUri()
|
||||
val artworkUri = Uri.parse(CustomGlideRequest.createUrl(coverArtId, getImageSize()))
|
||||
|
||||
val bundle = Bundle()
|
||||
bundle.putString("id", id)
|
||||
bundle.putString("parentId", parentId)
|
||||
bundle.putBoolean("isDir", isDir)
|
||||
bundle.putString("title", title)
|
||||
bundle.putString("album", album)
|
||||
bundle.putString("artist", artist)
|
||||
bundle.putInt("track", track ?: 0)
|
||||
bundle.putInt("year", year ?: 0)
|
||||
bundle.putString("genre", genre)
|
||||
bundle.putString("coverArtId", coverArtId)
|
||||
bundle.putLong("size", size ?: 0)
|
||||
bundle.putString("contentType", contentType)
|
||||
bundle.putString("suffix", suffix)
|
||||
bundle.putString("transcodedContentType", transcodedContentType)
|
||||
bundle.putString("transcodedSuffix", transcodedSuffix)
|
||||
bundle.putInt("duration", duration ?: 0)
|
||||
bundle.putInt("bitrate", bitrate ?: 0)
|
||||
bundle.putString("path", path)
|
||||
bundle.putBoolean("isVideo", isVideo)
|
||||
bundle.putInt("userRating", userRating ?: 0)
|
||||
bundle.putDouble("averageRating", averageRating ?: .0)
|
||||
bundle.putLong("playCount", playCount ?: 0)
|
||||
bundle.putInt("discNumber", discNumber ?: 0)
|
||||
bundle.putLong("created", created?.time ?: 0)
|
||||
bundle.putLong("starred", starred?.time ?: 0)
|
||||
bundle.putString("albumId", albumId)
|
||||
bundle.putString("artistId", artistId)
|
||||
bundle.putString("type", Constants.MEDIA_TYPE_MUSIC)
|
||||
bundle.putLong("bookmarkPosition", bookmarkPosition ?: 0)
|
||||
bundle.putInt("originalWidth", originalWidth ?: 0)
|
||||
bundle.putInt("originalHeight", originalHeight ?: 0)
|
||||
bundle.putString("uri", uri.toString())
|
||||
|
||||
return MediaItem.Builder()
|
||||
.setMediaId(id!!)
|
||||
.setMediaMetadata(
|
||||
MediaMetadata.Builder()
|
||||
.setTitle(MusicUtil.getReadableString(title))
|
||||
.setTrackNumber(track ?: 0)
|
||||
.setDiscNumber(discNumber ?: 0)
|
||||
.setReleaseYear(year ?: 0)
|
||||
.setAlbumTitle(MusicUtil.getReadableString(album))
|
||||
.setArtist(MusicUtil.getReadableString(artist))
|
||||
.setArtworkUri(artworkUri)
|
||||
.setExtras(bundle)
|
||||
.setIsBrowsable(false)
|
||||
.setIsPlayable(true)
|
||||
.build()
|
||||
)
|
||||
.setRequestMetadata(
|
||||
RequestMetadata.Builder()
|
||||
.setMediaUri(uri)
|
||||
.setExtras(bundle)
|
||||
.build()
|
||||
)
|
||||
.setMimeType(MimeTypes.BASE_TYPE_AUDIO)
|
||||
.setUri(uri)
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun getStreamUri(): Uri {
|
||||
return when (type) {
|
||||
Constants.MEDIA_TYPE_MUSIC -> {
|
||||
MusicUtil.getStreamUri(id)
|
||||
}
|
||||
|
||||
Constants.MEDIA_TYPE_PODCAST -> {
|
||||
MusicUtil.getStreamUri(streamId)
|
||||
}
|
||||
|
||||
Constants.MEDIA_TYPE_RADIO -> {
|
||||
Uri.parse(streamUrl)
|
||||
}
|
||||
|
||||
else -> {
|
||||
MusicUtil.getStreamUri(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -105,7 +105,9 @@ public class AlbumRepository {
|
||||
List<Child> tracks = new ArrayList<>();
|
||||
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getAlbum() != null) {
|
||||
tracks.addAll(response.body().getSubsonicResponse().getAlbum().getSongs());
|
||||
if (response.body().getSubsonicResponse().getAlbum().getSongs() != null) {
|
||||
tracks.addAll(response.body().getSubsonicResponse().getAlbum().getSongs());
|
||||
}
|
||||
}
|
||||
|
||||
albumTracks.setValue(tracks);
|
||||
@@ -247,7 +249,7 @@ public class AlbumRepository {
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getAlbumList2() != null) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getAlbumList2() != null && response.body().getSubsonicResponse().getAlbumList2().getAlbums() != null) {
|
||||
if (response.body().getSubsonicResponse().getAlbumList2().getAlbums().size() > 0 && !response.body().getSubsonicResponse().getAlbumList2().getAlbums().isEmpty()) {
|
||||
callback.onLoadYear(response.body().getSubsonicResponse().getAlbumList2().getAlbums().get(0).getYear());
|
||||
} else {
|
||||
|
||||
@@ -63,9 +63,11 @@ public class ArtistRepository {
|
||||
if (response.isSuccessful() && response.body() != null) {
|
||||
List<ArtistID3> artists = new ArrayList<>();
|
||||
|
||||
if(response.body().getSubsonicResponse().getArtists() != null) {
|
||||
if(response.body().getSubsonicResponse().getArtists() != null && response.body().getSubsonicResponse().getArtists().getIndices() != null) {
|
||||
for (IndexID3 index : response.body().getSubsonicResponse().getArtists().getIndices()) {
|
||||
artists.addAll(index.getArtists());
|
||||
if(index != null && index.getArtists() != null) {
|
||||
artists.addAll(index.getArtists());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,957 @@
|
||||
package com.cappielloantonio.tempo.repository;
|
||||
|
||||
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.OptIn;
|
||||
import androidx.media3.common.MediaItem;
|
||||
import androidx.media3.common.MediaMetadata;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.session.LibraryResult;
|
||||
|
||||
import com.cappielloantonio.tempo.App;
|
||||
import com.cappielloantonio.tempo.database.AppDatabase;
|
||||
import com.cappielloantonio.tempo.database.dao.SessionMediaItemDao;
|
||||
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
||||
import com.cappielloantonio.tempo.model.SessionMediaItem;
|
||||
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
||||
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Artist;
|
||||
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Directory;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Index;
|
||||
import com.cappielloantonio.tempo.subsonic.models.InternetRadioStation;
|
||||
import com.cappielloantonio.tempo.subsonic.models.MusicFolder;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Playlist;
|
||||
import com.cappielloantonio.tempo.subsonic.models.PodcastEpisode;
|
||||
import com.cappielloantonio.tempo.util.MappingUtil;
|
||||
import com.cappielloantonio.tempo.util.MusicUtil;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.common.util.concurrent.SettableFuture;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
|
||||
public class AutomotiveRepository {
|
||||
private final SessionMediaItemDao sessionMediaItemDao = AppDatabase.getInstance().sessionMediaItemDao();
|
||||
|
||||
public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> getAlbums(String prefix, String type, int size) {
|
||||
final SettableFuture<LibraryResult<ImmutableList<MediaItem>>> listenableFuture = SettableFuture.create();
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getAlbumSongListClient()
|
||||
.getAlbumList2(type, size, 0, null, null)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getAlbumList2() != null && response.body().getSubsonicResponse().getAlbumList2().getAlbums() != null) {
|
||||
List<AlbumID3> albums = response.body().getSubsonicResponse().getAlbumList2().getAlbums();
|
||||
|
||||
List<MediaItem> mediaItems = new ArrayList<>();
|
||||
|
||||
for (AlbumID3 album : albums) {
|
||||
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(album.getCoverArtId(), Preferences.getImageSize()));
|
||||
|
||||
MediaMetadata mediaMetadata = new MediaMetadata.Builder()
|
||||
.setTitle(album.getName())
|
||||
.setAlbumTitle(album.getName())
|
||||
.setArtist(album.getArtist())
|
||||
.setGenre(album.getGenre())
|
||||
.setIsBrowsable(true)
|
||||
.setIsPlayable(false)
|
||||
.setMediaType(MediaMetadata.MEDIA_TYPE_ALBUM)
|
||||
.setArtworkUri(artworkUri)
|
||||
.build();
|
||||
|
||||
MediaItem mediaItem = new MediaItem.Builder()
|
||||
.setMediaId(prefix + album.getId())
|
||||
.setMediaMetadata(mediaMetadata)
|
||||
.setUri("")
|
||||
.build();
|
||||
|
||||
mediaItems.add(mediaItem);
|
||||
}
|
||||
|
||||
LibraryResult<ImmutableList<MediaItem>> libraryResult = LibraryResult.ofItemList(ImmutableList.copyOf(mediaItems), null);
|
||||
|
||||
listenableFuture.set(libraryResult);
|
||||
} else {
|
||||
listenableFuture.set(LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
listenableFuture.setException(t);
|
||||
}
|
||||
});
|
||||
|
||||
return listenableFuture;
|
||||
}
|
||||
|
||||
public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> getStarredSongs() {
|
||||
final SettableFuture<LibraryResult<ImmutableList<MediaItem>>> listenableFuture = SettableFuture.create();
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getAlbumSongListClient()
|
||||
.getStarred2()
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getStarred2() != null && response.body().getSubsonicResponse().getStarred2().getSongs() != null) {
|
||||
List<Child> songs = response.body().getSubsonicResponse().getStarred2().getSongs();
|
||||
|
||||
setChildrenMetadata(songs);
|
||||
|
||||
List<MediaItem> mediaItems = MappingUtil.mapMediaItems(songs);
|
||||
|
||||
LibraryResult<ImmutableList<MediaItem>> libraryResult = LibraryResult.ofItemList(ImmutableList.copyOf(mediaItems), null);
|
||||
|
||||
listenableFuture.set(libraryResult);
|
||||
} else {
|
||||
listenableFuture.set(LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
listenableFuture.setException(t);
|
||||
}
|
||||
});
|
||||
|
||||
return listenableFuture;
|
||||
}
|
||||
|
||||
public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> getStarredAlbums(String prefix) {
|
||||
final SettableFuture<LibraryResult<ImmutableList<MediaItem>>> listenableFuture = SettableFuture.create();
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getAlbumSongListClient()
|
||||
.getStarred2()
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getStarred2() != null && response.body().getSubsonicResponse().getStarred2().getAlbums() != null) {
|
||||
List<AlbumID3> albums = response.body().getSubsonicResponse().getStarred2().getAlbums();
|
||||
|
||||
List<MediaItem> mediaItems = new ArrayList<>();
|
||||
|
||||
for (AlbumID3 album : albums) {
|
||||
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(album.getCoverArtId(), Preferences.getImageSize()));
|
||||
|
||||
MediaMetadata mediaMetadata = new MediaMetadata.Builder()
|
||||
.setTitle(album.getName())
|
||||
.setArtist(album.getArtist())
|
||||
.setGenre(album.getGenre())
|
||||
.setIsBrowsable(true)
|
||||
.setIsPlayable(false)
|
||||
.setMediaType(MediaMetadata.MEDIA_TYPE_ALBUM)
|
||||
.setArtworkUri(artworkUri)
|
||||
.build();
|
||||
|
||||
MediaItem mediaItem = new MediaItem.Builder()
|
||||
.setMediaId(prefix + album.getId())
|
||||
.setMediaMetadata(mediaMetadata)
|
||||
.setUri("")
|
||||
.build();
|
||||
|
||||
mediaItems.add(mediaItem);
|
||||
}
|
||||
|
||||
LibraryResult<ImmutableList<MediaItem>> libraryResult = LibraryResult.ofItemList(ImmutableList.copyOf(mediaItems), null);
|
||||
|
||||
listenableFuture.set(libraryResult);
|
||||
} else {
|
||||
listenableFuture.set(LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
return listenableFuture;
|
||||
}
|
||||
|
||||
public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> getStarredArtists(String prefix) {
|
||||
final SettableFuture<LibraryResult<ImmutableList<MediaItem>>> listenableFuture = SettableFuture.create();
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getAlbumSongListClient()
|
||||
.getStarred2()
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getStarred2() != null && response.body().getSubsonicResponse().getStarred2().getArtists() != null) {
|
||||
List<ArtistID3> artists = response.body().getSubsonicResponse().getStarred2().getArtists();
|
||||
|
||||
Collections.shuffle(artists);
|
||||
|
||||
List<MediaItem> mediaItems = new ArrayList<>();
|
||||
|
||||
for (ArtistID3 artist : artists) {
|
||||
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(artist.getCoverArtId(), Preferences.getImageSize()));
|
||||
|
||||
MediaMetadata mediaMetadata = new MediaMetadata.Builder()
|
||||
.setTitle(artist.getName())
|
||||
.setIsBrowsable(true)
|
||||
.setIsPlayable(false)
|
||||
.setMediaType(MediaMetadata.MEDIA_TYPE_PLAYLIST)
|
||||
.setArtworkUri(artworkUri)
|
||||
.build();
|
||||
|
||||
MediaItem mediaItem = new MediaItem.Builder()
|
||||
.setMediaId(prefix + artist.getId())
|
||||
.setMediaMetadata(mediaMetadata)
|
||||
.setUri("")
|
||||
.build();
|
||||
|
||||
mediaItems.add(mediaItem);
|
||||
}
|
||||
|
||||
LibraryResult<ImmutableList<MediaItem>> libraryResult = LibraryResult.ofItemList(ImmutableList.copyOf(mediaItems), null);
|
||||
|
||||
listenableFuture.set(libraryResult);
|
||||
} else {
|
||||
listenableFuture.set(LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
listenableFuture.setException(t);
|
||||
}
|
||||
});
|
||||
|
||||
return listenableFuture;
|
||||
}
|
||||
|
||||
public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> getMusicFolders(String prefix) {
|
||||
final SettableFuture<LibraryResult<ImmutableList<MediaItem>>> listenableFuture = SettableFuture.create();
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getBrowsingClient()
|
||||
.getMusicFolders()
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getMusicFolders() != null && response.body().getSubsonicResponse().getMusicFolders().getMusicFolders() != null) {
|
||||
List<MusicFolder> musicFolders = response.body().getSubsonicResponse().getMusicFolders().getMusicFolders();
|
||||
|
||||
List<MediaItem> mediaItems = new ArrayList<>();
|
||||
|
||||
for (MusicFolder musicFolder : musicFolders) {
|
||||
MediaMetadata mediaMetadata = new MediaMetadata.Builder()
|
||||
.setTitle(musicFolder.getName())
|
||||
.setIsBrowsable(true)
|
||||
.setIsPlayable(false)
|
||||
.setMediaType(MediaMetadata.MEDIA_TYPE_FOLDER_MIXED)
|
||||
.build();
|
||||
|
||||
MediaItem mediaItem = new MediaItem.Builder()
|
||||
.setMediaId(prefix + musicFolder.getId())
|
||||
.setMediaMetadata(mediaMetadata)
|
||||
.setUri("")
|
||||
.build();
|
||||
|
||||
mediaItems.add(mediaItem);
|
||||
}
|
||||
|
||||
LibraryResult<ImmutableList<MediaItem>> libraryResult = LibraryResult.ofItemList(ImmutableList.copyOf(mediaItems), null);
|
||||
|
||||
listenableFuture.set(libraryResult);
|
||||
} else {
|
||||
listenableFuture.set(LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
listenableFuture.setException(t);
|
||||
}
|
||||
});
|
||||
|
||||
return listenableFuture;
|
||||
}
|
||||
|
||||
public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> getIndexes(String prefix, String id) {
|
||||
final SettableFuture<LibraryResult<ImmutableList<MediaItem>>> listenableFuture = SettableFuture.create();
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getBrowsingClient()
|
||||
.getIndexes(id, null)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getIndexes() != null) {
|
||||
List<MediaItem> mediaItems = new ArrayList<>();
|
||||
|
||||
if (response.body().getSubsonicResponse().getIndexes().getIndices() != null) {
|
||||
List<Index> indices = response.body().getSubsonicResponse().getIndexes().getIndices();
|
||||
|
||||
for (Index index : indices) {
|
||||
if (index.getArtists() != null) {
|
||||
for (Artist artist : index.getArtists()) {
|
||||
MediaMetadata mediaMetadata = new MediaMetadata.Builder()
|
||||
.setTitle(artist.getName())
|
||||
.setIsBrowsable(true)
|
||||
.setIsPlayable(false)
|
||||
.setMediaType(MediaMetadata.MEDIA_TYPE_ARTIST)
|
||||
.build();
|
||||
|
||||
MediaItem mediaItem = new MediaItem.Builder()
|
||||
.setMediaId(prefix + artist.getId())
|
||||
.setMediaMetadata(mediaMetadata)
|
||||
.setUri("")
|
||||
.build();
|
||||
|
||||
mediaItems.add(mediaItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (response.body().getSubsonicResponse().getIndexes().getChildren() != null) {
|
||||
List<Child> children = response.body().getSubsonicResponse().getIndexes().getChildren();
|
||||
|
||||
for (Child song : children) {
|
||||
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(song.getCoverArtId(), Preferences.getImageSize()));
|
||||
|
||||
MediaMetadata mediaMetadata = new MediaMetadata.Builder()
|
||||
.setTitle(song.getTitle())
|
||||
.setAlbumTitle(song.getAlbum())
|
||||
.setArtist(song.getArtist())
|
||||
.setIsBrowsable(false)
|
||||
.setIsPlayable(true)
|
||||
.setMediaType(MediaMetadata.MEDIA_TYPE_MUSIC)
|
||||
.setArtworkUri(artworkUri)
|
||||
.build();
|
||||
|
||||
MediaItem mediaItem = new MediaItem.Builder()
|
||||
.setMediaId(prefix + song.getId())
|
||||
.setMediaMetadata(mediaMetadata)
|
||||
.setUri(MusicUtil.getStreamUri(song.getId()))
|
||||
.build();
|
||||
|
||||
mediaItems.add(mediaItem);
|
||||
}
|
||||
|
||||
setChildrenMetadata(children);
|
||||
}
|
||||
|
||||
LibraryResult<ImmutableList<MediaItem>> libraryResult = LibraryResult.ofItemList(ImmutableList.copyOf(mediaItems), null);
|
||||
|
||||
listenableFuture.set(libraryResult);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
listenableFuture.setException(t);
|
||||
}
|
||||
});
|
||||
|
||||
return listenableFuture;
|
||||
}
|
||||
|
||||
public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> getDirectories(String prefix, String id) {
|
||||
final SettableFuture<LibraryResult<ImmutableList<MediaItem>>> listenableFuture = SettableFuture.create();
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getBrowsingClient()
|
||||
.getMusicDirectory(id)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getDirectory() != null && response.body().getSubsonicResponse().getDirectory().getChildren() != null) {
|
||||
Directory directory = response.body().getSubsonicResponse().getDirectory();
|
||||
|
||||
List<MediaItem> mediaItems = new ArrayList<>();
|
||||
|
||||
for (Child child : directory.getChildren()) {
|
||||
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(child.getCoverArtId(), Preferences.getImageSize()));
|
||||
|
||||
MediaMetadata mediaMetadata = new MediaMetadata.Builder()
|
||||
.setTitle(child.getTitle())
|
||||
.setIsBrowsable(child.isDir())
|
||||
.setIsPlayable(!child.isDir())
|
||||
.setMediaType(MediaMetadata.MEDIA_TYPE_FOLDER_MIXED)
|
||||
.setArtworkUri(artworkUri)
|
||||
.build();
|
||||
|
||||
MediaItem mediaItem = new MediaItem.Builder()
|
||||
.setMediaId(child.isDir() ? prefix + child.getId() : child.getId())
|
||||
.setMediaMetadata(mediaMetadata)
|
||||
.setUri(!child.isDir() ? MusicUtil.getStreamUri(child.getId()) : Uri.parse(""))
|
||||
.build();
|
||||
|
||||
mediaItems.add(mediaItem);
|
||||
}
|
||||
|
||||
setChildrenMetadata(directory.getChildren().stream().filter(child -> !child.isDir()).collect(Collectors.toList()));
|
||||
|
||||
LibraryResult<ImmutableList<MediaItem>> libraryResult = LibraryResult.ofItemList(ImmutableList.copyOf(mediaItems), null);
|
||||
|
||||
listenableFuture.set(libraryResult);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
listenableFuture.setException(t);
|
||||
}
|
||||
});
|
||||
|
||||
return listenableFuture;
|
||||
}
|
||||
|
||||
public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> getPlaylists(String prefix) {
|
||||
final SettableFuture<LibraryResult<ImmutableList<MediaItem>>> listenableFuture = SettableFuture.create();
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getPlaylistClient()
|
||||
.getPlaylists()
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getPlaylists() != null && response.body().getSubsonicResponse().getPlaylists().getPlaylists() != null) {
|
||||
List<Playlist> playlists = response.body().getSubsonicResponse().getPlaylists().getPlaylists();
|
||||
|
||||
List<MediaItem> mediaItems = new ArrayList<>();
|
||||
|
||||
for (Playlist playlist : playlists) {
|
||||
MediaMetadata mediaMetadata = new MediaMetadata.Builder()
|
||||
.setTitle(playlist.getName())
|
||||
.setIsBrowsable(true)
|
||||
.setIsPlayable(false)
|
||||
.setMediaType(MediaMetadata.MEDIA_TYPE_PLAYLIST)
|
||||
.build();
|
||||
|
||||
MediaItem mediaItem = new MediaItem.Builder()
|
||||
.setMediaId(prefix + playlist.getId())
|
||||
.setMediaMetadata(mediaMetadata)
|
||||
.setUri("")
|
||||
.build();
|
||||
|
||||
mediaItems.add(mediaItem);
|
||||
}
|
||||
|
||||
LibraryResult<ImmutableList<MediaItem>> libraryResult = LibraryResult.ofItemList(ImmutableList.copyOf(mediaItems), null);
|
||||
|
||||
listenableFuture.set(libraryResult);
|
||||
} else {
|
||||
listenableFuture.set(LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
listenableFuture.setException(t);
|
||||
}
|
||||
});
|
||||
|
||||
return listenableFuture;
|
||||
}
|
||||
|
||||
public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> getNewestPodcastEpisodes(int count) {
|
||||
final SettableFuture<LibraryResult<ImmutableList<MediaItem>>> listenableFuture = SettableFuture.create();
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getPodcastClient()
|
||||
.getNewestPodcasts(count)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getNewestPodcasts() != null && response.body().getSubsonicResponse().getNewestPodcasts().getEpisodes() != null) {
|
||||
List<PodcastEpisode> episodes = response.body().getSubsonicResponse().getNewestPodcasts().getEpisodes();
|
||||
|
||||
List<MediaItem> mediaItems = new ArrayList<>();
|
||||
|
||||
for (PodcastEpisode episode : episodes) {
|
||||
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(episode.getCoverArtId(), Preferences.getImageSize()));
|
||||
|
||||
MediaMetadata mediaMetadata = new MediaMetadata.Builder()
|
||||
.setTitle(episode.getTitle())
|
||||
.setIsBrowsable(false)
|
||||
.setIsPlayable(true)
|
||||
.setMediaType(MediaMetadata.MEDIA_TYPE_PODCAST_EPISODE)
|
||||
.setArtworkUri(artworkUri)
|
||||
.build();
|
||||
|
||||
MediaItem mediaItem = new MediaItem.Builder()
|
||||
.setMediaId(episode.getId())
|
||||
.setMediaMetadata(mediaMetadata)
|
||||
.setUri(MusicUtil.getStreamUri(episode.getStreamId()))
|
||||
.build();
|
||||
|
||||
mediaItems.add(mediaItem);
|
||||
}
|
||||
|
||||
setPodcastEpisodesMetadata(episodes);
|
||||
|
||||
LibraryResult<ImmutableList<MediaItem>> libraryResult = LibraryResult.ofItemList(ImmutableList.copyOf(mediaItems), null);
|
||||
|
||||
listenableFuture.set(libraryResult);
|
||||
} else {
|
||||
listenableFuture.set(LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
listenableFuture.setException(t);
|
||||
}
|
||||
});
|
||||
|
||||
return listenableFuture;
|
||||
}
|
||||
|
||||
public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> getInternetRadioStations() {
|
||||
final SettableFuture<LibraryResult<ImmutableList<MediaItem>>> listenableFuture = SettableFuture.create();
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getInternetRadioClient()
|
||||
.getInternetRadioStations()
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getInternetRadioStations() != null && response.body().getSubsonicResponse().getInternetRadioStations().getInternetRadioStations() != null) {
|
||||
|
||||
List<InternetRadioStation> radioStations = response.body().getSubsonicResponse().getInternetRadioStations().getInternetRadioStations();
|
||||
|
||||
List<MediaItem> mediaItems = new ArrayList<>();
|
||||
|
||||
for (InternetRadioStation radioStation : radioStations) {
|
||||
MediaMetadata mediaMetadata = new MediaMetadata.Builder()
|
||||
.setTitle(radioStation.getName())
|
||||
.setIsBrowsable(false)
|
||||
.setIsPlayable(true)
|
||||
.setMediaType(MediaMetadata.MEDIA_TYPE_RADIO_STATION)
|
||||
.build();
|
||||
|
||||
MediaItem mediaItem = new MediaItem.Builder()
|
||||
.setMediaId(radioStation.getId())
|
||||
.setMediaMetadata(mediaMetadata)
|
||||
.setUri(radioStation.getStreamUrl())
|
||||
.build();
|
||||
|
||||
mediaItems.add(mediaItem);
|
||||
}
|
||||
|
||||
setInternetRadioStationsMetadata(radioStations);
|
||||
|
||||
LibraryResult<ImmutableList<MediaItem>> libraryResult = LibraryResult.ofItemList(ImmutableList.copyOf(mediaItems), null);
|
||||
|
||||
listenableFuture.set(libraryResult);
|
||||
} else {
|
||||
listenableFuture.set(LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
listenableFuture.setException(t);
|
||||
}
|
||||
});
|
||||
|
||||
return listenableFuture;
|
||||
}
|
||||
|
||||
public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> getAlbumTracks(String id) {
|
||||
final SettableFuture<LibraryResult<ImmutableList<MediaItem>>> listenableFuture = SettableFuture.create();
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getBrowsingClient()
|
||||
.getAlbum(id)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getAlbum() != null && response.body().getSubsonicResponse().getAlbum().getSongs() != null) {
|
||||
List<Child> tracks = response.body().getSubsonicResponse().getAlbum().getSongs();
|
||||
|
||||
setChildrenMetadata(tracks);
|
||||
|
||||
List<MediaItem> mediaItems = MappingUtil.mapMediaItems(tracks);
|
||||
|
||||
LibraryResult<ImmutableList<MediaItem>> libraryResult = LibraryResult.ofItemList(ImmutableList.copyOf(mediaItems), null);
|
||||
|
||||
listenableFuture.set(libraryResult);
|
||||
} else {
|
||||
listenableFuture.set(LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
listenableFuture.setException(t);
|
||||
}
|
||||
});
|
||||
|
||||
return listenableFuture;
|
||||
}
|
||||
|
||||
public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> getArtistAlbum(String prefix, String id) {
|
||||
final SettableFuture<LibraryResult<ImmutableList<MediaItem>>> listenableFuture = SettableFuture.create();
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getBrowsingClient()
|
||||
.getArtist(id)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getArtist() != null && response.body().getSubsonicResponse().getArtist().getAlbums() != null) {
|
||||
List<AlbumID3> albums = response.body().getSubsonicResponse().getArtist().getAlbums();
|
||||
|
||||
List<MediaItem> mediaItems = new ArrayList<>();
|
||||
|
||||
for (AlbumID3 album : albums) {
|
||||
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(album.getCoverArtId(), Preferences.getImageSize()));
|
||||
|
||||
MediaMetadata mediaMetadata = new MediaMetadata.Builder()
|
||||
.setTitle(album.getName())
|
||||
.setAlbumTitle(album.getName())
|
||||
.setArtist(album.getArtist())
|
||||
.setGenre(album.getGenre())
|
||||
.setIsBrowsable(true)
|
||||
.setIsPlayable(false)
|
||||
.setMediaType(MediaMetadata.MEDIA_TYPE_ALBUM)
|
||||
.setArtworkUri(artworkUri)
|
||||
.build();
|
||||
|
||||
MediaItem mediaItem = new MediaItem.Builder()
|
||||
.setMediaId(prefix + album.getId())
|
||||
.setMediaMetadata(mediaMetadata)
|
||||
.setUri("")
|
||||
.build();
|
||||
|
||||
mediaItems.add(mediaItem);
|
||||
}
|
||||
|
||||
LibraryResult<ImmutableList<MediaItem>> libraryResult = LibraryResult.ofItemList(ImmutableList.copyOf(mediaItems), null);
|
||||
|
||||
listenableFuture.set(libraryResult);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
listenableFuture.setException(t);
|
||||
}
|
||||
});
|
||||
|
||||
return listenableFuture;
|
||||
}
|
||||
|
||||
public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> getPlaylistSongs(String id) {
|
||||
final SettableFuture<LibraryResult<ImmutableList<MediaItem>>> listenableFuture = SettableFuture.create();
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getPlaylistClient()
|
||||
.getPlaylist(id)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getPlaylist() != null && response.body().getSubsonicResponse().getPlaylist().getEntries() != null) {
|
||||
List<Child> tracks = response.body().getSubsonicResponse().getPlaylist().getEntries();
|
||||
|
||||
setChildrenMetadata(tracks);
|
||||
|
||||
List<MediaItem> mediaItems = MappingUtil.mapMediaItems(tracks);
|
||||
|
||||
LibraryResult<ImmutableList<MediaItem>> libraryResult = LibraryResult.ofItemList(ImmutableList.copyOf(mediaItems), null);
|
||||
|
||||
listenableFuture.set(libraryResult);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
listenableFuture.setException(t);
|
||||
}
|
||||
});
|
||||
|
||||
return listenableFuture;
|
||||
}
|
||||
|
||||
public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> getMadeForYou(String id, int count) {
|
||||
final SettableFuture<LibraryResult<ImmutableList<MediaItem>>> listenableFuture = SettableFuture.create();
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getBrowsingClient()
|
||||
.getSimilarSongs2(id, count)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getSimilarSongs2() != null && response.body().getSubsonicResponse().getSimilarSongs2().getSongs() != null) {
|
||||
List<Child> tracks = response.body().getSubsonicResponse().getSimilarSongs2().getSongs();
|
||||
|
||||
setChildrenMetadata(tracks);
|
||||
|
||||
List<MediaItem> mediaItems = MappingUtil.mapMediaItems(tracks);
|
||||
|
||||
LibraryResult<ImmutableList<MediaItem>> libraryResult = LibraryResult.ofItemList(ImmutableList.copyOf(mediaItems), null);
|
||||
|
||||
listenableFuture.set(libraryResult);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
listenableFuture.setException(t);
|
||||
}
|
||||
});
|
||||
|
||||
return listenableFuture;
|
||||
}
|
||||
|
||||
public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> search(String query, String albumPrefix, String artistPrefix) {
|
||||
final SettableFuture<LibraryResult<ImmutableList<MediaItem>>> listenableFuture = SettableFuture.create();
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getSearchingClient()
|
||||
.search3(query, 20, 20, 20)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getSearchResult3() != null) {
|
||||
List<MediaItem> mediaItems = new ArrayList<>();
|
||||
|
||||
if (response.body().getSubsonicResponse().getSearchResult3().getArtists() != null) {
|
||||
for (ArtistID3 artist : response.body().getSubsonicResponse().getSearchResult3().getArtists()) {
|
||||
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(artist.getCoverArtId(), Preferences.getImageSize()));
|
||||
|
||||
MediaMetadata mediaMetadata = new MediaMetadata.Builder()
|
||||
.setTitle(artist.getName())
|
||||
.setIsBrowsable(true)
|
||||
.setIsPlayable(false)
|
||||
.setMediaType(MediaMetadata.MEDIA_TYPE_PLAYLIST)
|
||||
.setArtworkUri(artworkUri)
|
||||
.build();
|
||||
|
||||
MediaItem mediaItem = new MediaItem.Builder()
|
||||
.setMediaId(artistPrefix + artist.getId())
|
||||
.setMediaMetadata(mediaMetadata)
|
||||
.setUri("")
|
||||
.build();
|
||||
|
||||
mediaItems.add(mediaItem);
|
||||
}
|
||||
}
|
||||
|
||||
if (response.body().getSubsonicResponse().getSearchResult3().getAlbums() != null) {
|
||||
for (AlbumID3 album : response.body().getSubsonicResponse().getSearchResult3().getAlbums()) {
|
||||
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(album.getCoverArtId(), Preferences.getImageSize()));
|
||||
|
||||
MediaMetadata mediaMetadata = new MediaMetadata.Builder()
|
||||
.setTitle(album.getName())
|
||||
.setAlbumTitle(album.getName())
|
||||
.setArtist(album.getArtist())
|
||||
.setGenre(album.getGenre())
|
||||
.setIsBrowsable(true)
|
||||
.setIsPlayable(false)
|
||||
.setMediaType(MediaMetadata.MEDIA_TYPE_ALBUM)
|
||||
.setArtworkUri(artworkUri)
|
||||
.build();
|
||||
|
||||
MediaItem mediaItem = new MediaItem.Builder()
|
||||
.setMediaId(albumPrefix + album.getId())
|
||||
.setMediaMetadata(mediaMetadata)
|
||||
.setUri("")
|
||||
.build();
|
||||
|
||||
mediaItems.add(mediaItem);
|
||||
}
|
||||
}
|
||||
|
||||
if (response.body().getSubsonicResponse().getSearchResult3().getSongs() != null) {
|
||||
List<Child> tracks = response.body().getSubsonicResponse().getSearchResult3().getSongs();
|
||||
setChildrenMetadata(tracks);
|
||||
mediaItems.addAll(MappingUtil.mapMediaItems(tracks));
|
||||
}
|
||||
|
||||
LibraryResult<ImmutableList<MediaItem>> libraryResult = LibraryResult.ofItemList(ImmutableList.copyOf(mediaItems), null);
|
||||
|
||||
listenableFuture.set(libraryResult);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
listenableFuture.setException(t);
|
||||
}
|
||||
});
|
||||
|
||||
return listenableFuture;
|
||||
}
|
||||
|
||||
@OptIn(markerClass = UnstableApi.class)
|
||||
public void setChildrenMetadata(List<Child> children) {
|
||||
long timestamp = System.currentTimeMillis();
|
||||
ArrayList<SessionMediaItem> sessionMediaItems = new ArrayList<>();
|
||||
|
||||
for (Child child : children) {
|
||||
SessionMediaItem sessionMediaItem = new SessionMediaItem(child);
|
||||
sessionMediaItem.setTimestamp(timestamp);
|
||||
sessionMediaItems.add(sessionMediaItem);
|
||||
}
|
||||
|
||||
InsertAllThreadSafe insertAll = new InsertAllThreadSafe(sessionMediaItemDao, sessionMediaItems);
|
||||
Thread thread = new Thread(insertAll);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
@OptIn(markerClass = UnstableApi.class)
|
||||
public void setPodcastEpisodesMetadata(List<PodcastEpisode> podcastEpisodes) {
|
||||
long timestamp = System.currentTimeMillis();
|
||||
ArrayList<SessionMediaItem> sessionMediaItems = new ArrayList<>();
|
||||
|
||||
for (PodcastEpisode podcastEpisode : podcastEpisodes) {
|
||||
SessionMediaItem sessionMediaItem = new SessionMediaItem(podcastEpisode);
|
||||
sessionMediaItem.setTimestamp(timestamp);
|
||||
sessionMediaItems.add(sessionMediaItem);
|
||||
}
|
||||
|
||||
InsertAllThreadSafe insertAll = new InsertAllThreadSafe(sessionMediaItemDao, sessionMediaItems);
|
||||
Thread thread = new Thread(insertAll);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
@OptIn(markerClass = UnstableApi.class)
|
||||
public void setInternetRadioStationsMetadata(List<InternetRadioStation> internetRadioStations) {
|
||||
long timestamp = System.currentTimeMillis();
|
||||
ArrayList<SessionMediaItem> sessionMediaItems = new ArrayList<>();
|
||||
|
||||
for (InternetRadioStation internetRadioStation : internetRadioStations) {
|
||||
SessionMediaItem sessionMediaItem = new SessionMediaItem(internetRadioStation);
|
||||
sessionMediaItem.setTimestamp(timestamp);
|
||||
sessionMediaItems.add(sessionMediaItem);
|
||||
}
|
||||
|
||||
InsertAllThreadSafe insertAll = new InsertAllThreadSafe(sessionMediaItemDao, sessionMediaItems);
|
||||
Thread thread = new Thread(insertAll);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
public SessionMediaItem getSessionMediaItem(String id) {
|
||||
SessionMediaItem sessionMediaItem = null;
|
||||
|
||||
GetMediaItemThreadSafe getMediaItemThreadSafe = new GetMediaItemThreadSafe(sessionMediaItemDao, id);
|
||||
Thread thread = new Thread(getMediaItemThreadSafe);
|
||||
thread.start();
|
||||
|
||||
try {
|
||||
thread.join();
|
||||
sessionMediaItem = getMediaItemThreadSafe.getSessionMediaItem();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return sessionMediaItem;
|
||||
}
|
||||
|
||||
public List<MediaItem> getMetadatas(long timestamp) {
|
||||
List<MediaItem> mediaItems = Collections.emptyList();
|
||||
|
||||
GetMediaItemsThreadSafe getMediaItemsThreadSafe = new GetMediaItemsThreadSafe(sessionMediaItemDao, timestamp);
|
||||
Thread thread = new Thread(getMediaItemsThreadSafe);
|
||||
thread.start();
|
||||
|
||||
try {
|
||||
thread.join();
|
||||
mediaItems = getMediaItemsThreadSafe.getMediaItems();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return mediaItems;
|
||||
}
|
||||
|
||||
public void deleteMetadata() {
|
||||
DeleteAllThreadSafe delete = new DeleteAllThreadSafe(sessionMediaItemDao);
|
||||
Thread thread = new Thread(delete);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
private static class GetMediaItemThreadSafe implements Runnable {
|
||||
private final SessionMediaItemDao sessionMediaItemDao;
|
||||
private final String id;
|
||||
|
||||
private SessionMediaItem sessionMediaItem;
|
||||
|
||||
public GetMediaItemThreadSafe(SessionMediaItemDao sessionMediaItemDao, String id) {
|
||||
this.sessionMediaItemDao = sessionMediaItemDao;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
sessionMediaItem = sessionMediaItemDao.get(id);
|
||||
}
|
||||
|
||||
public SessionMediaItem getSessionMediaItem() {
|
||||
return sessionMediaItem;
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(markerClass = UnstableApi.class)
|
||||
private static class GetMediaItemsThreadSafe implements Runnable {
|
||||
private final SessionMediaItemDao sessionMediaItemDao;
|
||||
private final Long timestamp;
|
||||
private final List<MediaItem> mediaItems = new ArrayList<>();
|
||||
|
||||
public GetMediaItemsThreadSafe(SessionMediaItemDao sessionMediaItemDao, Long timestamp) {
|
||||
this.sessionMediaItemDao = sessionMediaItemDao;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
List<SessionMediaItem> sessionMediaItems = sessionMediaItemDao.get(timestamp);
|
||||
sessionMediaItems.forEach(sessionMediaItem -> mediaItems.add(sessionMediaItem.getMediaItem()));
|
||||
}
|
||||
|
||||
public List<MediaItem> getMediaItems() {
|
||||
return mediaItems;
|
||||
}
|
||||
}
|
||||
|
||||
private static class InsertAllThreadSafe implements Runnable {
|
||||
private final SessionMediaItemDao sessionMediaItemDao;
|
||||
private final List<SessionMediaItem> sessionMediaItems;
|
||||
|
||||
public InsertAllThreadSafe(SessionMediaItemDao sessionMediaItemDao, List<SessionMediaItem> sessionMediaItems) {
|
||||
this.sessionMediaItemDao = sessionMediaItemDao;
|
||||
this.sessionMediaItems = sessionMediaItems;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
sessionMediaItemDao.insertAll(sessionMediaItems);
|
||||
}
|
||||
}
|
||||
|
||||
private static class DeleteAllThreadSafe implements Runnable {
|
||||
private final SessionMediaItemDao sessionMediaItemDao;
|
||||
|
||||
public DeleteAllThreadSafe(SessionMediaItemDao sessionMediaItemDao) {
|
||||
this.sessionMediaItemDao = sessionMediaItemDao;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
sessionMediaItemDao.deleteAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,9 @@ import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Genre;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
@@ -39,7 +41,7 @@ public class GenreRepository {
|
||||
if (size != -1) {
|
||||
genres.setValue(genreList.subList(0, Math.min(size, genreList.size())));
|
||||
} else {
|
||||
genres.setValue(genreList);
|
||||
genres.setValue(genreList.stream().sorted(Comparator.comparing(Genre::getGenre)).collect(Collectors.toList()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package com.cappielloantonio.tempo.repository;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
@@ -95,12 +93,29 @@ public class PlaylistRepository {
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
Log.d("PLAYLIST", response.toString());
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
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) {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ public class PodcastRepository {
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
Log.d(TAG, "onFailure()");
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package com.cappielloantonio.tempo.repository;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
@@ -36,7 +34,9 @@ public class SearchingRepository {
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
result.setValue(response.body().getSubsonicResponse().getSearchResult2());
|
||||
if (response.isSuccessful() && response.body() != null) {
|
||||
result.setValue(response.body().getSubsonicResponse().getSearchResult2());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -57,7 +57,9 @@ public class SearchingRepository {
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
result.setValue(response.body().getSubsonicResponse().getSearchResult3());
|
||||
if (response.isSuccessful() && response.body() != null) {
|
||||
result.setValue(response.body().getSubsonicResponse().getSearchResult3());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -80,7 +82,7 @@ public class SearchingRepository {
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
List<String> newSuggestions = new ArrayList();
|
||||
|
||||
if (response.isSuccessful() && response.body() != null) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getSearchResult3() != null) {
|
||||
if (response.body().getSubsonicResponse().getSearchResult3().getArtists() != null) {
|
||||
for (ArtistID3 artistID3 : response.body().getSubsonicResponse().getSearchResult3().getArtists()) {
|
||||
newSuggestions.add(artistID3.getName());
|
||||
@@ -102,8 +104,6 @@ public class SearchingRepository {
|
||||
LinkedHashSet<String> hashSet = new LinkedHashSet<>(newSuggestions);
|
||||
ArrayList<String> suggestionsWithoutDuplicates = new ArrayList<>(hashSet);
|
||||
|
||||
Log.d("suggestionsWithoutDuplicates", suggestionsWithoutDuplicates.toString());
|
||||
|
||||
suggestions.setValue(suggestionsWithoutDuplicates);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
package com.cappielloantonio.tempo.repository;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import com.cappielloantonio.tempo.App;
|
||||
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Share;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
|
||||
public class SharingRepository {
|
||||
public MutableLiveData<List<Share>> getShares() {
|
||||
MutableLiveData<List<Share>> shares = new MutableLiveData<>(new ArrayList<>());
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getSharingClient()
|
||||
.getShares()
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getShares() != null && response.body().getSubsonicResponse().getShares().getShares() != null) {
|
||||
shares.setValue(response.body().getSubsonicResponse().getShares().getShares());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
return shares;
|
||||
}
|
||||
|
||||
public MutableLiveData<Share> createShare(String id, String description, Long expires) {
|
||||
MutableLiveData<Share> share = new MutableLiveData<>();
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getSharingClient()
|
||||
.createShare(id, description, expires)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getShares() != null && response.body().getSubsonicResponse().getShares().getShares() != null && response.body().getSubsonicResponse().getShares().getShares().get(0) != null) {
|
||||
share.setValue(response.body().getSubsonicResponse().getShares().getShares().get(0));
|
||||
} else {
|
||||
share.setValue(null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
share.setValue(null);
|
||||
}
|
||||
});
|
||||
|
||||
return share;
|
||||
}
|
||||
|
||||
public void updateShare(String id, String description, Long expires) {
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getSharingClient()
|
||||
.updateShare(id, description, expires)
|
||||
.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 deleteShare(String id) {
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getSharingClient()
|
||||
.deleteShare(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) {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -97,17 +97,17 @@ public class SongRepository {
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
Log.d(TAG, "onFailure: ");
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
return randomSongsSample;
|
||||
}
|
||||
|
||||
public void scrobble(String id) {
|
||||
public void scrobble(String id, boolean submission) {
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getMediaAnnotationClient()
|
||||
.scrobble(id)
|
||||
.scrobble(id, submission)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
@@ -205,7 +205,7 @@ public class SongRepository {
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
Log.d(TAG, "onFailure: ");
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -31,9 +31,10 @@ public class DownloaderManager {
|
||||
|
||||
private final Context context;
|
||||
private final DataSource.Factory dataSourceFactory;
|
||||
private final HashMap<String, Download> downloads;
|
||||
private final DownloadIndex downloadIndex;
|
||||
|
||||
private static HashMap<String, Download> downloads;
|
||||
|
||||
public DownloaderManager(Context context, DataSource.Factory dataSourceFactory, DownloadManager downloadManager) {
|
||||
this.context = context.getApplicationContext();
|
||||
this.dataSourceFactory = dataSourceFactory;
|
||||
@@ -61,19 +62,11 @@ public class DownloaderManager {
|
||||
}
|
||||
|
||||
public boolean isDownloaded(MediaItem mediaItem) {
|
||||
@Nullable Download download = downloads.get(mediaItem.mediaId);
|
||||
return download != null && download.state != Download.STATE_FAILED;
|
||||
return isDownloaded(mediaItem.mediaId);
|
||||
}
|
||||
|
||||
public boolean areDownloaded(List<MediaItem> mediaItems) {
|
||||
for (MediaItem mediaItem : mediaItems) {
|
||||
@Nullable Download download = downloads.get(mediaItem.mediaId);
|
||||
if (download != null && download.state != Download.STATE_FAILED) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return mediaItems.stream().anyMatch(this::isDownloaded);
|
||||
}
|
||||
|
||||
public void download(MediaItem mediaItem, com.cappielloantonio.tempo.model.Download download) {
|
||||
@@ -92,6 +85,7 @@ public class DownloaderManager {
|
||||
public void remove(MediaItem mediaItem, com.cappielloantonio.tempo.model.Download download) {
|
||||
DownloadService.sendRemoveDownload(context, DownloaderService.class, buildDownloadRequest(mediaItem).id, false);
|
||||
deleteDatabase(download.getId());
|
||||
downloads.remove(download.getId());
|
||||
}
|
||||
|
||||
public void remove(List<MediaItem> mediaItems, List<com.cappielloantonio.tempo.model.Download> downloads) {
|
||||
@@ -122,23 +116,33 @@ public class DownloaderManager {
|
||||
return download != null ? download.getTitle() : null;
|
||||
}
|
||||
|
||||
public static void updateRequestDownload(Download download) {
|
||||
updateDatabase(download.request.id);
|
||||
downloads.put(download.request.id, download);
|
||||
}
|
||||
|
||||
public static void removeRequestDownload(Download download) {
|
||||
deleteDatabase(download.request.id);
|
||||
downloads.remove(download.request.id);
|
||||
}
|
||||
|
||||
private static DownloadRepository getDownloadRepository() {
|
||||
return new DownloadRepository();
|
||||
}
|
||||
|
||||
public static void insertDatabase(com.cappielloantonio.tempo.model.Download download) {
|
||||
private static void insertDatabase(com.cappielloantonio.tempo.model.Download download) {
|
||||
getDownloadRepository().insert(download);
|
||||
}
|
||||
|
||||
public static void deleteDatabase(String id) {
|
||||
private static void deleteDatabase(String id) {
|
||||
getDownloadRepository().delete(id);
|
||||
}
|
||||
|
||||
public static void deleteAllDatabase() {
|
||||
private static void deleteAllDatabase() {
|
||||
getDownloadRepository().deleteAll();
|
||||
}
|
||||
|
||||
public static void updateDatabase(String id) {
|
||||
private static void updateDatabase(String id) {
|
||||
getDownloadRepository().update(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,17 +51,40 @@ public class DownloaderService extends androidx.media3.exoplayer.offline.Downloa
|
||||
}
|
||||
|
||||
private static final class TerminalStateNotificationHelper implements DownloadManager.Listener {
|
||||
private static final String TAG = "TerminalStateNotificatinHelper";
|
||||
|
||||
private final Context context;
|
||||
private final DownloadNotificationHelper notificationHelper;
|
||||
|
||||
private final Notification successfulDownloadGroupNotification;
|
||||
private final Notification failedDownloadGroupNotification;
|
||||
|
||||
private final int successfulDownloadGroupNotificationId;
|
||||
private final int failedDownloadGroupNotificationId;
|
||||
|
||||
private int nextNotificationId;
|
||||
|
||||
public TerminalStateNotificationHelper(Context context, DownloadNotificationHelper notificationHelper, int firstNotificationId) {
|
||||
this.context = context.getApplicationContext();
|
||||
this.notificationHelper = notificationHelper;
|
||||
nextNotificationId = firstNotificationId;
|
||||
|
||||
successfulDownloadGroupNotification = DownloadUtil.buildGroupSummaryNotification(
|
||||
this.context,
|
||||
DownloadUtil.DOWNLOAD_NOTIFICATION_CHANNEL_ID,
|
||||
DownloadUtil.DOWNLOAD_NOTIFICATION_SUCCESSFUL_GROUP,
|
||||
R.drawable.ic_check_circle,
|
||||
"Downloads completed"
|
||||
);
|
||||
|
||||
failedDownloadGroupNotification = DownloadUtil.buildGroupSummaryNotification(
|
||||
this.context,
|
||||
DownloadUtil.DOWNLOAD_NOTIFICATION_CHANNEL_ID,
|
||||
DownloadUtil.DOWNLOAD_NOTIFICATION_FAILED_GROUP,
|
||||
R.drawable.ic_error,
|
||||
"Downloads failed"
|
||||
);
|
||||
|
||||
successfulDownloadGroupNotificationId = nextNotificationId++;
|
||||
failedDownloadGroupNotificationId = nextNotificationId++;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -70,9 +93,13 @@ public class DownloaderService extends androidx.media3.exoplayer.offline.Downloa
|
||||
|
||||
if (download.state == Download.STATE_COMPLETED) {
|
||||
notification = notificationHelper.buildDownloadCompletedNotification(context, R.drawable.ic_check_circle, null, DownloaderManager.getDownloadNotificationMessage(download.request.id));
|
||||
DownloaderManager.updateDatabase(download.request.id);
|
||||
notification = Notification.Builder.recoverBuilder(context, notification).setGroup(DownloadUtil.DOWNLOAD_NOTIFICATION_SUCCESSFUL_GROUP).build();
|
||||
NotificationUtil.setNotification(this.context, successfulDownloadGroupNotificationId, successfulDownloadGroupNotification);
|
||||
DownloaderManager.updateRequestDownload(download);
|
||||
} else if (download.state == Download.STATE_FAILED) {
|
||||
notification = notificationHelper.buildDownloadFailedNotification(context, R.drawable.ic_error, null, DownloaderManager.getDownloadNotificationMessage(download.request.id));
|
||||
notification = Notification.Builder.recoverBuilder(context, notification).setGroup(DownloadUtil.DOWNLOAD_NOTIFICATION_FAILED_GROUP).build();
|
||||
NotificationUtil.setNotification(this.context, failedDownloadGroupNotificationId, failedDownloadGroupNotification);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
@@ -82,7 +109,7 @@ public class DownloaderService extends androidx.media3.exoplayer.offline.Downloa
|
||||
|
||||
@Override
|
||||
public void onDownloadRemoved(@NonNull DownloadManager downloadManager, Download download) {
|
||||
DownloaderManager.deleteDatabase(download.request.id);
|
||||
DownloaderManager.removeRequestDownload(download);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||
import com.cappielloantonio.tempo.subsonic.models.InternetRadioStation;
|
||||
import com.cappielloantonio.tempo.subsonic.models.PodcastEpisode;
|
||||
import com.cappielloantonio.tempo.util.MappingUtil;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.common.util.concurrent.MoreExecutors;
|
||||
|
||||
@@ -204,6 +205,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) {
|
||||
if (mediaBrowserListenableFuture != null) {
|
||||
mediaBrowserListenableFuture.addListener(() -> {
|
||||
@@ -238,6 +255,21 @@ public class MediaManager {
|
||||
}
|
||||
}
|
||||
|
||||
public static void removeRange(ListenableFuture<MediaBrowser> mediaBrowserListenableFuture, List<Child> media, int fromItem, int toItem) {
|
||||
if (mediaBrowserListenableFuture != null) {
|
||||
mediaBrowserListenableFuture.addListener(() -> {
|
||||
try {
|
||||
if (mediaBrowserListenableFuture.isDone()) {
|
||||
mediaBrowserListenableFuture.get().removeMediaItems(fromItem, toItem);
|
||||
removeRangeDatabase(media, fromItem, toItem);
|
||||
}
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}, MoreExecutors.directExecutor());
|
||||
}
|
||||
}
|
||||
|
||||
public static void getCurrentIndex(ListenableFuture<MediaBrowser> mediaBrowserListenableFuture, MediaIndexCallback callback) {
|
||||
if (mediaBrowserListenableFuture != null) {
|
||||
mediaBrowserListenableFuture.addListener(() -> {
|
||||
@@ -261,9 +293,9 @@ public class MediaManager {
|
||||
getQueueRepository().setPlayingPausedTimestamp(mediaItem.mediaId, ms);
|
||||
}
|
||||
|
||||
public static void scrobble(MediaItem mediaItem) {
|
||||
if (mediaItem != null) {
|
||||
getSongRepository().scrobble(mediaItem.mediaMetadata.extras.getString("id"));
|
||||
public static void scrobble(MediaItem mediaItem, boolean submission) {
|
||||
if (mediaItem != null && Preferences.isScrobblingEnabled()) {
|
||||
getSongRepository().scrobble(mediaItem.mediaMetadata.extras.getString("id"), submission);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -304,6 +336,14 @@ public class MediaManager {
|
||||
}
|
||||
}
|
||||
|
||||
private static void removeRangeDatabase(List<Child> media, int fromItem, int toItem) {
|
||||
List<Child> toRemove = media.subList(fromItem, toItem);
|
||||
|
||||
media.removeAll(toRemove);
|
||||
|
||||
getQueueRepository().insertAll(media, true, 0);
|
||||
}
|
||||
|
||||
public static void clearDatabase() {
|
||||
getQueueRepository().deleteAll();
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import com.cappielloantonio.tempo.subsonic.api.mediaretrieval.MediaRetrievalClie
|
||||
import com.cappielloantonio.tempo.subsonic.api.playlist.PlaylistClient;
|
||||
import com.cappielloantonio.tempo.subsonic.api.podcast.PodcastClient;
|
||||
import com.cappielloantonio.tempo.subsonic.api.searching.SearchingClient;
|
||||
import com.cappielloantonio.tempo.subsonic.api.sharing.SharingClient;
|
||||
import com.cappielloantonio.tempo.subsonic.api.system.SystemClient;
|
||||
import com.cappielloantonio.tempo.subsonic.base.Version;
|
||||
|
||||
@@ -33,6 +34,7 @@ public class Subsonic {
|
||||
private MediaLibraryScanningClient mediaLibraryScanningClient;
|
||||
private BookmarksClient bookmarksClient;
|
||||
private InternetRadioClient internetRadioClient;
|
||||
private SharingClient sharingClient;
|
||||
|
||||
public Subsonic(SubsonicPreferences preferences) {
|
||||
this.preferences = preferences;
|
||||
@@ -119,6 +121,13 @@ public class Subsonic {
|
||||
return internetRadioClient;
|
||||
}
|
||||
|
||||
public SharingClient getSharingClient() {
|
||||
if (sharingClient == null) {
|
||||
sharingClient = new SharingClient(this);
|
||||
}
|
||||
return sharingClient;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
String url = preferences.getServerUrl() + "/rest/";
|
||||
return url.replace("//rest", "/rest");
|
||||
|
||||
@@ -9,7 +9,7 @@ import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
||||
import retrofit2.Call;
|
||||
|
||||
public class MediaAnnotationClient {
|
||||
private static final String TAG = "BrowsingClient";
|
||||
private static final String TAG = "MediaAnnotationClient";
|
||||
|
||||
private final Subsonic subsonic;
|
||||
private final MediaAnnotationService mediaAnnotationService;
|
||||
@@ -34,8 +34,8 @@ public class MediaAnnotationClient {
|
||||
return mediaAnnotationService.setRating(subsonic.getParams(), id, rating);
|
||||
}
|
||||
|
||||
public Call<ApiResponse> scrobble(String id) {
|
||||
public Call<ApiResponse> scrobble(String id, boolean submission) {
|
||||
Log.d(TAG, "scrobble()");
|
||||
return mediaAnnotationService.scrobble(subsonic.getParams(), id);
|
||||
return mediaAnnotationService.scrobble(subsonic.getParams(), id, submission);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,5 +20,5 @@ public interface MediaAnnotationService {
|
||||
Call<ApiResponse> setRating(@QueryMap Map<String, String> params, @Query("id") String id, @Query("rating") int rating);
|
||||
|
||||
@GET("scrobble")
|
||||
Call<ApiResponse> scrobble(@QueryMap Map<String, String> params, @Query("id") String id);
|
||||
Call<ApiResponse> scrobble(@QueryMap Map<String, String> params, @Query("id") String id, @Query("submission") Boolean submission);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
||||
import retrofit2.Call;
|
||||
|
||||
public class MediaLibraryScanningClient {
|
||||
private static final String TAG = "SystemClient";
|
||||
private static final String TAG = "MediaLibraryScanningClient";
|
||||
|
||||
private final Subsonic subsonic;
|
||||
private final MediaLibraryScanningService mediaLibraryScanningService;
|
||||
|
||||
@@ -9,7 +9,7 @@ import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
||||
import retrofit2.Call;
|
||||
|
||||
public class PodcastClient {
|
||||
private static final String TAG = "SystemClient";
|
||||
private static final String TAG = "PodcastClient";
|
||||
|
||||
private final Subsonic subsonic;
|
||||
private final PodcastService podcastService;
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.cappielloantonio.tempo.subsonic.api.sharing;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.cappielloantonio.tempo.subsonic.RetrofitClient;
|
||||
import com.cappielloantonio.tempo.subsonic.Subsonic;
|
||||
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
||||
|
||||
import retrofit2.Call;
|
||||
|
||||
public class SharingClient {
|
||||
private static final String TAG = "BrowsingClient";
|
||||
|
||||
private final Subsonic subsonic;
|
||||
private final SharingService sharingService;
|
||||
|
||||
public SharingClient(Subsonic subsonic) {
|
||||
this.subsonic = subsonic;
|
||||
this.sharingService = new RetrofitClient(subsonic).getRetrofit().create(SharingService.class);
|
||||
}
|
||||
|
||||
public Call<ApiResponse> getShares() {
|
||||
Log.d(TAG, "getShares()");
|
||||
return sharingService.getShares(subsonic.getParams());
|
||||
}
|
||||
|
||||
public Call<ApiResponse> createShare(String id, String description, Long expires) {
|
||||
Log.d(TAG, "createShare()");
|
||||
return sharingService.createShare(subsonic.getParams(), id, description, expires);
|
||||
}
|
||||
|
||||
public Call<ApiResponse> updateShare(String id, String description, Long expires) {
|
||||
Log.d(TAG, "updateShare()");
|
||||
return sharingService.updateShare(subsonic.getParams(), id, description, expires);
|
||||
}
|
||||
|
||||
public Call<ApiResponse> deleteShare(String id) {
|
||||
Log.d(TAG, "deleteShare()");
|
||||
return sharingService.deleteShare(subsonic.getParams(), id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.cappielloantonio.tempo.subsonic.api.sharing;
|
||||
|
||||
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.GET;
|
||||
import retrofit2.http.Query;
|
||||
import retrofit2.http.QueryMap;
|
||||
|
||||
public interface SharingService {
|
||||
@GET("getShares")
|
||||
Call<ApiResponse> getShares(@QueryMap Map<String, String> params);
|
||||
|
||||
@GET("createShare")
|
||||
Call<ApiResponse> createShare(@QueryMap Map<String, String> params, @Query("id") String id, @Query("description") String description, @Query("expires") Long expires);
|
||||
|
||||
@GET("updateShare")
|
||||
Call<ApiResponse> updateShare(@QueryMap Map<String, String> params, @Query("id") String id, @Query("description") String description, @Query("expires") Long expires);
|
||||
|
||||
@GET("deleteShare")
|
||||
Call<ApiResponse> deleteShare(@QueryMap Map<String, String> params, @Query("id") String id);
|
||||
}
|
||||
@@ -7,5 +7,5 @@ import com.google.gson.annotations.SerializedName
|
||||
@Keep
|
||||
class ApiResponse {
|
||||
@SerializedName("subsonic-response")
|
||||
var subsonicResponse: SubsonicResponse? = null
|
||||
lateinit var subsonicResponse: SubsonicResponse
|
||||
}
|
||||
@@ -14,6 +14,7 @@ class PodcastChannel : Parcelable {
|
||||
var url: String? = null
|
||||
var title: String? = null
|
||||
var description: String? = null
|
||||
@SerializedName("coverArt")
|
||||
var coverArtId: String? = null
|
||||
var originalImageUrl: String? = null
|
||||
var status: String? = null
|
||||
|
||||
@@ -12,12 +12,10 @@ class PodcastEpisode : Parcelable {
|
||||
var id: String? = null
|
||||
@SerializedName("parent")
|
||||
var parentId: String? = null
|
||||
@SerializedName("isDir")
|
||||
var isDir = false
|
||||
var title: String? = null
|
||||
var album: String? = null
|
||||
var artist: String? = null
|
||||
var track: Int? = null
|
||||
var year: Int? = null
|
||||
var genre: String? = null
|
||||
@SerializedName("coverArt")
|
||||
@@ -25,26 +23,14 @@ class PodcastEpisode : Parcelable {
|
||||
var size: Long? = null
|
||||
var contentType: String? = null
|
||||
var suffix: String? = null
|
||||
var transcodedContentType: String? = null
|
||||
var transcodedSuffix: String? = null
|
||||
var duration: Int? = null
|
||||
@SerializedName("bitRate")
|
||||
var bitrate: Int? = null
|
||||
var path: String? = null
|
||||
@SerializedName("isVideo")
|
||||
var isVideo: Boolean = false
|
||||
var userRating: Int? = null
|
||||
var averageRating: Double? = null
|
||||
var playCount: Long? = null
|
||||
var discNumber: Int? = null
|
||||
var created: Date? = null
|
||||
var starred: Date? = null
|
||||
var albumId: String? = null
|
||||
var artistId: String? = null
|
||||
var type: String? = null
|
||||
var bookmarkPosition: Long? = null
|
||||
var originalWidth: Int? = null
|
||||
var originalHeight: Int? = null
|
||||
var streamId: String? = null
|
||||
var channelId: String? = null
|
||||
var description: String? = null
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
package com.cappielloantonio.tempo.subsonic.models
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.annotation.Keep
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import java.util.*
|
||||
|
||||
@Keep
|
||||
class Share {
|
||||
@Parcelize
|
||||
class Share : Parcelable {
|
||||
@SerializedName("entry")
|
||||
var entries: List<Child>? = null
|
||||
var id: String? = null
|
||||
var url: String? = null
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package com.cappielloantonio.tempo.subsonic.models
|
||||
|
||||
import androidx.annotation.Keep
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
@Keep
|
||||
class Shares {
|
||||
@SerializedName("share")
|
||||
var shares: List<Share>? = null
|
||||
}
|
||||
@@ -94,7 +94,7 @@ public class MainActivity extends BaseActivity {
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (bottomSheetBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED)
|
||||
collapseBottomSheet();
|
||||
collapseBottomSheetDelayed();
|
||||
else
|
||||
super.onBackPressed();
|
||||
}
|
||||
@@ -118,7 +118,7 @@ public class MainActivity extends BaseActivity {
|
||||
bottomSheetBehavior.addBottomSheetCallback(bottomSheetCallback);
|
||||
fragmentManager.beginTransaction().replace(R.id.player_bottom_sheet, new PlayerBottomSheetFragment(), "PlayerBottomSheet").commit();
|
||||
|
||||
setBottomSheetInPeek(mainViewModel.isQueueLoaded());
|
||||
checkBottomSheetAfterStateChanged();
|
||||
}
|
||||
|
||||
public void setBottomSheetInPeek(Boolean isVisible) {
|
||||
@@ -137,7 +137,13 @@ public class MainActivity extends BaseActivity {
|
||||
}
|
||||
}
|
||||
|
||||
public void collapseBottomSheet() {
|
||||
private void checkBottomSheetAfterStateChanged() {
|
||||
final Handler handler = new Handler();
|
||||
final Runnable runnable = () -> setBottomSheetInPeek(mainViewModel.isQueueLoaded());
|
||||
handler.postDelayed(runnable, 100);
|
||||
}
|
||||
|
||||
public void collapseBottomSheetDelayed() {
|
||||
final Handler handler = new Handler();
|
||||
final Runnable runnable = () -> bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
|
||||
handler.postDelayed(runnable, 100);
|
||||
@@ -161,7 +167,7 @@ public class MainActivity extends BaseActivity {
|
||||
|
||||
switch (state) {
|
||||
case BottomSheetBehavior.STATE_HIDDEN:
|
||||
hideMusicSession();
|
||||
resetMusicSession();
|
||||
break;
|
||||
case BottomSheetBehavior.STATE_COLLAPSED:
|
||||
if (playerBottomSheetFragment != null)
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.PowerManager;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
@@ -37,6 +38,7 @@ public class BaseActivity extends AppCompatActivity {
|
||||
initializeDownloader();
|
||||
checkBatteryOptimization();
|
||||
checkPermission();
|
||||
checkAlwaysOnDisplay();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -66,6 +68,12 @@ public class BaseActivity extends AppCompatActivity {
|
||||
}
|
||||
}
|
||||
|
||||
private void checkAlwaysOnDisplay() {
|
||||
if (Preferences.isDisplayAlwaysOn()) {
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean detectBatteryOptimization() {
|
||||
String packageName = getPackageName();
|
||||
PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
|
||||
|
||||
@@ -42,7 +42,7 @@ public class AlbumAdapter extends RecyclerView.Adapter<AlbumAdapter.ViewHolder>
|
||||
holder.item.artistNameLabel.setText(MusicUtil.getReadableString(album.getArtist()));
|
||||
|
||||
CustomGlideRequest.Builder
|
||||
.from(holder.itemView.getContext(), album.getCoverArtId())
|
||||
.from(holder.itemView.getContext(), album.getCoverArtId(), CustomGlideRequest.ResourceType.Album)
|
||||
.build()
|
||||
.into(holder.item.albumCoverImageView);
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ public class AlbumArtistPageOrSimilarAdapter extends RecyclerView.Adapter<AlbumA
|
||||
holder.item.artistNameLabel.setText(MusicUtil.getReadableString(album.getArtist()));
|
||||
|
||||
CustomGlideRequest.Builder
|
||||
.from(holder.itemView.getContext(), album.getCoverArtId())
|
||||
.from(holder.itemView.getContext(), album.getCoverArtId(), CustomGlideRequest.ResourceType.Album)
|
||||
.build()
|
||||
.into(holder.item.artistPageAlbumCoverImageView);
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@ import java.util.List;
|
||||
|
||||
public class AlbumCatalogueAdapter extends RecyclerView.Adapter<AlbumCatalogueAdapter.ViewHolder> implements Filterable {
|
||||
private final ClickCallback click;
|
||||
private String currentFilter;
|
||||
|
||||
private final Filter filtering = new Filter() {
|
||||
@Override
|
||||
protected FilterResults performFiltering(CharSequence constraint) {
|
||||
@@ -32,6 +34,7 @@ public class AlbumCatalogueAdapter extends RecyclerView.Adapter<AlbumCatalogueAd
|
||||
filteredList.addAll(albumsFull);
|
||||
} else {
|
||||
String filterPattern = constraint.toString().toLowerCase().trim();
|
||||
currentFilter = filterPattern;
|
||||
|
||||
for (AlbumID3 item : albumsFull) {
|
||||
if (item.getName().toLowerCase().contains(filterPattern)) {
|
||||
@@ -48,8 +51,7 @@ public class AlbumCatalogueAdapter extends RecyclerView.Adapter<AlbumCatalogueAd
|
||||
|
||||
@Override
|
||||
protected void publishResults(CharSequence constraint, FilterResults results) {
|
||||
albums.clear();
|
||||
albums.addAll((List) results.values);
|
||||
albums = (List<AlbumID3>) results.values;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
};
|
||||
@@ -60,6 +62,8 @@ public class AlbumCatalogueAdapter extends RecyclerView.Adapter<AlbumCatalogueAd
|
||||
public AlbumCatalogueAdapter(ClickCallback click) {
|
||||
this.click = click;
|
||||
this.albums = Collections.emptyList();
|
||||
this.albumsFull = Collections.emptyList();
|
||||
this.currentFilter = "";
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@@ -77,7 +81,7 @@ public class AlbumCatalogueAdapter extends RecyclerView.Adapter<AlbumCatalogueAd
|
||||
holder.item.artistNameLabel.setText(MusicUtil.getReadableString(album.getArtist()));
|
||||
|
||||
CustomGlideRequest.Builder
|
||||
.from(holder.itemView.getContext(), album.getCoverArtId())
|
||||
.from(holder.itemView.getContext(), album.getCoverArtId(), CustomGlideRequest.ResourceType.Album)
|
||||
.build()
|
||||
.into(holder.item.albumCatalogueCoverImageView);
|
||||
}
|
||||
@@ -92,9 +96,8 @@ public class AlbumCatalogueAdapter extends RecyclerView.Adapter<AlbumCatalogueAd
|
||||
}
|
||||
|
||||
public void setItems(List<AlbumID3> albums) {
|
||||
this.albums = albums;
|
||||
this.albumsFull = new ArrayList<>(albums);
|
||||
notifyDataSetChanged();
|
||||
filtering.filter(currentFilter);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -162,4 +165,4 @@ public class AlbumCatalogueAdapter extends RecyclerView.Adapter<AlbumCatalogueAd
|
||||
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,7 +44,7 @@ public class AlbumHorizontalAdapter extends RecyclerView.Adapter<AlbumHorizontal
|
||||
holder.item.albumArtistTextView.setText(MusicUtil.getReadableString(album.getArtist()));
|
||||
|
||||
CustomGlideRequest.Builder
|
||||
.from(holder.itemView.getContext(), album.getCoverArtId())
|
||||
.from(holder.itemView.getContext(), album.getCoverArtId(), CustomGlideRequest.ResourceType.Album)
|
||||
.build()
|
||||
.into(holder.item.albumCoverImageView);
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ public class ArtistAdapter extends RecyclerView.Adapter<ArtistAdapter.ViewHolder
|
||||
holder.item.artistNameLabel.setText(MusicUtil.getReadableString(artist.getName()));
|
||||
|
||||
CustomGlideRequest.Builder
|
||||
.from(holder.itemView.getContext(), artist.getCoverArtId())
|
||||
.from(holder.itemView.getContext(), artist.getCoverArtId(), CustomGlideRequest.ResourceType.Artist)
|
||||
.build()
|
||||
.into(holder.item.artistCoverImageView);
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ public class ArtistCatalogueAdapter extends RecyclerView.Adapter<ArtistCatalogue
|
||||
holder.item.artistNameLabel.setText(MusicUtil.getReadableString(artist.getName()));
|
||||
|
||||
CustomGlideRequest.Builder
|
||||
.from(holder.itemView.getContext(), artist.getCoverArtId())
|
||||
.from(holder.itemView.getContext(), artist.getCoverArtId(), CustomGlideRequest.ResourceType.Artist)
|
||||
.build()
|
||||
.into(holder.item.artistCatalogueCoverImageView);
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ public class ArtistHorizontalAdapter extends RecyclerView.Adapter<ArtistHorizont
|
||||
}
|
||||
|
||||
CustomGlideRequest.Builder
|
||||
.from(holder.itemView.getContext(), artist.getCoverArtId())
|
||||
.from(holder.itemView.getContext(), artist.getCoverArtId(), CustomGlideRequest.ResourceType.Artist)
|
||||
.build()
|
||||
.into(holder.item.artistCoverImageView);
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ public class ArtistSimilarAdapter extends RecyclerView.Adapter<ArtistSimilarAdap
|
||||
holder.item.artistNameLabel.setText(MusicUtil.getReadableString(artist.getName()));
|
||||
|
||||
CustomGlideRequest.Builder
|
||||
.from(holder.itemView.getContext(), artist.getCoverArtId())
|
||||
.from(holder.itemView.getContext(), artist.getCoverArtId(), CustomGlideRequest.ResourceType.Artist)
|
||||
.build()
|
||||
.into(holder.item.similarArtistCoverImageView);
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ public class DiscoverSongAdapter extends RecyclerView.Adapter<DiscoverSongAdapte
|
||||
holder.item.albumDiscoverSongLabel.setText(MusicUtil.getReadableString(song.getAlbum()));
|
||||
|
||||
CustomGlideRequest.Builder
|
||||
.from(holder.itemView.getContext(), song.getCoverArtId())
|
||||
.from(holder.itemView.getContext(), song.getCoverArtId(), CustomGlideRequest.ResourceType.Song)
|
||||
.build()
|
||||
.into(holder.item.discoverSongCoverImageView);
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ public class DownloadHorizontalAdapter extends RecyclerView.Adapter<DownloadHori
|
||||
private String filterValue;
|
||||
|
||||
private List<Child> songs;
|
||||
private List<Child> shuffling;
|
||||
private List<Child> grouped;
|
||||
|
||||
public DownloadHorizontalAdapter(ClickCallback click) {
|
||||
@@ -82,6 +83,7 @@ public class DownloadHorizontalAdapter extends RecyclerView.Adapter<DownloadHori
|
||||
|
||||
this.songs = songs;
|
||||
this.grouped = groupSong(songs);
|
||||
this.shuffling = shufflingSong(new ArrayList<>(songs));
|
||||
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
@@ -90,6 +92,10 @@ public class DownloadHorizontalAdapter extends RecyclerView.Adapter<DownloadHori
|
||||
return grouped.get(id);
|
||||
}
|
||||
|
||||
public List<Child> getShuffling() {
|
||||
return shuffling;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
return position;
|
||||
@@ -136,6 +142,27 @@ public class DownloadHorizontalAdapter extends RecyclerView.Adapter<DownloadHori
|
||||
return songs;
|
||||
}
|
||||
|
||||
private List<Child> shufflingSong(List<Child> songs) {
|
||||
if (filterValue == null) {
|
||||
return songs;
|
||||
}
|
||||
|
||||
switch (filterKey) {
|
||||
case Constants.DOWNLOAD_TYPE_TRACK:
|
||||
return songs.stream().filter(child -> child.getId().equals(filterValue)).collect(Collectors.toList());
|
||||
case Constants.DOWNLOAD_TYPE_ALBUM:
|
||||
return songs.stream().filter(child -> Objects.equals(child.getAlbumId(), filterValue)).collect(Collectors.toList());
|
||||
case Constants.DOWNLOAD_TYPE_GENRE:
|
||||
return songs.stream().filter(child -> Objects.equals(child.getGenre(), filterValue)).collect(Collectors.toList());
|
||||
case Constants.DOWNLOAD_TYPE_YEAR:
|
||||
return songs.stream().filter(child -> Objects.equals(child.getYear(), Integer.valueOf(filterValue))).collect(Collectors.toList());
|
||||
case Constants.DOWNLOAD_TYPE_ARTIST:
|
||||
return songs.stream().filter(child -> Objects.equals(child.getArtistId(), filterValue)).collect(Collectors.toList());
|
||||
default:
|
||||
return songs;
|
||||
}
|
||||
}
|
||||
|
||||
private String countSong(String filterKey, String filterValue, List<Child> songs) {
|
||||
if (filterValue != null) {
|
||||
switch (filterKey) {
|
||||
@@ -163,7 +190,7 @@ public class DownloadHorizontalAdapter extends RecyclerView.Adapter<DownloadHori
|
||||
holder.item.downloadedItemPreTextView.setText(MusicUtil.getReadableString(song.getAlbum()));
|
||||
|
||||
CustomGlideRequest.Builder
|
||||
.from(holder.itemView.getContext(), song.getCoverArtId())
|
||||
.from(holder.itemView.getContext(), song.getCoverArtId(), CustomGlideRequest.ResourceType.Song)
|
||||
.build()
|
||||
.into(holder.item.itemCoverImageView);
|
||||
|
||||
@@ -186,12 +213,12 @@ public class DownloadHorizontalAdapter extends RecyclerView.Adapter<DownloadHori
|
||||
holder.item.downloadedItemPreTextView.setText(MusicUtil.getReadableString(song.getArtist()));
|
||||
|
||||
CustomGlideRequest.Builder
|
||||
.from(holder.itemView.getContext(), song.getCoverArtId())
|
||||
.from(holder.itemView.getContext(), song.getCoverArtId(), CustomGlideRequest.ResourceType.Song)
|
||||
.build()
|
||||
.into(holder.item.itemCoverImageView);
|
||||
|
||||
holder.item.itemCoverImageView.setVisibility(View.VISIBLE);
|
||||
holder.item.downloadedItemMoreButton.setVisibility(View.GONE);
|
||||
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).getArtist(), grouped.get(position).getArtist())) {
|
||||
@@ -208,12 +235,12 @@ public class DownloadHorizontalAdapter extends RecyclerView.Adapter<DownloadHori
|
||||
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())
|
||||
.from(holder.itemView.getContext(), song.getCoverArtId(), CustomGlideRequest.ResourceType.Song)
|
||||
.build()
|
||||
.into(holder.item.itemCoverImageView);
|
||||
|
||||
holder.item.itemCoverImageView.setVisibility(View.VISIBLE);
|
||||
holder.item.downloadedItemMoreButton.setVisibility(View.GONE);
|
||||
holder.item.downloadedItemMoreButton.setVisibility(View.VISIBLE);
|
||||
holder.item.divider.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@@ -224,7 +251,7 @@ public class DownloadHorizontalAdapter extends RecyclerView.Adapter<DownloadHori
|
||||
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.downloadedItemMoreButton.setVisibility(View.VISIBLE);
|
||||
holder.item.divider.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@@ -235,7 +262,7 @@ public class DownloadHorizontalAdapter extends RecyclerView.Adapter<DownloadHori
|
||||
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.downloadedItemMoreButton.setVisibility(View.VISIBLE);
|
||||
holder.item.divider.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@@ -285,24 +312,36 @@ public class DownloadHorizontalAdapter extends RecyclerView.Adapter<DownloadHori
|
||||
}
|
||||
|
||||
private boolean onLongClick() {
|
||||
ArrayList<Child> filteredSongs = new ArrayList<>();
|
||||
|
||||
Bundle bundle = new Bundle();
|
||||
|
||||
switch (view) {
|
||||
case Constants.DOWNLOAD_TYPE_TRACK:
|
||||
bundle.putParcelable(Constants.TRACK_OBJECT, grouped.get(getBindingAdapterPosition()));
|
||||
click.onMediaLongClick(bundle);
|
||||
return true;
|
||||
filteredSongs.add(grouped.get(getBindingAdapterPosition()));
|
||||
break;
|
||||
case Constants.DOWNLOAD_TYPE_ALBUM:
|
||||
bundle.putString(Constants.DOWNLOAD_TYPE_ALBUM, grouped.get(getBindingAdapterPosition()).getAlbumId());
|
||||
click.onAlbumLongClick(bundle);
|
||||
return true;
|
||||
filteredSongs.addAll(filterSong(Constants.DOWNLOAD_TYPE_ALBUM, grouped.get(getBindingAdapterPosition()).getAlbumId(), songs));
|
||||
break;
|
||||
case Constants.DOWNLOAD_TYPE_ARTIST:
|
||||
bundle.putString(Constants.DOWNLOAD_TYPE_ARTIST, grouped.get(getBindingAdapterPosition()).getArtistId());
|
||||
click.onArtistLongClick(bundle);
|
||||
return true;
|
||||
filteredSongs.addAll(filterSong(Constants.DOWNLOAD_TYPE_ARTIST, grouped.get(getBindingAdapterPosition()).getArtistId(), songs));
|
||||
break;
|
||||
case Constants.DOWNLOAD_TYPE_GENRE:
|
||||
filteredSongs.addAll(filterSong(Constants.DOWNLOAD_TYPE_GENRE, grouped.get(getBindingAdapterPosition()).getGenre(), songs));
|
||||
break;
|
||||
case Constants.DOWNLOAD_TYPE_YEAR:
|
||||
filteredSongs.addAll(filterSong(Constants.DOWNLOAD_TYPE_YEAR, grouped.get(getBindingAdapterPosition()).getYear().toString(), songs));
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
if (filteredSongs.isEmpty()) return false;
|
||||
|
||||
bundle.putParcelableArrayList(Constants.DOWNLOAD_GROUP, new ArrayList<>(filteredSongs));
|
||||
bundle.putString(Constants.DOWNLOAD_GROUP_TITLE, item.downloadedItemTitleTextView.getText().toString());
|
||||
bundle.putString(Constants.DOWNLOAD_GROUP_SUBTITLE, item.downloadedItemSubtitleTextView.getText().toString());
|
||||
click.onDownloadGroupLongClick(bundle);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ public class GridTrackAdapter extends RecyclerView.Adapter<GridTrackAdapter.View
|
||||
Chronology item = items.get(position);
|
||||
|
||||
CustomGlideRequest.Builder
|
||||
.from(holder.itemView.getContext(), item.getCoverArtId())
|
||||
.from(holder.itemView.getContext(), item.getCoverArtId(), CustomGlideRequest.ResourceType.Song)
|
||||
.build()
|
||||
.into(holder.item.trackCoverImageView);
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ public class InternetRadioStationAdapter extends RecyclerView.Adapter<InternetRa
|
||||
holder.item.internetRadioStationSubtitleTextView.setText(internetRadioStation.getStreamUrl());
|
||||
|
||||
CustomGlideRequest.Builder
|
||||
.from(holder.itemView.getContext(), internetRadioStation.getStreamUrl())
|
||||
.from(holder.itemView.getContext(), internetRadioStation.getStreamUrl(), CustomGlideRequest.ResourceType.Radio)
|
||||
.build()
|
||||
.into(holder.item.internetRadioStationCoverImageView);
|
||||
}
|
||||
|
||||
@@ -43,13 +43,17 @@ public class MusicDirectoryAdapter extends RecyclerView.Adapter<MusicDirectoryAd
|
||||
|
||||
holder.item.musicDirectoryTitleTextView.setText(child.getTitle());
|
||||
|
||||
CustomGlideRequest.ResourceType type = child.isDir()
|
||||
? CustomGlideRequest.ResourceType.Directory
|
||||
: CustomGlideRequest.ResourceType.Song;
|
||||
|
||||
CustomGlideRequest.Builder
|
||||
.from(holder.itemView.getContext(), child.getCoverArtId())
|
||||
.from(holder.itemView.getContext(), child.getCoverArtId(), type)
|
||||
.build()
|
||||
.into(holder.item.musicDirectoryCoverImageView);
|
||||
|
||||
holder.item.musicDirectoryMoreButton.setVisibility(child.isDir() ? View.VISIBLE : View.GONE);
|
||||
holder.item.musicDirectoryPlayButton.setVisibility(child.isDir() ? View.GONE : View.VISIBLE);
|
||||
holder.item.musicDirectoryMoreButton.setVisibility(child.isDir() ? View.VISIBLE : View.INVISIBLE);
|
||||
holder.item.musicDirectoryPlayButton.setVisibility(child.isDir() ? View.INVISIBLE : View.VISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -73,6 +77,8 @@ public class MusicDirectoryAdapter extends RecyclerView.Adapter<MusicDirectoryAd
|
||||
item.musicDirectoryTitleTextView.setSelected(true);
|
||||
|
||||
itemView.setOnClickListener(v -> onClick());
|
||||
itemView.setOnLongClickListener(v -> onLongClick());
|
||||
|
||||
item.musicDirectoryMoreButton.setOnClickListener(v -> onClick());
|
||||
}
|
||||
|
||||
@@ -88,5 +94,18 @@ public class MusicDirectoryAdapter extends RecyclerView.Adapter<MusicDirectoryAd
|
||||
click.onMediaClick(bundle);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean onLongClick() {
|
||||
if (!children.get(getBindingAdapterPosition()).isDir()) {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelable(Constants.TRACK_OBJECT, children.get(getBindingAdapterPosition()));
|
||||
|
||||
click.onMediaLongClick(bundle);
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ public class MusicFolderAdapter extends RecyclerView.Adapter<MusicFolderAdapter.
|
||||
holder.item.musicFolderTitleTextView.setText(musicFolder.getName());
|
||||
|
||||
CustomGlideRequest.Builder
|
||||
.from(holder.itemView.getContext(), musicFolder.getName())
|
||||
.from(holder.itemView.getContext(), musicFolder.getName(), CustomGlideRequest.ResourceType.Folder)
|
||||
.build()
|
||||
.into(holder.item.musicFolderCoverImageView);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.cappielloantonio.tempo.databinding.ItemLibraryMusicIndexBinding;
|
||||
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
||||
import com.cappielloantonio.tempo.helper.recyclerview.FastScrollbar;
|
||||
import com.cappielloantonio.tempo.interfaces.ClickCallback;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Artist;
|
||||
@@ -42,10 +43,10 @@ public class MusicIndexAdapter extends RecyclerView.Adapter<MusicIndexAdapter.Vi
|
||||
|
||||
holder.item.musicIndexTitleTextView.setText(artist.getName());
|
||||
|
||||
/* CustomGlideRequest.Builder
|
||||
.from(holder.itemView.getContext(), artist.getName())
|
||||
CustomGlideRequest.Builder
|
||||
.from(holder.itemView.getContext(), artist.getName(), CustomGlideRequest.ResourceType.Directory)
|
||||
.build()
|
||||
.into(holder.item.musicIndexCoverImageView); */
|
||||
.into(holder.item.musicIndexCoverImageView);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -60,7 +61,7 @@ public class MusicIndexAdapter extends RecyclerView.Adapter<MusicIndexAdapter.Vi
|
||||
|
||||
@Override
|
||||
public String getTextToShowInBubble(int pos) {
|
||||
return Character.toString(Objects.requireNonNull(artists.get(pos).getName().toUpperCase()).charAt(0));
|
||||
return artists != null && !artists.isEmpty() ? Character.toString(Objects.requireNonNull(artists.get(pos).getName().toUpperCase()).charAt(0)) : null;
|
||||
}
|
||||
|
||||
public class ViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
@@ -49,7 +49,7 @@ public class PlayerSongQueueAdapter extends RecyclerView.Adapter<PlayerSongQueue
|
||||
holder.item.queueSongSubtitleTextView.setText(holder.itemView.getContext().getString(R.string.song_subtitle_formatter, MusicUtil.getReadableString(song.getArtist()), MusicUtil.getReadableDurationString(song.getDuration(), false)));
|
||||
|
||||
CustomGlideRequest.Builder
|
||||
.from(holder.itemView.getContext(), song.getCoverArtId())
|
||||
.from(holder.itemView.getContext(), song.getCoverArtId(), CustomGlideRequest.ResourceType.Song)
|
||||
.build()
|
||||
.into(holder.item.queueSongCoverImageView);
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ public class PlaylistDialogSongHorizontalAdapter extends RecyclerView.Adapter<Pl
|
||||
holder.item.playlistDialogSongDurationTextView.setText(MusicUtil.getReadableDurationString(song.getDuration(), false));
|
||||
|
||||
CustomGlideRequest.Builder
|
||||
.from(holder.itemView.getContext(), song.getCoverArtId())
|
||||
.from(holder.itemView.getContext(), song.getCoverArtId(), CustomGlideRequest.ResourceType.Song)
|
||||
.build()
|
||||
.into(holder.item.playlistDialogSongCoverImageView);
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ public class PlaylistHorizontalAdapter extends RecyclerView.Adapter<PlaylistHori
|
||||
holder.item.playlistSubtitleTextView.setText(holder.itemView.getContext().getString(R.string.playlist_counted_tracks, playlist.getSongCount(), MusicUtil.getReadableDurationString(playlist.getDuration(), false)));
|
||||
|
||||
CustomGlideRequest.Builder
|
||||
.from(holder.itemView.getContext(), playlist.getCoverArtId())
|
||||
.from(holder.itemView.getContext(), playlist.getCoverArtId(), CustomGlideRequest.ResourceType.Playlist)
|
||||
.build()
|
||||
.into(holder.item.playlistCoverImageView);
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ public class PodcastChannelCatalogueAdapter extends RecyclerView.Adapter<Podcast
|
||||
holder.item.podcastChannelTitleLabel.setText(MusicUtil.getReadableString(podcastChannel.getTitle()));
|
||||
|
||||
CustomGlideRequest.Builder
|
||||
.from(holder.itemView.getContext(), podcastChannel.getCoverArtId())
|
||||
.from(holder.itemView.getContext(), podcastChannel.getCoverArtId(), CustomGlideRequest.ResourceType.Podcast)
|
||||
.build()
|
||||
.into(holder.item.podcastChannelCatalogueCoverImageView);
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ public class PodcastChannelHorizontalAdapter extends RecyclerView.Adapter<Podcas
|
||||
holder.item.podcastChannelDescriptionTextView.setText(MusicUtil.getReadableString(podcastChannel.getDescription()));
|
||||
|
||||
CustomGlideRequest.Builder
|
||||
.from(holder.itemView.getContext(), podcastChannel.getOriginalImageUrl())
|
||||
.from(holder.itemView.getContext(), podcastChannel.getCoverArtId(), CustomGlideRequest.ResourceType.Podcast)
|
||||
.build()
|
||||
.into(holder.item.podcastChannelCoverImageView);
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ public class PodcastEpisodeAdapter extends RecyclerView.Adapter<PodcastEpisodeAd
|
||||
holder.item.podcastDescriptionText.setText(MusicUtil.getReadableString(podcastEpisode.getDescription()));
|
||||
|
||||
CustomGlideRequest.Builder
|
||||
.from(holder.itemView.getContext(), podcastEpisode.getCoverArtId())
|
||||
.from(holder.itemView.getContext(), podcastEpisode.getCoverArtId(), CustomGlideRequest.ResourceType.Podcast)
|
||||
.build()
|
||||
.into(holder.item.podcastCoverImageView);
|
||||
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
package com.cappielloantonio.tempo.ui.adapter;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.cappielloantonio.tempo.R;
|
||||
import com.cappielloantonio.tempo.databinding.ItemHorizontalShareBinding;
|
||||
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
||||
import com.cappielloantonio.tempo.interfaces.ClickCallback;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Share;
|
||||
import com.cappielloantonio.tempo.util.Constants;
|
||||
import com.cappielloantonio.tempo.util.MusicUtil;
|
||||
import com.cappielloantonio.tempo.util.UIUtil;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class ShareHorizontalAdapter extends RecyclerView.Adapter<ShareHorizontalAdapter.ViewHolder> {
|
||||
private final ClickCallback click;
|
||||
|
||||
private List<Share> shares;
|
||||
|
||||
public ShareHorizontalAdapter(ClickCallback click) {
|
||||
this.click = click;
|
||||
this.shares = Collections.emptyList();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
ItemHorizontalShareBinding view = ItemHorizontalShareBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
|
||||
return new ViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(ViewHolder holder, int position) {
|
||||
Share share = shares.get(position);
|
||||
|
||||
holder.item.shareTitleTextView.setText(MusicUtil.getReadableString(share.getDescription()));
|
||||
holder.item.shareSubtitleTextView.setText(holder.itemView.getContext().getString(R.string.share_subtitle_item, UIUtil.getReadableDate(share.getExpires())));
|
||||
|
||||
if (share.getEntries() != null && !share.getEntries().isEmpty()) CustomGlideRequest.Builder
|
||||
.from(holder.itemView.getContext(), share.getEntries().get(0).getCoverArtId(), CustomGlideRequest.ResourceType.Album)
|
||||
.build()
|
||||
.into(holder.item.shareCoverImageView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return shares.size();
|
||||
}
|
||||
|
||||
public void setItems(List<Share> shares) {
|
||||
this.shares = shares;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public Share getItem(int id) {
|
||||
return shares.get(id);
|
||||
}
|
||||
|
||||
public class ViewHolder extends RecyclerView.ViewHolder {
|
||||
ItemHorizontalShareBinding item;
|
||||
|
||||
ViewHolder(ItemHorizontalShareBinding item) {
|
||||
super(item.getRoot());
|
||||
|
||||
this.item = item;
|
||||
|
||||
item.shareTitleTextView.setSelected(true);
|
||||
item.shareSubtitleTextView.setSelected(true);
|
||||
|
||||
itemView.setOnClickListener(v -> onClick());
|
||||
itemView.setOnLongClickListener(v -> onLongClick());
|
||||
|
||||
item.shareButton.setOnClickListener(v -> onLongClick());
|
||||
}
|
||||
|
||||
private void onClick() {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelable(Constants.SHARE_OBJECT, shares.get(getBindingAdapterPosition()));
|
||||
|
||||
click.onShareClick(bundle);
|
||||
}
|
||||
|
||||
private boolean onLongClick() {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelable(Constants.SHARE_OBJECT, shares.get(getBindingAdapterPosition()));
|
||||
|
||||
click.onShareLongClick(bundle);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,7 @@ public class SimilarTrackAdapter extends RecyclerView.Adapter<SimilarTrackAdapte
|
||||
holder.item.titleTrackLabel.setText(MusicUtil.getReadableString(song.getTitle()));
|
||||
|
||||
CustomGlideRequest.Builder
|
||||
.from(holder.itemView.getContext(), song.getCoverArtId())
|
||||
.from(holder.itemView.getContext(), song.getCoverArtId(), CustomGlideRequest.ResourceType.Song)
|
||||
.build()
|
||||
.into(holder.item.trackCoverImageView);
|
||||
}
|
||||
|
||||
@@ -25,13 +25,15 @@ import java.util.List;
|
||||
@UnstableApi
|
||||
public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAdapter.ViewHolder> {
|
||||
private final ClickCallback click;
|
||||
private final boolean isCoverVisible;
|
||||
private final boolean showCoverArt;
|
||||
private final boolean showAlbum;
|
||||
|
||||
private List<Child> songs;
|
||||
|
||||
public SongHorizontalAdapter(ClickCallback click, boolean isCoverVisible) {
|
||||
public SongHorizontalAdapter(ClickCallback click, boolean showCoverArt, boolean showAlbum) {
|
||||
this.click = click;
|
||||
this.isCoverVisible = isCoverVisible;
|
||||
this.showCoverArt = showCoverArt;
|
||||
this.showAlbum = showAlbum;
|
||||
this.songs = Collections.emptyList();
|
||||
}
|
||||
|
||||
@@ -47,7 +49,7 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAd
|
||||
Child song = songs.get(position);
|
||||
|
||||
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(), false)));
|
||||
holder.item.trackNumberTextView.setText(MusicUtil.getReadableTrackNumber(holder.itemView.getContext(), song.getTrack()));
|
||||
|
||||
if (DownloadUtil.getDownloadTracker(holder.itemView.getContext()).isDownloaded(song.getId())) {
|
||||
@@ -56,16 +58,15 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAd
|
||||
holder.item.searchResultDowanloadIndicatorImageView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (isCoverVisible) CustomGlideRequest.Builder
|
||||
.from(holder.itemView.getContext(), song.getCoverArtId())
|
||||
if (showCoverArt) CustomGlideRequest.Builder
|
||||
.from(holder.itemView.getContext(), song.getCoverArtId(), CustomGlideRequest.ResourceType.Song)
|
||||
.build()
|
||||
.into(holder.item.songCoverImageView);
|
||||
|
||||
if (isCoverVisible) holder.item.trackNumberTextView.setVisibility(View.INVISIBLE);
|
||||
holder.item.trackNumberTextView.setVisibility(showCoverArt ? View.INVISIBLE : View.VISIBLE);
|
||||
holder.item.songCoverImageView.setVisibility(showCoverArt ? View.VISIBLE : View.INVISIBLE);
|
||||
|
||||
if (!isCoverVisible) 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);
|
||||
}
|
||||
}
|
||||
@@ -114,7 +115,7 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAd
|
||||
public void onClick() {
|
||||
Bundle bundle = new Bundle();
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.cappielloantonio.tempo.ui.dialog;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
@@ -14,38 +13,23 @@ import androidx.media3.common.util.UnstableApi;
|
||||
import com.cappielloantonio.tempo.R;
|
||||
import com.cappielloantonio.tempo.databinding.DialogBatteryOptimizationBinding;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
|
||||
@OptIn(markerClass = UnstableApi.class)
|
||||
public class BatteryOptimizationDialog extends DialogFragment {
|
||||
private static final String TAG = "BatteryOptimizationDialog";
|
||||
|
||||
private DialogBatteryOptimizationBinding bind;
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
bind = DialogBatteryOptimizationBinding.inflate(getLayoutInflater());
|
||||
DialogBatteryOptimizationBinding bind = DialogBatteryOptimizationBinding.inflate(getLayoutInflater());
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
|
||||
builder.setView(bind.getRoot())
|
||||
return new MaterialAlertDialogBuilder(requireContext())
|
||||
.setView(bind.getRoot())
|
||||
.setTitle(R.string.activity_battery_optimizations_title)
|
||||
.setNeutralButton(R.string.battery_optimization_neutral_button, (dialog, id) -> Preferences.dontAskForOptimization())
|
||||
.setPositiveButton(R.string.battery_optimization_positive_button, (dialog, listener) -> openPowerSettings())
|
||||
.setNeutralButton(R.string.battery_optimization_neutral_button, (dialog, listener) -> Preferences.dontAskForOptimization())
|
||||
.setNegativeButton(R.string.battery_optimization_negative_button, null)
|
||||
.setPositiveButton(R.string.battery_optimization_positive_button, (dialog, id) -> openPowerSettings());
|
||||
|
||||
AlertDialog popup = builder.create();
|
||||
|
||||
popup.setCancelable(false);
|
||||
popup.setCanceledOnTouchOutside(false);
|
||||
|
||||
return popup;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
bind = null;
|
||||
.create();
|
||||
}
|
||||
|
||||
private void openPowerSettings() {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.cappielloantonio.tempo.ui.dialog;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.os.Bundle;
|
||||
|
||||
@@ -10,22 +9,18 @@ import androidx.fragment.app.DialogFragment;
|
||||
import com.cappielloantonio.tempo.R;
|
||||
import com.cappielloantonio.tempo.databinding.DialogConnectionAlertBinding;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class ConnectionAlertDialog extends DialogFragment {
|
||||
private static final String TAG = "ServerUnreachableDialog";
|
||||
|
||||
private DialogConnectionAlertBinding bind;
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
bind = DialogConnectionAlertBinding.inflate(getLayoutInflater());
|
||||
DialogConnectionAlertBinding bind = DialogConnectionAlertBinding.inflate(getLayoutInflater());
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
|
||||
builder.setView(bind.getRoot())
|
||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(getActivity())
|
||||
.setView(bind.getRoot())
|
||||
.setTitle(R.string.connection_alert_dialog_title)
|
||||
.setPositiveButton(R.string.connection_alert_dialog_positive_button, (dialog, id) -> dialog.cancel())
|
||||
.setNegativeButton(R.string.connection_alert_dialog_negative_button, (dialog, id) -> dialog.cancel());
|
||||
@@ -45,16 +40,12 @@ public class ConnectionAlertDialog extends DialogFragment {
|
||||
setButtonAction();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
bind = null;
|
||||
}
|
||||
|
||||
private void setButtonAction() {
|
||||
((AlertDialog) Objects.requireNonNull(getDialog())).getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener(v -> {
|
||||
androidx.appcompat.app.AlertDialog alertDialog = (androidx.appcompat.app.AlertDialog) Objects.requireNonNull(getDialog());
|
||||
|
||||
alertDialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_NEUTRAL).setOnClickListener(v -> {
|
||||
Preferences.setDataSavingMode(true);
|
||||
Objects.requireNonNull(getDialog()).dismiss();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.cappielloantonio.tempo.ui.dialog;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.os.Bundle;
|
||||
import android.widget.Button;
|
||||
@@ -12,26 +11,23 @@ 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;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
|
||||
@OptIn(markerClass = UnstableApi.class)
|
||||
public class DeleteDownloadStorageDialog extends DialogFragment {
|
||||
private DialogDeleteDownloadStorageBinding bind;
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
bind = DialogDeleteDownloadStorageBinding.inflate(getLayoutInflater());
|
||||
DialogDeleteDownloadStorageBinding bind = DialogDeleteDownloadStorageBinding.inflate(getLayoutInflater());
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
|
||||
builder.setView(bind.getRoot())
|
||||
return new MaterialAlertDialogBuilder(requireContext())
|
||||
.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();
|
||||
.setNegativeButton(R.string.delete_download_storage_dialog_negative_button, null)
|
||||
.create();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -40,14 +36,8 @@ public class DeleteDownloadStorageDialog extends DialogFragment {
|
||||
setButtonAction();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
bind = null;
|
||||
}
|
||||
|
||||
private void setButtonAction() {
|
||||
AlertDialog dialog = ((AlertDialog) getDialog());
|
||||
androidx.appcompat.app.AlertDialog dialog = (androidx.appcompat.app.AlertDialog) getDialog();
|
||||
|
||||
if (dialog != null) {
|
||||
Button positiveButton = dialog.getButton(Dialog.BUTTON_POSITIVE);
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.cappielloantonio.tempo.ui.dialog;
|
||||
|
||||
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.DialogDownloadDirectoryBinding;
|
||||
import com.cappielloantonio.tempo.interfaces.DialogClickCallback;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
|
||||
@OptIn(markerClass = UnstableApi.class)
|
||||
public class DownloadDirectoryDialog extends DialogFragment {
|
||||
private final DialogClickCallback dialogClickCallback;
|
||||
|
||||
public DownloadDirectoryDialog(DialogClickCallback dialogClickCallback) {
|
||||
this.dialogClickCallback = dialogClickCallback;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
DialogDownloadDirectoryBinding bind = DialogDownloadDirectoryBinding.inflate(getLayoutInflater());
|
||||
|
||||
return new MaterialAlertDialogBuilder(requireContext())
|
||||
.setView(bind.getRoot())
|
||||
.setTitle(R.string.download_directory_dialog_title)
|
||||
.setPositiveButton(R.string.download_directory_dialog_positive_button, null)
|
||||
.setNegativeButton(R.string.download_directory_dialog_negative_button, null)
|
||||
.create();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
setButtonAction();
|
||||
}
|
||||
|
||||
private void setButtonAction() {
|
||||
androidx.appcompat.app.AlertDialog dialog = (androidx.appcompat.app.AlertDialog) getDialog();
|
||||
|
||||
if (dialog != null) {
|
||||
Button positiveButton = dialog.getButton(Dialog.BUTTON_POSITIVE);
|
||||
positiveButton.setOnClickListener(v -> {
|
||||
dialogClickCallback.onPositiveClick();
|
||||
dialog.dismiss();
|
||||
});
|
||||
|
||||
Button negativeButton = dialog.getButton(Dialog.BUTTON_NEGATIVE);
|
||||
negativeButton.setOnClickListener(v -> {
|
||||
dialogClickCallback.onNegativeClick();
|
||||
dialog.dismiss();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.cappielloantonio.tempo.ui.dialog;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.os.Bundle;
|
||||
import android.widget.Button;
|
||||
@@ -15,11 +14,10 @@ import com.cappielloantonio.tempo.databinding.DialogDownloadStorageBinding;
|
||||
import com.cappielloantonio.tempo.interfaces.DialogClickCallback;
|
||||
import com.cappielloantonio.tempo.util.DownloadUtil;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
|
||||
@OptIn(markerClass = UnstableApi.class)
|
||||
public class DownloadStorageDialog extends DialogFragment {
|
||||
private DialogDownloadStorageBinding bind;
|
||||
|
||||
private final DialogClickCallback dialogClickCallback;
|
||||
|
||||
public DownloadStorageDialog(DialogClickCallback dialogClickCallback) {
|
||||
@@ -29,16 +27,14 @@ public class DownloadStorageDialog extends DialogFragment {
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
bind = DialogDownloadStorageBinding.inflate(getLayoutInflater());
|
||||
DialogDownloadStorageBinding bind = DialogDownloadStorageBinding.inflate(getLayoutInflater());
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
|
||||
builder.setView(bind.getRoot())
|
||||
return new MaterialAlertDialogBuilder(getActivity())
|
||||
.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();
|
||||
.setNegativeButton(R.string.download_storage_internal_dialog_negative_button, null)
|
||||
.create();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -47,14 +43,8 @@ public class DownloadStorageDialog extends DialogFragment {
|
||||
setButtonAction();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
bind = null;
|
||||
}
|
||||
|
||||
private void setButtonAction() {
|
||||
AlertDialog dialog = ((AlertDialog) getDialog());
|
||||
androidx.appcompat.app.AlertDialog dialog = (androidx.appcompat.app.AlertDialog) getDialog();
|
||||
|
||||
if (dialog != null) {
|
||||
Button positiveButton = dialog.getButton(Dialog.BUTTON_POSITIVE);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.cappielloantonio.tempo.ui.dialog;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
@@ -17,6 +16,7 @@ import com.cappielloantonio.tempo.subsonic.models.Playlist;
|
||||
import com.cappielloantonio.tempo.ui.adapter.PlaylistDialogHorizontalAdapter;
|
||||
import com.cappielloantonio.tempo.util.Constants;
|
||||
import com.cappielloantonio.tempo.viewmodel.PlaylistChooserViewModel;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@@ -30,17 +30,15 @@ public class PlaylistChooserDialog extends DialogFragment implements ClickCallba
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
bind = DialogPlaylistChooserBinding.inflate(getLayoutInflater());
|
||||
|
||||
playlistChooserViewModel = new ViewModelProvider(requireActivity()).get(PlaylistChooserViewModel.class);
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
|
||||
builder.setView(bind.getRoot())
|
||||
return new MaterialAlertDialogBuilder(getActivity())
|
||||
.setView(bind.getRoot())
|
||||
.setTitle(R.string.playlist_chooser_dialog_title)
|
||||
.setNeutralButton(R.string.playlist_chooser_dialog_neutral_button, (dialog, id) -> {
|
||||
})
|
||||
.setNegativeButton(R.string.playlist_chooser_dialog_negative_button, (dialog, id) -> dialog.cancel());
|
||||
|
||||
return builder.create();
|
||||
.setNeutralButton(R.string.playlist_chooser_dialog_neutral_button, (dialog, id) -> { })
|
||||
.setNegativeButton(R.string.playlist_chooser_dialog_negative_button, (dialog, id) -> dialog.cancel())
|
||||
.create();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -63,11 +61,12 @@ public class PlaylistChooserDialog extends DialogFragment implements ClickCallba
|
||||
}
|
||||
|
||||
private void setButtonAction() {
|
||||
((AlertDialog) Objects.requireNonNull(getDialog())).getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener(v -> {
|
||||
androidx.appcompat.app.AlertDialog alertDialog = (androidx.appcompat.app.AlertDialog) Objects.requireNonNull(getDialog());
|
||||
alertDialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_NEUTRAL).setOnClickListener(v -> {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelable(Constants.TRACK_OBJECT, playlistChooserViewModel.getSongToAdd());
|
||||
|
||||
PlaylistEditorDialog dialog = new PlaylistEditorDialog();
|
||||
PlaylistEditorDialog dialog = new PlaylistEditorDialog(null);
|
||||
dialog.setArguments(bundle);
|
||||
dialog.show(requireActivity().getSupportFragmentManager(), null);
|
||||
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package com.cappielloantonio.tempo.ui.dialog;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
@@ -14,10 +17,13 @@ import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.cappielloantonio.tempo.R;
|
||||
import com.cappielloantonio.tempo.databinding.DialogPlaylistEditorBinding;
|
||||
import com.cappielloantonio.tempo.interfaces.PlaylistCallback;
|
||||
import com.cappielloantonio.tempo.ui.adapter.PlaylistDialogSongHorizontalAdapter;
|
||||
import com.cappielloantonio.tempo.util.Constants;
|
||||
import com.cappielloantonio.tempo.util.MusicUtil;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
import com.cappielloantonio.tempo.viewmodel.PlaylistEditorViewModel;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Objects;
|
||||
@@ -26,25 +32,29 @@ public class PlaylistEditorDialog extends DialogFragment {
|
||||
private DialogPlaylistEditorBinding bind;
|
||||
private PlaylistEditorViewModel playlistEditorViewModel;
|
||||
|
||||
private final PlaylistCallback playlistCallback;
|
||||
|
||||
private String playlistName;
|
||||
private PlaylistDialogSongHorizontalAdapter playlistDialogSongHorizontalAdapter;
|
||||
|
||||
public PlaylistEditorDialog(PlaylistCallback playlistCallback) {
|
||||
this.playlistCallback = playlistCallback;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
bind = DialogPlaylistEditorBinding.inflate(getLayoutInflater());
|
||||
|
||||
playlistEditorViewModel = new ViewModelProvider(requireActivity()).get(PlaylistEditorViewModel.class);
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
|
||||
builder.setView(bind.getRoot())
|
||||
return new MaterialAlertDialogBuilder(getActivity())
|
||||
.setView(bind.getRoot())
|
||||
.setTitle(R.string.playlist_editor_dialog_title)
|
||||
.setPositiveButton(R.string.playlist_editor_dialog_positive_button, (dialog, id) -> {
|
||||
})
|
||||
.setPositiveButton(R.string.playlist_editor_dialog_positive_button, (dialog, id) -> { })
|
||||
.setNeutralButton(R.string.playlist_editor_dialog_neutral_button, (dialog, id) -> dialog.cancel())
|
||||
.setNegativeButton(R.string.playlist_editor_dialog_negative_button, (dialog, id) -> dialog.cancel());
|
||||
|
||||
return builder.create();
|
||||
.setNegativeButton(R.string.playlist_editor_dialog_negative_button, (dialog, id) -> dialog.cancel())
|
||||
.create();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -77,7 +87,9 @@ public class PlaylistEditorDialog extends DialogFragment {
|
||||
}
|
||||
|
||||
private void setButtonAction() {
|
||||
((AlertDialog) Objects.requireNonNull(getDialog())).getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> {
|
||||
androidx.appcompat.app.AlertDialog alertDialog = (androidx.appcompat.app.AlertDialog) Objects.requireNonNull(getDialog());
|
||||
|
||||
alertDialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> {
|
||||
if (validateInput()) {
|
||||
if (playlistEditorViewModel.getSongToAdd() != null) {
|
||||
playlistEditorViewModel.createPlaylist(playlistName);
|
||||
@@ -85,14 +97,24 @@ public class PlaylistEditorDialog extends DialogFragment {
|
||||
playlistEditorViewModel.updatePlaylist(playlistName);
|
||||
}
|
||||
|
||||
Objects.requireNonNull(getDialog()).dismiss();
|
||||
dialogDismiss();
|
||||
}
|
||||
});
|
||||
|
||||
((AlertDialog) Objects.requireNonNull(getDialog())).getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener(v -> {
|
||||
alertDialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_NEUTRAL).setOnClickListener(v -> {
|
||||
playlistEditorViewModel.deletePlaylist();
|
||||
Objects.requireNonNull(getDialog()).dismiss();
|
||||
dialogDismiss();
|
||||
});
|
||||
|
||||
bind.playlistShareButton.setOnClickListener(view -> {
|
||||
playlistEditorViewModel.sharePlaylist().observe(requireActivity(), sharedPlaylist -> {
|
||||
ClipboardManager clipboardManager = (ClipboardManager) requireActivity().getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
ClipData clipData = ClipData.newPlainText(getString(R.string.app_name), sharedPlaylist.getUrl());
|
||||
clipboardManager.setPrimaryClip(clipData);
|
||||
});
|
||||
});
|
||||
|
||||
bind.playlistShareButton.setVisibility(Preferences.isSharingEnabled() ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
private void initSongsView() {
|
||||
@@ -102,7 +124,9 @@ public class PlaylistEditorDialog extends DialogFragment {
|
||||
playlistDialogSongHorizontalAdapter = new 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) {
|
||||
int originalPosition = -1;
|
||||
@@ -157,4 +181,11 @@ public class PlaylistEditorDialog extends DialogFragment {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void dialogDismiss() {
|
||||
Objects.requireNonNull(getDialog()).dismiss();
|
||||
if (playlistCallback != null) {
|
||||
playlistCallback.onDismiss();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package com.cappielloantonio.tempo.ui.dialog;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.widget.Button;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
@@ -13,13 +13,15 @@ import com.cappielloantonio.tempo.R;
|
||||
import com.cappielloantonio.tempo.databinding.DialogPodcastChannelEditorBinding;
|
||||
import com.cappielloantonio.tempo.interfaces.PodcastCallback;
|
||||
import com.cappielloantonio.tempo.viewmodel.PodcastChannelEditorViewModel;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class PodcastChannelEditorDialog extends DialogFragment {
|
||||
private DialogPodcastChannelEditorBinding bind;
|
||||
private PodcastChannelEditorViewModel podcastChannelEditorViewModel;
|
||||
private PodcastCallback podcastCallback;
|
||||
|
||||
private final PodcastCallback podcastCallback;
|
||||
|
||||
private String channelUrl;
|
||||
|
||||
@@ -31,17 +33,15 @@ public class PodcastChannelEditorDialog extends DialogFragment {
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
bind = DialogPodcastChannelEditorBinding.inflate(getLayoutInflater());
|
||||
|
||||
podcastChannelEditorViewModel = new ViewModelProvider(requireActivity()).get(PodcastChannelEditorViewModel.class);
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
|
||||
builder.setView(bind.getRoot())
|
||||
return new MaterialAlertDialogBuilder(getActivity())
|
||||
.setView(bind.getRoot())
|
||||
.setTitle(R.string.podcast_channel_editor_dialog_title)
|
||||
.setPositiveButton(R.string.radio_editor_dialog_positive_button, (dialog, id) -> {
|
||||
})
|
||||
.setNegativeButton(R.string.radio_editor_dialog_negative_button, (dialog, id) -> dialog.cancel());
|
||||
|
||||
return builder.create();
|
||||
.setPositiveButton(R.string.radio_editor_dialog_positive_button, (dialog, id) -> { })
|
||||
.setNegativeButton(R.string.radio_editor_dialog_negative_button, (dialog, id) -> dialog.cancel())
|
||||
.create();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -58,14 +58,19 @@ public class PodcastChannelEditorDialog extends DialogFragment {
|
||||
}
|
||||
|
||||
private void setButtonAction() {
|
||||
((AlertDialog) Objects.requireNonNull(getDialog())).getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> {
|
||||
if (validateInput()) {
|
||||
podcastChannelEditorViewModel.createChannel(channelUrl);
|
||||
dismissDialog();
|
||||
}
|
||||
});
|
||||
androidx.appcompat.app.AlertDialog dialog = (androidx.appcompat.app.AlertDialog) getDialog();
|
||||
if (dialog != null) {
|
||||
Button positiveButton = dialog.getButton(Dialog.BUTTON_POSITIVE);
|
||||
positiveButton.setOnClickListener(v -> {
|
||||
if (validateInput()) {
|
||||
podcastChannelEditorViewModel.createChannel(channelUrl);
|
||||
dismissDialog();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private boolean validateInput() {
|
||||
channelUrl = Objects.requireNonNull(bind.podcastChannelRssUrlNameTextView.getText()).toString().trim();
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.cappielloantonio.tempo.ui.dialog;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
@@ -15,13 +14,15 @@ import com.cappielloantonio.tempo.interfaces.RadioCallback;
|
||||
import com.cappielloantonio.tempo.subsonic.models.InternetRadioStation;
|
||||
import com.cappielloantonio.tempo.util.Constants;
|
||||
import com.cappielloantonio.tempo.viewmodel.RadioEditorViewModel;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class RadioEditorDialog extends DialogFragment {
|
||||
private DialogRadioEditorBinding bind;
|
||||
private RadioEditorViewModel radioEditorViewModel;
|
||||
private RadioCallback radioCallback;
|
||||
|
||||
private final RadioCallback radioCallback;
|
||||
|
||||
private String radioName;
|
||||
private String radioStreamURL;
|
||||
@@ -35,26 +36,36 @@ public class RadioEditorDialog extends DialogFragment {
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
bind = DialogRadioEditorBinding.inflate(getLayoutInflater());
|
||||
|
||||
radioEditorViewModel = new ViewModelProvider(requireActivity()).get(RadioEditorViewModel.class);
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
|
||||
builder.setView(bind.getRoot())
|
||||
return new MaterialAlertDialogBuilder(requireContext())
|
||||
.setView(bind.getRoot())
|
||||
.setTitle(R.string.radio_editor_dialog_title)
|
||||
.setPositiveButton(R.string.radio_editor_dialog_positive_button, (dialog, id) -> {
|
||||
if (validateInput()) {
|
||||
if (radioEditorViewModel.getRadioToEdit() == null) {
|
||||
radioEditorViewModel.createRadio(radioName, radioStreamURL, radioHomepageURL.isEmpty() ? null : radioHomepageURL);
|
||||
} else {
|
||||
radioEditorViewModel.updateRadio(radioName, radioStreamURL, radioHomepageURL.isEmpty() ? null : radioHomepageURL);
|
||||
}
|
||||
dismissDialog();
|
||||
}
|
||||
})
|
||||
.setNeutralButton(R.string.radio_editor_dialog_neutral_button, (dialog, id) -> dialog.cancel())
|
||||
.setNegativeButton(R.string.radio_editor_dialog_negative_button, (dialog, id) -> dialog.cancel());
|
||||
|
||||
return builder.create();
|
||||
.setNeutralButton(R.string.radio_editor_dialog_neutral_button, (dialog, id) -> {
|
||||
radioEditorViewModel.deleteRadio();
|
||||
dismissDialog();
|
||||
})
|
||||
.setNegativeButton(R.string.radio_editor_dialog_negative_button, (dialog, id) -> {
|
||||
dialog.cancel();
|
||||
})
|
||||
.create();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
|
||||
setParameterInfo();
|
||||
setButtonAction();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -75,25 +86,6 @@ public class RadioEditorDialog extends DialogFragment {
|
||||
}
|
||||
}
|
||||
|
||||
private void setButtonAction() {
|
||||
((AlertDialog) Objects.requireNonNull(getDialog())).getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> {
|
||||
if (validateInput()) {
|
||||
if (radioEditorViewModel.getRadioToEdit() == null) {
|
||||
radioEditorViewModel.createRadio(radioName, radioStreamURL, radioHomepageURL.isEmpty() ? null : radioHomepageURL);
|
||||
} else {
|
||||
radioEditorViewModel.updateRadio(radioName, radioStreamURL, radioHomepageURL.isEmpty() ? null : radioHomepageURL);
|
||||
}
|
||||
|
||||
dismissDialog();
|
||||
}
|
||||
});
|
||||
|
||||
((AlertDialog) Objects.requireNonNull(getDialog())).getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener(v -> {
|
||||
radioEditorViewModel.deleteRadio();
|
||||
dismissDialog();
|
||||
});
|
||||
}
|
||||
|
||||
private boolean validateInput() {
|
||||
radioName = Objects.requireNonNull(bind.internetRadioStationNameTextView.getText()).toString().trim();
|
||||
radioStreamURL = Objects.requireNonNull(bind.internetRadioStationStreamUrlTextView.getText()).toString().trim();
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.cappielloantonio.tempo.ui.dialog;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.os.Bundle;
|
||||
|
||||
@@ -12,6 +11,7 @@ import com.cappielloantonio.tempo.R;
|
||||
import com.cappielloantonio.tempo.databinding.DialogRatingBinding;
|
||||
import com.cappielloantonio.tempo.util.Constants;
|
||||
import com.cappielloantonio.tempo.viewmodel.RatingViewModel;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
|
||||
public class RatingDialog extends DialogFragment {
|
||||
private static final String TAG = "ServerSignupDialog";
|
||||
@@ -25,14 +25,12 @@ public class RatingDialog extends DialogFragment {
|
||||
bind = DialogRatingBinding.inflate(getLayoutInflater());
|
||||
ratingViewModel = new ViewModelProvider(requireActivity()).get(RatingViewModel.class);
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
|
||||
builder.setView(bind.getRoot())
|
||||
return new MaterialAlertDialogBuilder(getActivity())
|
||||
.setView(bind.getRoot())
|
||||
.setTitle(R.string.rating_dialog_title)
|
||||
.setNegativeButton(R.string.rating_dialog_negative_button, (dialog, id) -> dialog.cancel())
|
||||
.setPositiveButton(R.string.rating_dialog_positive_button, (dialog, id) -> ratingViewModel.rate((int) bind.ratingBar.getRating()));
|
||||
|
||||
return builder.create();
|
||||
.setPositiveButton(R.string.rating_dialog_positive_button, (dialog, id) -> ratingViewModel.rate((int) bind.ratingBar.getRating()))
|
||||
.create();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.cappielloantonio.tempo.ui.dialog;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
@@ -14,6 +13,7 @@ import com.cappielloantonio.tempo.databinding.DialogServerSignupBinding;
|
||||
import com.cappielloantonio.tempo.model.Server;
|
||||
import com.cappielloantonio.tempo.util.MusicUtil;
|
||||
import com.cappielloantonio.tempo.viewmodel.LoginViewModel;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
@@ -33,21 +33,17 @@ public class ServerSignupDialog extends DialogFragment {
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
loginViewModel = new ViewModelProvider(requireActivity()).get(LoginViewModel.class);
|
||||
|
||||
bind = DialogServerSignupBinding.inflate(getLayoutInflater());
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
loginViewModel = new ViewModelProvider(requireActivity()).get(LoginViewModel.class);
|
||||
|
||||
builder.setView(bind.getRoot())
|
||||
return new MaterialAlertDialogBuilder(getActivity())
|
||||
.setView(bind.getRoot())
|
||||
.setTitle(R.string.server_signup_dialog_title)
|
||||
.setNeutralButton(R.string.server_signup_dialog_neutral_button, (dialog, id) -> {
|
||||
})
|
||||
.setPositiveButton(R.string.server_signup_dialog_positive_button, (dialog, id) -> {
|
||||
})
|
||||
.setNegativeButton(R.string.server_signup_dialog_negative_button, (dialog, id) -> dialog.cancel());
|
||||
|
||||
return builder.create();
|
||||
.setNeutralButton(R.string.server_signup_dialog_neutral_button, (dialog, id) -> { })
|
||||
.setPositiveButton(R.string.server_signup_dialog_positive_button, (dialog, id) -> { })
|
||||
.setNegativeButton(R.string.server_signup_dialog_negative_button, (dialog, id) -> dialog.cancel())
|
||||
.create();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -81,14 +77,16 @@ public class ServerSignupDialog extends DialogFragment {
|
||||
}
|
||||
|
||||
private void setButtonAction() {
|
||||
((AlertDialog) Objects.requireNonNull(getDialog())).getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> {
|
||||
androidx.appcompat.app.AlertDialog alertDialog = (androidx.appcompat.app.AlertDialog) Objects.requireNonNull(getDialog());
|
||||
|
||||
alertDialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> {
|
||||
if (validateInput()) {
|
||||
saveServerPreference();
|
||||
Objects.requireNonNull(getDialog()).dismiss();
|
||||
}
|
||||
});
|
||||
|
||||
((AlertDialog) Objects.requireNonNull(getDialog())).getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener(v -> {
|
||||
alertDialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_NEUTRAL).setOnClickListener(v -> {
|
||||
loginViewModel.deleteServer(null);
|
||||
Objects.requireNonNull(getDialog()).dismiss();
|
||||
});
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package com.cappielloantonio.tempo.ui.dialog;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.OptIn;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
|
||||
@@ -13,34 +13,32 @@ import com.cappielloantonio.tempo.R;
|
||||
import com.cappielloantonio.tempo.databinding.DialogServerUnreachableBinding;
|
||||
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@OptIn(markerClass = UnstableApi.class)
|
||||
public class ServerUnreachableDialog extends DialogFragment {
|
||||
private static final String TAG = "ServerUnreachableDialog";
|
||||
|
||||
private DialogServerUnreachableBinding bind;
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
bind = DialogServerUnreachableBinding.inflate(getLayoutInflater());
|
||||
DialogServerUnreachableBinding bind = DialogServerUnreachableBinding.inflate(getLayoutInflater());
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
|
||||
builder.setView(bind.getRoot())
|
||||
AlertDialog popup = new MaterialAlertDialogBuilder(getActivity()).setView(bind.getRoot())
|
||||
.setTitle(R.string.server_unreachable_dialog_title)
|
||||
.setPositiveButton(R.string.server_unreachable_dialog_positive_button, null)
|
||||
.setNeutralButton(R.string.server_unreachable_dialog_neutral_button, null)
|
||||
.setNegativeButton(R.string.server_unreachable_dialog_negative_button, (dialog, id) -> dialog.cancel());
|
||||
.setNegativeButton(R.string.server_unreachable_dialog_negative_button, (dialog, id) -> dialog.cancel())
|
||||
.create();
|
||||
|
||||
AlertDialog popup = builder.create();
|
||||
|
||||
popup.setCancelable(false);
|
||||
popup.setCanceledOnTouchOutside(false);
|
||||
popup.setCancelable(false);
|
||||
|
||||
return popup;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
@@ -48,26 +46,18 @@ public class ServerUnreachableDialog extends DialogFragment {
|
||||
setButtonAction();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
bind = null;
|
||||
}
|
||||
|
||||
private void setButtonAction() {
|
||||
AlertDialog dialog = (AlertDialog) getDialog();
|
||||
androidx.appcompat.app.AlertDialog alertDialog = (androidx.appcompat.app.AlertDialog) Objects.requireNonNull(getDialog());
|
||||
|
||||
if(dialog != null) {
|
||||
(dialog).getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener(v -> {
|
||||
MainActivity activity = (MainActivity) getActivity();
|
||||
if (activity != null) activity.quit();
|
||||
dialog.dismiss();
|
||||
});
|
||||
alertDialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_NEUTRAL).setOnClickListener(v -> {
|
||||
MainActivity activity = (MainActivity) getActivity();
|
||||
if (activity != null) activity.quit();
|
||||
alertDialog.dismiss();
|
||||
});
|
||||
|
||||
(dialog).getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> {
|
||||
Preferences.setServerUnreachableDatetime(System.currentTimeMillis());
|
||||
dialog.dismiss();
|
||||
});
|
||||
}
|
||||
alertDialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> {
|
||||
Preferences.setServerUnreachableDatetime(System.currentTimeMillis());
|
||||
alertDialog.dismiss();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
package com.cappielloantonio.tempo.ui.dialog;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import com.cappielloantonio.tempo.R;
|
||||
import com.cappielloantonio.tempo.databinding.DialogShareUpdateBinding;
|
||||
import com.cappielloantonio.tempo.util.UIUtil;
|
||||
import com.cappielloantonio.tempo.viewmodel.HomeViewModel;
|
||||
import com.cappielloantonio.tempo.viewmodel.ShareBottomSheetViewModel;
|
||||
import com.google.android.material.datepicker.CalendarConstraints;
|
||||
import com.google.android.material.datepicker.DateValidatorPointForward;
|
||||
import com.google.android.material.datepicker.MaterialDatePicker;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Objects;
|
||||
|
||||
public class ShareUpdateDialog extends DialogFragment {
|
||||
private DialogShareUpdateBinding bind;
|
||||
private HomeViewModel homeViewModel;
|
||||
private ShareBottomSheetViewModel shareBottomSheetViewModel;
|
||||
|
||||
private MaterialDatePicker<Long> datePicker;
|
||||
|
||||
private String descriptionTextView;
|
||||
private String expirationTextView;
|
||||
private long expiration;
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
homeViewModel = new ViewModelProvider(requireActivity()).get(HomeViewModel.class);
|
||||
|
||||
shareBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(ShareBottomSheetViewModel.class);
|
||||
|
||||
bind = DialogShareUpdateBinding.inflate(getLayoutInflater());
|
||||
|
||||
return new MaterialAlertDialogBuilder(requireContext())
|
||||
.setView(bind.getRoot())
|
||||
.setTitle(R.string.share_update_dialog_title)
|
||||
.setPositiveButton(R.string.share_update_dialog_positive_button, (dialog, id) -> {
|
||||
})
|
||||
.setNegativeButton(R.string.share_update_dialog_negative_button, (dialog, id) -> dialog.cancel())
|
||||
.create();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
|
||||
setShareInfo();
|
||||
setShareCalendar();
|
||||
setButtonAction();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
bind = null;
|
||||
}
|
||||
|
||||
private void setShareInfo() {
|
||||
if (shareBottomSheetViewModel.getShare() != null) {
|
||||
bind.shareDescriptionTextView.setText(shareBottomSheetViewModel.getShare().getDescription());
|
||||
// bind.shareExpirationTextView.setText(shareBottomSheetViewModel.getShare().getExpires());
|
||||
}
|
||||
}
|
||||
|
||||
private void setShareCalendar() {
|
||||
expiration = shareBottomSheetViewModel.getShare().getExpires().getTime();
|
||||
|
||||
bind.shareExpirationTextView.setText(UIUtil.getReadableDate(new Date(expiration)));
|
||||
|
||||
bind.shareExpirationTextView.setFocusable(false);
|
||||
bind.shareExpirationTextView.setOnLongClickListener(null);
|
||||
|
||||
bind.shareExpirationTextView.setOnClickListener(view -> {
|
||||
CalendarConstraints constraints = new CalendarConstraints.Builder()
|
||||
.setValidator(DateValidatorPointForward.now())
|
||||
.build();
|
||||
|
||||
datePicker = MaterialDatePicker.Builder.datePicker()
|
||||
.setCalendarConstraints(constraints)
|
||||
.setSelection(expiration)
|
||||
.build();
|
||||
|
||||
datePicker.addOnPositiveButtonClickListener(selection -> {
|
||||
expiration = selection;
|
||||
bind.shareExpirationTextView.setText(UIUtil.getReadableDate(new Date(selection)));
|
||||
});
|
||||
|
||||
datePicker.show(requireActivity().getSupportFragmentManager(), null);
|
||||
});
|
||||
}
|
||||
|
||||
private void setButtonAction() {
|
||||
((AlertDialog) Objects.requireNonNull(getDialog())).getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> {
|
||||
if (validateInput()) {
|
||||
updateShare();
|
||||
Objects.requireNonNull(getDialog()).dismiss();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private boolean validateInput() {
|
||||
descriptionTextView = Objects.requireNonNull(bind.shareDescriptionTextView.getText()).toString().trim();
|
||||
expirationTextView = Objects.requireNonNull(bind.shareExpirationTextView.getText()).toString().trim();
|
||||
|
||||
if (TextUtils.isEmpty(descriptionTextView)) {
|
||||
bind.shareDescriptionTextView.setError(getString(R.string.error_required));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (TextUtils.isEmpty(expirationTextView)) {
|
||||
bind.shareExpirationTextView.setError(getString(R.string.error_required));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void updateShare() {
|
||||
shareBottomSheetViewModel.updateShare(descriptionTextView, expiration);
|
||||
homeViewModel.refreshShares(requireActivity());
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.cappielloantonio.tempo.ui.dialog;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
@@ -19,31 +18,28 @@ import com.cappielloantonio.tempo.util.DownloadUtil;
|
||||
import com.cappielloantonio.tempo.util.MappingUtil;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
import com.cappielloantonio.tempo.viewmodel.StarredSyncViewModel;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@OptIn(markerClass = UnstableApi.class)
|
||||
public class StarredSyncDialog extends DialogFragment {
|
||||
private static final String TAG = "ServerUnreachableDialog";
|
||||
|
||||
private DialogStarredSyncBinding bind;
|
||||
private StarredSyncViewModel starredSyncViewModel;
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
bind = DialogStarredSyncBinding.inflate(getLayoutInflater());
|
||||
DialogStarredSyncBinding bind = DialogStarredSyncBinding.inflate(getLayoutInflater());
|
||||
|
||||
starredSyncViewModel = new ViewModelProvider(requireActivity()).get(StarredSyncViewModel.class);
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
|
||||
builder.setView(bind.getRoot())
|
||||
return new MaterialAlertDialogBuilder(getActivity())
|
||||
.setView(bind.getRoot())
|
||||
.setTitle(R.string.starred_sync_dialog_title)
|
||||
.setPositiveButton(R.string.starred_sync_dialog_positive_button, null)
|
||||
.setNeutralButton(R.string.starred_sync_dialog_neutral_button, null)
|
||||
.setNegativeButton(R.string.starred_sync_dialog_negative_button, null);
|
||||
|
||||
return builder.create();
|
||||
.setNegativeButton(R.string.starred_sync_dialog_negative_button, null)
|
||||
.create();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -52,14 +48,8 @@ public class StarredSyncDialog extends DialogFragment {
|
||||
setButtonAction(requireContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
bind = null;
|
||||
}
|
||||
|
||||
private void setButtonAction(Context context) {
|
||||
AlertDialog dialog = ((AlertDialog) getDialog());
|
||||
androidx.appcompat.app.AlertDialog dialog = (androidx.appcompat.app.AlertDialog) getDialog();
|
||||
|
||||
if (dialog != null) {
|
||||
Button positiveButton = dialog.getButton(Dialog.BUTTON_POSITIVE);
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
package com.cappielloantonio.tempo.ui.dialog;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.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;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
|
||||
public class TrackInfoDialog extends DialogFragment {
|
||||
private DialogTrackInfoBinding bind;
|
||||
|
||||
private final MediaMetadata mediaMetadata;
|
||||
|
||||
public TrackInfoDialog(MediaMetadata mediaMetadata) {
|
||||
this.mediaMetadata = mediaMetadata;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
bind = DialogTrackInfoBinding.inflate(getLayoutInflater());
|
||||
|
||||
return new MaterialAlertDialogBuilder(requireActivity())
|
||||
.setView(bind.getRoot())
|
||||
.setPositiveButton(R.string.track_info_dialog_positive_button, (dialog, id) -> dialog.cancel())
|
||||
.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", ""), CustomGlideRequest.ResourceType.Song)
|
||||
.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,6 +52,12 @@ public class AlbumCatalogueFragment extends Fragment implements ClickCallback {
|
||||
initData();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
albumCatalogueViewModel.stopLoading();
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
activity = (MainActivity) getActivity();
|
||||
@@ -73,7 +79,7 @@ public class AlbumCatalogueFragment extends Fragment implements ClickCallback {
|
||||
|
||||
private void initData() {
|
||||
albumCatalogueViewModel = new ViewModelProvider(requireActivity()).get(AlbumCatalogueViewModel.class);
|
||||
albumCatalogueViewModel.loadAlbums(500);
|
||||
albumCatalogueViewModel.loadAlbums();
|
||||
}
|
||||
|
||||
private void initAppBar() {
|
||||
|
||||
@@ -172,7 +172,7 @@ public class AlbumPageFragment extends Fragment implements ClickCallback {
|
||||
|
||||
private void initBackCover() {
|
||||
CustomGlideRequest.Builder
|
||||
.from(requireContext(), albumPageViewModel.getAlbum().getCoverArtId())
|
||||
.from(requireContext(), albumPageViewModel.getAlbum().getCoverArtId(), CustomGlideRequest.ResourceType.Album)
|
||||
.build()
|
||||
.into(bind.albumCoverImageView);
|
||||
}
|
||||
@@ -181,7 +181,7 @@ public class AlbumPageFragment extends Fragment implements ClickCallback {
|
||||
bind.songRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
|
||||
bind.songRecyclerView.setHasFixedSize(true);
|
||||
|
||||
songHorizontalAdapter = new SongHorizontalAdapter(this, false);
|
||||
songHorizontalAdapter = new SongHorizontalAdapter(this, false, false);
|
||||
bind.songRecyclerView.setAdapter(songHorizontalAdapter);
|
||||
|
||||
albumPageViewModel.getAlbumSongLiveList().observe(getViewLifecycleOwner(), songs -> songHorizontalAdapter.setItems(songs));
|
||||
|
||||
@@ -16,21 +16,25 @@ import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.session.MediaBrowser;
|
||||
import androidx.media3.session.SessionToken;
|
||||
import androidx.navigation.Navigation;
|
||||
import androidx.recyclerview.widget.GridLayoutManager;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
|
||||
import com.cappielloantonio.tempo.R;
|
||||
import com.cappielloantonio.tempo.databinding.FragmentArtistPageBinding;
|
||||
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
||||
import com.cappielloantonio.tempo.helper.recyclerview.CustomLinearSnapHelper;
|
||||
import com.cappielloantonio.tempo.helper.recyclerview.GridItemDecoration;
|
||||
import com.cappielloantonio.tempo.interfaces.ClickCallback;
|
||||
import com.cappielloantonio.tempo.service.MediaManager;
|
||||
import com.cappielloantonio.tempo.service.MediaService;
|
||||
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
||||
import com.cappielloantonio.tempo.ui.adapter.AlbumArtistPageOrSimilarAdapter;
|
||||
import com.cappielloantonio.tempo.ui.adapter.AlbumCatalogueAdapter;
|
||||
import com.cappielloantonio.tempo.ui.adapter.ArtistSimilarAdapter;
|
||||
import com.cappielloantonio.tempo.ui.adapter.SongHorizontalAdapter;
|
||||
import com.cappielloantonio.tempo.util.Constants;
|
||||
import com.cappielloantonio.tempo.util.MusicUtil;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
import com.cappielloantonio.tempo.viewmodel.ArtistPageViewModel;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
|
||||
@@ -42,6 +46,7 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
|
||||
|
||||
private SongHorizontalAdapter songHorizontalAdapter;
|
||||
private AlbumArtistPageOrSimilarAdapter albumArtistPageOrSimilarAdapter;
|
||||
private AlbumCatalogueAdapter albumCatalogueAdapter;
|
||||
private ArtistSimilarAdapter artistSimilarAdapter;
|
||||
|
||||
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
||||
@@ -59,7 +64,8 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
|
||||
initArtistInfo();
|
||||
initPlayButtons();
|
||||
initTopSongsView();
|
||||
initAlbumsView();
|
||||
initHorizontalAlbumsView();
|
||||
initVerticalAlbumsView();
|
||||
initSimilarArtistsView();
|
||||
|
||||
return view;
|
||||
@@ -93,6 +99,18 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
|
||||
bundle.putParcelable(Constants.ARTIST_OBJECT, artistPageViewModel.getArtist());
|
||||
activity.navController.navigate(R.id.action_artistPageFragment_to_songListPageFragment, bundle);
|
||||
});
|
||||
|
||||
bind.artistPageAlbumsSwitchLayoutTextViewClickable.setOnClickListener(view -> {
|
||||
boolean isHorizontalRecyclerViewVisible = bind.albumsHorizontalRecyclerView.getVisibility() == View.VISIBLE;
|
||||
|
||||
bind.albumsHorizontalRecyclerView.setVisibility(isHorizontalRecyclerViewVisible ? View.GONE : View.VISIBLE);
|
||||
bind.albumsVerticalRecyclerView.setVisibility(isHorizontalRecyclerViewVisible ? View.VISIBLE : View.GONE);
|
||||
|
||||
Preferences.setArtistAlbumLayout(!isHorizontalRecyclerViewVisible);
|
||||
});
|
||||
|
||||
bind.albumsHorizontalRecyclerView.setVisibility(Preferences.isArtistAlbumLayoutHorizontal() ? View.VISIBLE : View.GONE);
|
||||
bind.albumsVerticalRecyclerView.setVisibility(Preferences.isArtistAlbumLayoutHorizontal() ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
|
||||
private void initAppBar() {
|
||||
@@ -107,8 +125,9 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
|
||||
|
||||
private void initArtistInfo() {
|
||||
artistPageViewModel.getArtistInfo(artistPageViewModel.getArtist().getId()).observe(getViewLifecycleOwner(), artistInfo -> {
|
||||
if(artistInfo == null) {
|
||||
if (bind != null) bind.artistPageBioPlaceholder.placeholder.setVisibility(View.VISIBLE);
|
||||
if (artistInfo == null) {
|
||||
if (bind != null)
|
||||
bind.artistPageBioPlaceholder.placeholder.setVisibility(View.VISIBLE);
|
||||
if (bind != null) bind.artistPageBioSector.setVisibility(View.GONE);
|
||||
} else {
|
||||
String normalizedBio = MusicUtil.forceReadableString(artistInfo.getBiography());
|
||||
@@ -119,7 +138,7 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
|
||||
bind.bioMoreTextViewClickable.setVisibility(artistInfo.getLastFmUrl() != null ? View.VISIBLE : View.GONE);
|
||||
|
||||
if (getContext() != null && bind != null) CustomGlideRequest.Builder
|
||||
.from(requireContext(), artistPageViewModel.getArtist().getId())
|
||||
.from(requireContext(), artistPageViewModel.getArtist().getId(), CustomGlideRequest.ResourceType.Artist)
|
||||
.build()
|
||||
.into(bind.artistBackdropImageView);
|
||||
|
||||
@@ -131,7 +150,8 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
|
||||
startActivity(intent);
|
||||
});
|
||||
|
||||
if (bind != null) bind.artistPageBioPlaceholder.placeholder.setVisibility(View.GONE);
|
||||
if (bind != null)
|
||||
bind.artistPageBioPlaceholder.placeholder.setVisibility(View.GONE);
|
||||
if (bind != null) bind.artistPageBioSector.setVisibility(View.VISIBLE);
|
||||
}
|
||||
});
|
||||
@@ -164,38 +184,67 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
|
||||
private void initTopSongsView() {
|
||||
bind.mostStreamedSongRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
|
||||
|
||||
songHorizontalAdapter = new SongHorizontalAdapter(this, true);
|
||||
songHorizontalAdapter = new SongHorizontalAdapter(this, true, true);
|
||||
bind.mostStreamedSongRecyclerView.setAdapter(songHorizontalAdapter);
|
||||
artistPageViewModel.getArtistTopSongList().observe(getViewLifecycleOwner(), songs -> {
|
||||
if (songs == null) {
|
||||
if (bind != null) bind.artistPageTopTracksPlaceholder.placeholder.setVisibility(View.VISIBLE);
|
||||
if (bind != null)
|
||||
bind.artistPageTopTracksPlaceholder.placeholder.setVisibility(View.VISIBLE);
|
||||
if (bind != null) bind.artistPageTopSongsSector.setVisibility(View.GONE);
|
||||
} else {
|
||||
if (bind != null) bind.artistPageTopTracksPlaceholder.placeholder.setVisibility(View.GONE);
|
||||
if (bind != null) bind.artistPageTopSongsSector.setVisibility(!songs.isEmpty() ? View.VISIBLE : View.GONE);
|
||||
if (bind != null)
|
||||
bind.artistPageTopTracksPlaceholder.placeholder.setVisibility(View.GONE);
|
||||
if (bind != null)
|
||||
bind.artistPageTopSongsSector.setVisibility(!songs.isEmpty() ? View.VISIBLE : View.GONE);
|
||||
songHorizontalAdapter.setItems(songs);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void initAlbumsView() {
|
||||
bind.albumsRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false));
|
||||
private void initHorizontalAlbumsView() {
|
||||
bind.albumsHorizontalRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false));
|
||||
|
||||
albumArtistPageOrSimilarAdapter = new AlbumArtistPageOrSimilarAdapter(this);
|
||||
bind.albumsRecyclerView.setAdapter(albumArtistPageOrSimilarAdapter);
|
||||
bind.albumsHorizontalRecyclerView.setAdapter(albumArtistPageOrSimilarAdapter);
|
||||
artistPageViewModel.getAlbumList().observe(getViewLifecycleOwner(), albums -> {
|
||||
if (albums == null) {
|
||||
if (bind != null) bind.artistPageAlbumPlaceholder.placeholder.setVisibility(View.VISIBLE);
|
||||
if (bind != null)
|
||||
bind.artistPageAlbumPlaceholder.placeholder.setVisibility(View.VISIBLE);
|
||||
if (bind != null) bind.artistPageAlbumsSector.setVisibility(View.GONE);
|
||||
} else {
|
||||
if (bind != null) bind.artistPageAlbumPlaceholder.placeholder.setVisibility(View.GONE);
|
||||
if (bind != null) bind.artistPageAlbumsSector.setVisibility(!albums.isEmpty() ? View.VISIBLE : View.GONE);
|
||||
if (bind != null)
|
||||
bind.artistPageAlbumPlaceholder.placeholder.setVisibility(View.GONE);
|
||||
if (bind != null)
|
||||
bind.artistPageAlbumsSector.setVisibility(!albums.isEmpty() ? View.VISIBLE : View.GONE);
|
||||
albumArtistPageOrSimilarAdapter.setItems(albums);
|
||||
}
|
||||
});
|
||||
|
||||
CustomLinearSnapHelper albumSnapHelper = new CustomLinearSnapHelper();
|
||||
albumSnapHelper.attachToRecyclerView(bind.albumsRecyclerView);
|
||||
albumSnapHelper.attachToRecyclerView(bind.albumsHorizontalRecyclerView);
|
||||
}
|
||||
|
||||
private void initVerticalAlbumsView() {
|
||||
bind.albumsVerticalRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), 2));
|
||||
bind.albumsVerticalRecyclerView.addItemDecoration(new GridItemDecoration(2, 20, false));
|
||||
bind.albumsVerticalRecyclerView.setHasFixedSize(true);
|
||||
|
||||
albumCatalogueAdapter = new AlbumCatalogueAdapter(this);
|
||||
bind.albumsVerticalRecyclerView.setAdapter(albumCatalogueAdapter);
|
||||
|
||||
artistPageViewModel.getAlbumList().observe(getViewLifecycleOwner(), albums -> {
|
||||
if (albums == null) {
|
||||
if (bind != null)
|
||||
bind.artistPageAlbumPlaceholder.placeholder.setVisibility(View.VISIBLE);
|
||||
if (bind != null) bind.artistPageAlbumsSector.setVisibility(View.GONE);
|
||||
} else {
|
||||
if (bind != null)
|
||||
bind.artistPageAlbumPlaceholder.placeholder.setVisibility(View.GONE);
|
||||
if (bind != null)
|
||||
bind.artistPageAlbumsSector.setVisibility(!albums.isEmpty() ? View.VISIBLE : View.GONE);
|
||||
albumCatalogueAdapter.setItems(albums);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void initSimilarArtistsView() {
|
||||
@@ -206,11 +255,14 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
|
||||
bind.similarArtistsRecyclerView.setAdapter(artistSimilarAdapter);
|
||||
artistPageViewModel.getArtistInfo(artistPageViewModel.getArtist().getId()).observe(getViewLifecycleOwner(), artist -> {
|
||||
if (artist == null) {
|
||||
if (bind != null) bind.artistPageSimilarArtistPlaceholder.placeholder.setVisibility(View.VISIBLE);
|
||||
if (bind != null)
|
||||
bind.artistPageSimilarArtistPlaceholder.placeholder.setVisibility(View.VISIBLE);
|
||||
if (bind != null) bind.similarArtistSector.setVisibility(View.GONE);
|
||||
} else {
|
||||
if (bind != null) bind.artistPageSimilarArtistPlaceholder.placeholder.setVisibility(View.GONE);
|
||||
if (bind != null) bind.similarArtistSector.setVisibility(!artist.getSimilarArtists().isEmpty() ? View.VISIBLE : View.GONE);
|
||||
if (bind != null)
|
||||
bind.artistPageSimilarArtistPlaceholder.placeholder.setVisibility(View.GONE);
|
||||
if (bind != null)
|
||||
bind.similarArtistSector.setVisibility(!artist.getSimilarArtists().isEmpty() ? View.VISIBLE : View.GONE);
|
||||
artistSimilarAdapter.setItems(artist.getSimilarArtists());
|
||||
}
|
||||
});
|
||||
|
||||
@@ -23,12 +23,14 @@ import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import com.cappielloantonio.tempo.R;
|
||||
import com.cappielloantonio.tempo.databinding.FragmentDirectoryBinding;
|
||||
import com.cappielloantonio.tempo.interfaces.ClickCallback;
|
||||
import com.cappielloantonio.tempo.interfaces.DialogClickCallback;
|
||||
import com.cappielloantonio.tempo.model.Download;
|
||||
import com.cappielloantonio.tempo.service.MediaManager;
|
||||
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.adapter.MusicDirectoryAdapter;
|
||||
import com.cappielloantonio.tempo.ui.dialog.DownloadDirectoryDialog;
|
||||
import com.cappielloantonio.tempo.util.Constants;
|
||||
import com.cappielloantonio.tempo.util.DownloadUtil;
|
||||
import com.cappielloantonio.tempo.util.MappingUtil;
|
||||
@@ -50,6 +52,8 @@ public class DirectoryFragment extends Fragment implements ClickCallback {
|
||||
|
||||
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
||||
|
||||
private MenuItem menuItem;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
@@ -60,6 +64,8 @@ public class DirectoryFragment extends Fragment implements ClickCallback {
|
||||
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
inflater.inflate(R.menu.directory_page_menu, menu);
|
||||
|
||||
menuItem = menu.getItem(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -97,15 +103,23 @@ public class DirectoryFragment extends Fragment implements ClickCallback {
|
||||
@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())
|
||||
);
|
||||
DownloadDirectoryDialog dialog = new DownloadDirectoryDialog(new DialogClickCallback() {
|
||||
@Override
|
||||
public void onPositiveClick() {
|
||||
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())
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
dialog.show(activity.getSupportFragmentManager(), null);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -144,6 +158,14 @@ public class DirectoryFragment extends Fragment implements ClickCallback {
|
||||
bind.directoryTitleLabel.setText(directory.getName());
|
||||
|
||||
musicDirectoryAdapter.setItems(directory.getChildren());
|
||||
|
||||
menuItem.setVisible(
|
||||
directory.getChildren() != null && directory.getChildren()
|
||||
.stream()
|
||||
.filter(child -> !child.isDir())
|
||||
.findFirst()
|
||||
.orElse(null) != null
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -160,6 +182,11 @@ public class DirectoryFragment extends Fragment implements ClickCallback {
|
||||
MediaManager.startQueue(mediaBrowserListenableFuture, bundle.getParcelableArrayList(Constants.TRACKS_OBJECT), bundle.getInt(Constants.ITEM_POSITION));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMediaLongClick(Bundle bundle) {
|
||||
Navigation.findNavController(requireView()).navigate(R.id.songBottomSheetDialog, bundle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMusicDirectoryClick(Bundle bundle) {
|
||||
Navigation.findNavController(requireView()).navigate(R.id.directoryFragment, bundle);
|
||||
|
||||
@@ -7,6 +7,7 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.PopupMenu;
|
||||
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
@@ -32,11 +33,14 @@ import com.cappielloantonio.tempo.viewmodel.DownloadViewModel;
|
||||
import com.google.android.material.appbar.MaterialToolbar;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
@UnstableApi
|
||||
public class DownloadFragment extends Fragment implements ClickCallback {
|
||||
private static final String TAG = "DownloadFragment";
|
||||
|
||||
private FragmentDownloadBinding bind;
|
||||
private MainActivity activity;
|
||||
private DownloadViewModel downloadViewModel;
|
||||
@@ -160,6 +164,36 @@ public class DownloadFragment extends Fragment implements ClickCallback {
|
||||
}
|
||||
|
||||
bind.downloadedGoBackImageView.setVisibility(stack.size() > 1 ? View.VISIBLE : View.GONE);
|
||||
|
||||
setupBackPressing(stack.size());
|
||||
setupShuffleButton();
|
||||
});
|
||||
}
|
||||
|
||||
private void setupShuffleButton() {
|
||||
bind.shuffleDownloadedTextViewClickable.setOnClickListener(view -> {
|
||||
List<Child> songs = downloadHorizontalAdapter.getShuffling();
|
||||
|
||||
if (songs != null && !songs.isEmpty()) {
|
||||
Collections.shuffle(songs);
|
||||
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
|
||||
activity.setBottomSheetInPeek(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setupBackPressing(int stackSize) {
|
||||
requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(), new OnBackPressedCallback(true) {
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
if (stackSize > 1) {
|
||||
downloadViewModel.popViewStack();
|
||||
} else {
|
||||
activity.navController.navigateUp();
|
||||
}
|
||||
|
||||
remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -234,4 +268,9 @@ public class DownloadFragment extends Fragment implements ClickCallback {
|
||||
public void onMediaLongClick(Bundle bundle) {
|
||||
Navigation.findNavController(requireView()).navigate(R.id.songBottomSheetDialog, bundle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDownloadGroupLongClick(Bundle bundle) {
|
||||
Navigation.findNavController(requireView()).navigate(R.id.downloadBottomSheetDialog, bundle);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package com.cappielloantonio.tempo.ui.fragment;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -34,6 +36,7 @@ import com.cappielloantonio.tempo.service.DownloaderManager;
|
||||
import com.cappielloantonio.tempo.service.MediaManager;
|
||||
import com.cappielloantonio.tempo.service.MediaService;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Share;
|
||||
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
||||
import com.cappielloantonio.tempo.ui.adapter.AlbumAdapter;
|
||||
import com.cappielloantonio.tempo.ui.adapter.AlbumHorizontalAdapter;
|
||||
@@ -41,12 +44,14 @@ import com.cappielloantonio.tempo.ui.adapter.ArtistAdapter;
|
||||
import com.cappielloantonio.tempo.ui.adapter.ArtistHorizontalAdapter;
|
||||
import com.cappielloantonio.tempo.ui.adapter.DiscoverSongAdapter;
|
||||
import com.cappielloantonio.tempo.ui.adapter.GridTrackAdapter;
|
||||
import com.cappielloantonio.tempo.ui.adapter.ShareHorizontalAdapter;
|
||||
import com.cappielloantonio.tempo.ui.adapter.SimilarTrackAdapter;
|
||||
import com.cappielloantonio.tempo.ui.adapter.SongHorizontalAdapter;
|
||||
import com.cappielloantonio.tempo.ui.adapter.YearAdapter;
|
||||
import com.cappielloantonio.tempo.util.Constants;
|
||||
import com.cappielloantonio.tempo.util.DownloadUtil;
|
||||
import com.cappielloantonio.tempo.util.MappingUtil;
|
||||
import com.cappielloantonio.tempo.util.MusicUtil;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
import com.cappielloantonio.tempo.util.UIUtil;
|
||||
import com.cappielloantonio.tempo.viewmodel.HomeViewModel;
|
||||
@@ -77,6 +82,7 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||
private AlbumHorizontalAdapter newReleasesAlbumAdapter;
|
||||
private YearAdapter yearAdapter;
|
||||
private GridTrackAdapter gridTrackAdapter;
|
||||
private ShareHorizontalAdapter shareHorizontalAdapter;
|
||||
|
||||
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
||||
|
||||
@@ -112,6 +118,7 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||
initYearSongView();
|
||||
initRecentAddedAlbumView();
|
||||
initGridView();
|
||||
initSharesView();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -121,6 +128,12 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||
initializeMediaBrowser();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
refreshSharesView();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
releaseMediaBrowser();
|
||||
@@ -141,6 +154,8 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||
|
||||
bind.discoveryTextViewClickable.setOnClickListener(v -> {
|
||||
homeViewModel.getRandomShuffleSample().observe(getViewLifecycleOwner(), songs -> {
|
||||
MusicUtil.ratingFilter(songs);
|
||||
|
||||
if (songs.size() > 0) {
|
||||
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
|
||||
activity.setBottomSheetInPeek(true);
|
||||
@@ -228,6 +243,11 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||
homeViewModel.refreshMostRecentlyAddedAlbums(getViewLifecycleOwner());
|
||||
return true;
|
||||
});
|
||||
|
||||
bind.sharesTextViewRefreshable.setOnLongClickListener(v -> {
|
||||
homeViewModel.refreshShares(getViewLifecycleOwner());
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void initSyncStarredView() {
|
||||
@@ -289,12 +309,17 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||
bind.discoverSongViewPager.setAdapter(discoverSongAdapter);
|
||||
bind.discoverSongViewPager.setOffscreenPageLimit(1);
|
||||
homeViewModel.getDiscoverSongSample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), songs -> {
|
||||
MusicUtil.ratingFilter(songs);
|
||||
|
||||
if (songs == null) {
|
||||
if (bind != null) bind.homeDiscoveryPlaceholder.placeholder.setVisibility(View.VISIBLE);
|
||||
if (bind != null)
|
||||
bind.homeDiscoveryPlaceholder.placeholder.setVisibility(View.VISIBLE);
|
||||
if (bind != null) bind.homeDiscoverSector.setVisibility(View.GONE);
|
||||
} else {
|
||||
if (bind != null) bind.homeDiscoveryPlaceholder.placeholder.setVisibility(View.GONE);
|
||||
if (bind != null) bind.homeDiscoverSector.setVisibility(!songs.isEmpty() ? View.VISIBLE : View.GONE);
|
||||
if (bind != null)
|
||||
bind.homeDiscoveryPlaceholder.placeholder.setVisibility(View.GONE);
|
||||
if (bind != null)
|
||||
bind.homeDiscoverSector.setVisibility(!songs.isEmpty() ? View.VISIBLE : View.GONE);
|
||||
|
||||
discoverSongAdapter.setItems(songs);
|
||||
}
|
||||
@@ -310,12 +335,17 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||
similarMusicAdapter = new SimilarTrackAdapter(this);
|
||||
bind.similarTracksRecyclerView.setAdapter(similarMusicAdapter);
|
||||
homeViewModel.getStarredTracksSample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), songs -> {
|
||||
MusicUtil.ratingFilter(songs);
|
||||
|
||||
if (songs == null) {
|
||||
if (bind != null) bind.homeSimilarTracksPlaceholder.placeholder.setVisibility(View.VISIBLE);
|
||||
if (bind != null)
|
||||
bind.homeSimilarTracksPlaceholder.placeholder.setVisibility(View.VISIBLE);
|
||||
if (bind != null) bind.homeSimilarTracksSector.setVisibility(View.GONE);
|
||||
} else {
|
||||
if (bind != null) bind.homeSimilarTracksPlaceholder.placeholder.setVisibility(View.GONE);
|
||||
if (bind != null) bind.homeSimilarTracksSector.setVisibility(!songs.isEmpty() ? View.VISIBLE : View.GONE);
|
||||
if (bind != null)
|
||||
bind.homeSimilarTracksPlaceholder.placeholder.setVisibility(View.GONE);
|
||||
if (bind != null)
|
||||
bind.homeSimilarTracksSector.setVisibility(!songs.isEmpty() ? View.VISIBLE : View.GONE);
|
||||
|
||||
similarMusicAdapter.setItems(songs);
|
||||
}
|
||||
@@ -333,11 +363,14 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||
bind.bestOfArtistRecyclerView.setAdapter(bestOfArtistAdapter);
|
||||
homeViewModel.getBestOfArtists(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), artists -> {
|
||||
if (artists == null) {
|
||||
if (bind != null) bind.homeBestOfArtistPlaceholder.placeholder.setVisibility(View.VISIBLE);
|
||||
if (bind != null)
|
||||
bind.homeBestOfArtistPlaceholder.placeholder.setVisibility(View.VISIBLE);
|
||||
if (bind != null) bind.homeBestOfArtistSector.setVisibility(View.GONE);
|
||||
} else {
|
||||
if (bind != null) bind.homeBestOfArtistPlaceholder.placeholder.setVisibility(View.GONE);
|
||||
if (bind != null) bind.homeBestOfArtistSector.setVisibility(!artists.isEmpty() ? View.VISIBLE : View.GONE);
|
||||
if (bind != null)
|
||||
bind.homeBestOfArtistPlaceholder.placeholder.setVisibility(View.GONE);
|
||||
if (bind != null)
|
||||
bind.homeBestOfArtistSector.setVisibility(!artists.isEmpty() ? View.VISIBLE : View.GONE);
|
||||
|
||||
bestOfArtistAdapter.setItems(artists);
|
||||
}
|
||||
@@ -355,12 +388,16 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||
bind.radioArtistRecyclerView.setAdapter(radioArtistAdapter);
|
||||
homeViewModel.getStarredArtistsSample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), artists -> {
|
||||
if (artists == null) {
|
||||
if (bind != null) bind.homeRadioArtistPlaceholder.placeholder.setVisibility(View.VISIBLE);
|
||||
if (bind != null)
|
||||
bind.homeRadioArtistPlaceholder.placeholder.setVisibility(View.VISIBLE);
|
||||
if (bind != null) bind.homeRadioArtistSector.setVisibility(View.GONE);
|
||||
} else {
|
||||
if (bind != null) bind.homeRadioArtistPlaceholder.placeholder.setVisibility(View.GONE);
|
||||
if (bind != null) bind.homeRadioArtistSector.setVisibility(!artists.isEmpty() ? View.VISIBLE : View.GONE);
|
||||
if (bind != null) bind.afterRadioArtistDivider.setVisibility(!artists.isEmpty() ? View.VISIBLE : View.GONE);
|
||||
if (bind != null)
|
||||
bind.homeRadioArtistPlaceholder.placeholder.setVisibility(View.GONE);
|
||||
if (bind != null)
|
||||
bind.homeRadioArtistSector.setVisibility(!artists.isEmpty() ? View.VISIBLE : View.GONE);
|
||||
if (bind != null)
|
||||
bind.afterRadioArtistDivider.setVisibility(!artists.isEmpty() ? View.VISIBLE : View.GONE);
|
||||
|
||||
radioArtistAdapter.setItems(artists);
|
||||
}
|
||||
@@ -378,14 +415,18 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||
gridTrackAdapter = new GridTrackAdapter(this);
|
||||
bind.gridTracksRecyclerView.setAdapter(gridTrackAdapter);
|
||||
|
||||
homeViewModel.getGridSongSample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), chronologies -> {
|
||||
if (chronologies == null || chronologies.size() == 0) {
|
||||
if (bind != null) bind.homeGridTracksSector.setVisibility(View.GONE);
|
||||
if (bind != null) bind.afterGridDivider.setVisibility(View.GONE);
|
||||
} else {
|
||||
if (bind != null) bind.homeGridTracksSector.setVisibility(View.VISIBLE);
|
||||
if (bind != null) bind.afterGridDivider.setVisibility(View.VISIBLE);
|
||||
gridTrackAdapter.setItems(chronologies);
|
||||
homeViewModel.getDiscoverSongSample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), music -> {
|
||||
if (music != null) {
|
||||
homeViewModel.getGridSongSample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), chronologies -> {
|
||||
if (chronologies == null || chronologies.size() == 0) {
|
||||
if (bind != null) bind.homeGridTracksSector.setVisibility(View.GONE);
|
||||
if (bind != null) bind.afterGridDivider.setVisibility(View.GONE);
|
||||
} else {
|
||||
if (bind != null) bind.homeGridTracksSector.setVisibility(View.VISIBLE);
|
||||
if (bind != null) bind.afterGridDivider.setVisibility(View.VISIBLE);
|
||||
gridTrackAdapter.setItems(chronologies);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -393,16 +434,20 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||
private void initStarredTracksView() {
|
||||
bind.starredTracksRecyclerView.setHasFixedSize(true);
|
||||
|
||||
starredSongAdapter = new SongHorizontalAdapter(this, true);
|
||||
starredSongAdapter = new SongHorizontalAdapter(this, true, false);
|
||||
bind.starredTracksRecyclerView.setAdapter(starredSongAdapter);
|
||||
homeViewModel.getStarredTracks(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), songs -> {
|
||||
if (songs == null) {
|
||||
if (bind != null) bind.starredTracksPlaceholder.placeholder.setVisibility(View.VISIBLE);
|
||||
if (bind != null)
|
||||
bind.starredTracksPlaceholder.placeholder.setVisibility(View.VISIBLE);
|
||||
if (bind != null) bind.starredTracksSector.setVisibility(View.GONE);
|
||||
} else {
|
||||
if (bind != null) bind.starredTracksPlaceholder.placeholder.setVisibility(View.GONE);
|
||||
if (bind != null) bind.starredTracksSector.setVisibility(!songs.isEmpty() ? View.VISIBLE : View.GONE);
|
||||
if (bind != null) bind.starredTracksRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), UIUtil.getSpanCount(songs.size(), 5), GridLayoutManager.HORIZONTAL, false));
|
||||
if (bind != null)
|
||||
bind.starredTracksPlaceholder.placeholder.setVisibility(View.GONE);
|
||||
if (bind != null)
|
||||
bind.starredTracksSector.setVisibility(!songs.isEmpty() ? View.VISIBLE : View.GONE);
|
||||
if (bind != null)
|
||||
bind.starredTracksRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), UIUtil.getSpanCount(songs.size(), 5), GridLayoutManager.HORIZONTAL, false));
|
||||
|
||||
starredSongAdapter.setItems(songs);
|
||||
}
|
||||
@@ -428,12 +473,16 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||
bind.starredAlbumsRecyclerView.setAdapter(starredAlbumAdapter);
|
||||
homeViewModel.getStarredAlbums(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), albums -> {
|
||||
if (albums == null) {
|
||||
if (bind != null) bind.starredAlbumsPlaceholder.placeholder.setVisibility(View.VISIBLE);
|
||||
if (bind != null)
|
||||
bind.starredAlbumsPlaceholder.placeholder.setVisibility(View.VISIBLE);
|
||||
if (bind != null) bind.starredAlbumsSector.setVisibility(View.GONE);
|
||||
} else {
|
||||
if (bind != null) bind.starredAlbumsPlaceholder.placeholder.setVisibility(View.GONE);
|
||||
if (bind != null) bind.starredAlbumsSector.setVisibility(!albums.isEmpty() ? View.VISIBLE : View.GONE);
|
||||
if (bind != null) bind.starredAlbumsRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), UIUtil.getSpanCount(albums.size(), 5), GridLayoutManager.HORIZONTAL, false));
|
||||
if (bind != null)
|
||||
bind.starredAlbumsPlaceholder.placeholder.setVisibility(View.GONE);
|
||||
if (bind != null)
|
||||
bind.starredAlbumsSector.setVisibility(!albums.isEmpty() ? View.VISIBLE : View.GONE);
|
||||
if (bind != null)
|
||||
bind.starredAlbumsRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), UIUtil.getSpanCount(albums.size(), 5), GridLayoutManager.HORIZONTAL, false));
|
||||
|
||||
starredAlbumAdapter.setItems(albums);
|
||||
}
|
||||
@@ -459,13 +508,18 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||
bind.starredArtistsRecyclerView.setAdapter(starredArtistAdapter);
|
||||
homeViewModel.getStarredArtists(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), artists -> {
|
||||
if (artists == null) {
|
||||
if (bind != null) bind.starredArtistsPlaceholder.placeholder.setVisibility(View.VISIBLE);
|
||||
if (bind != null)
|
||||
bind.starredArtistsPlaceholder.placeholder.setVisibility(View.VISIBLE);
|
||||
if (bind != null) bind.starredArtistsSector.setVisibility(View.GONE);
|
||||
} else {
|
||||
if (bind != null) bind.starredArtistsPlaceholder.placeholder.setVisibility(View.GONE);
|
||||
if (bind != null) bind.starredArtistsSector.setVisibility(!artists.isEmpty() ? View.VISIBLE : View.GONE);
|
||||
if (bind != null) bind.afterFavoritesDivider.setVisibility(!artists.isEmpty() ? View.VISIBLE : View.GONE);
|
||||
if (bind != null) bind.starredArtistsRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), UIUtil.getSpanCount(artists.size(), 5), GridLayoutManager.HORIZONTAL, false));
|
||||
if (bind != null)
|
||||
bind.starredArtistsPlaceholder.placeholder.setVisibility(View.GONE);
|
||||
if (bind != null)
|
||||
bind.starredArtistsSector.setVisibility(!artists.isEmpty() ? View.VISIBLE : View.GONE);
|
||||
if (bind != null)
|
||||
bind.afterFavoritesDivider.setVisibility(!artists.isEmpty() ? View.VISIBLE : View.GONE);
|
||||
if (bind != null)
|
||||
bind.starredArtistsRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), UIUtil.getSpanCount(artists.size(), 5), GridLayoutManager.HORIZONTAL, false));
|
||||
|
||||
starredArtistAdapter.setItems(artists);
|
||||
}
|
||||
@@ -491,12 +545,16 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||
bind.newReleasesRecyclerView.setAdapter(newReleasesAlbumAdapter);
|
||||
homeViewModel.getRecentlyReleasedAlbums(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), albums -> {
|
||||
if (albums == null) {
|
||||
if (bind != null) bind.homeNewReleasesPlaceholder.placeholder.setVisibility(View.VISIBLE);
|
||||
if (bind != null)
|
||||
bind.homeNewReleasesPlaceholder.placeholder.setVisibility(View.VISIBLE);
|
||||
if (bind != null) bind.homeNewReleasesSector.setVisibility(View.GONE);
|
||||
} else {
|
||||
if (bind != null) bind.homeNewReleasesPlaceholder.placeholder.setVisibility(View.GONE);
|
||||
if (bind != null) bind.homeNewReleasesSector.setVisibility(!albums.isEmpty() ? View.VISIBLE : View.GONE);
|
||||
if (bind != null) bind.newReleasesRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), UIUtil.getSpanCount(albums.size(), 5), GridLayoutManager.HORIZONTAL, false));
|
||||
if (bind != null)
|
||||
bind.homeNewReleasesPlaceholder.placeholder.setVisibility(View.GONE);
|
||||
if (bind != null)
|
||||
bind.homeNewReleasesSector.setVisibility(!albums.isEmpty() ? View.VISIBLE : View.GONE);
|
||||
if (bind != null)
|
||||
bind.newReleasesRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), UIUtil.getSpanCount(albums.size(), 5), GridLayoutManager.HORIZONTAL, false));
|
||||
|
||||
newReleasesAlbumAdapter.setItems(albums);
|
||||
}
|
||||
@@ -523,11 +581,14 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||
bind.yearsRecyclerView.setAdapter(yearAdapter);
|
||||
homeViewModel.getYearList(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), years -> {
|
||||
if (years == null) {
|
||||
if (bind != null) bind.homeFlashbackPlaceholder.placeholder.setVisibility(View.VISIBLE);
|
||||
if (bind != null)
|
||||
bind.homeFlashbackPlaceholder.placeholder.setVisibility(View.VISIBLE);
|
||||
if (bind != null) bind.homeFlashbackSector.setVisibility(View.GONE);
|
||||
} else {
|
||||
if (bind != null) bind.homeFlashbackPlaceholder.placeholder.setVisibility(View.GONE);
|
||||
if (bind != null) bind.homeFlashbackSector.setVisibility(!years.isEmpty() ? View.VISIBLE : View.GONE);
|
||||
if (bind != null)
|
||||
bind.homeFlashbackPlaceholder.placeholder.setVisibility(View.GONE);
|
||||
if (bind != null)
|
||||
bind.homeFlashbackSector.setVisibility(!years.isEmpty() ? View.VISIBLE : View.GONE);
|
||||
|
||||
yearAdapter.setItems(years);
|
||||
}
|
||||
@@ -545,11 +606,14 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||
bind.mostPlayedAlbumsRecyclerView.setAdapter(mostPlayedAlbumAdapter);
|
||||
homeViewModel.getMostPlayedAlbums(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), albums -> {
|
||||
if (albums == null) {
|
||||
if (bind != null) bind.homeMostPlayedAlbumsPlaceholder.placeholder.setVisibility(View.VISIBLE);
|
||||
if (bind != null)
|
||||
bind.homeMostPlayedAlbumsPlaceholder.placeholder.setVisibility(View.VISIBLE);
|
||||
if (bind != null) bind.homeMostPlayedAlbumsSector.setVisibility(View.GONE);
|
||||
} else {
|
||||
if (bind != null) bind.homeMostPlayedAlbumsPlaceholder.placeholder.setVisibility(View.GONE);
|
||||
if (bind != null) bind.homeMostPlayedAlbumsSector.setVisibility(!albums.isEmpty() ? View.VISIBLE : View.GONE);
|
||||
if (bind != null)
|
||||
bind.homeMostPlayedAlbumsPlaceholder.placeholder.setVisibility(View.GONE);
|
||||
if (bind != null)
|
||||
bind.homeMostPlayedAlbumsSector.setVisibility(!albums.isEmpty() ? View.VISIBLE : View.GONE);
|
||||
// if (albums.size() < 5) reorder();
|
||||
|
||||
mostPlayedAlbumAdapter.setItems(albums);
|
||||
@@ -568,11 +632,14 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||
bind.recentlyPlayedAlbumsRecyclerView.setAdapter(recentlyPlayedAlbumAdapter);
|
||||
homeViewModel.getRecentlyPlayedAlbumList(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), albums -> {
|
||||
if (albums == null) {
|
||||
if (bind != null) bind.homeRecentlyPlayedAlbumsPlaceholder.placeholder.setVisibility(View.VISIBLE);
|
||||
if (bind != null)
|
||||
bind.homeRecentlyPlayedAlbumsPlaceholder.placeholder.setVisibility(View.VISIBLE);
|
||||
if (bind != null) bind.homeRecentlyPlayedAlbumsSector.setVisibility(View.GONE);
|
||||
} else {
|
||||
if (bind != null) bind.homeRecentlyPlayedAlbumsPlaceholder.placeholder.setVisibility(View.GONE);
|
||||
if (bind != null) bind.homeRecentlyPlayedAlbumsSector.setVisibility(!albums.isEmpty() ? View.VISIBLE : View.GONE);
|
||||
if (bind != null)
|
||||
bind.homeRecentlyPlayedAlbumsPlaceholder.placeholder.setVisibility(View.GONE);
|
||||
if (bind != null)
|
||||
bind.homeRecentlyPlayedAlbumsSector.setVisibility(!albums.isEmpty() ? View.VISIBLE : View.GONE);
|
||||
|
||||
recentlyPlayedAlbumAdapter.setItems(albums);
|
||||
}
|
||||
@@ -590,11 +657,14 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||
bind.recentlyAddedAlbumsRecyclerView.setAdapter(recentlyAddedAlbumAdapter);
|
||||
homeViewModel.getMostRecentlyAddedAlbums(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), albums -> {
|
||||
if (albums == null) {
|
||||
if (bind != null) bind.homeRecentlyAddedAlbumsPlaceholder.placeholder.setVisibility(View.VISIBLE);
|
||||
if (bind != null)
|
||||
bind.homeRecentlyAddedAlbumsPlaceholder.placeholder.setVisibility(View.VISIBLE);
|
||||
if (bind != null) bind.homeRecentlyAddedAlbumsSector.setVisibility(View.GONE);
|
||||
} else {
|
||||
if (bind != null) bind.homeRecentlyAddedAlbumsPlaceholder.placeholder.setVisibility(View.GONE);
|
||||
if (bind != null) bind.homeRecentlyAddedAlbumsSector.setVisibility(!albums.isEmpty() ? View.VISIBLE : View.GONE);
|
||||
if (bind != null)
|
||||
bind.homeRecentlyAddedAlbumsPlaceholder.placeholder.setVisibility(View.GONE);
|
||||
if (bind != null)
|
||||
bind.homeRecentlyAddedAlbumsSector.setVisibility(!albums.isEmpty() ? View.VISIBLE : View.GONE);
|
||||
|
||||
recentlyAddedAlbumAdapter.setItems(albums);
|
||||
}
|
||||
@@ -604,6 +674,52 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||
recentAddedAlbumSnapHelper.attachToRecyclerView(bind.recentlyAddedAlbumsRecyclerView);
|
||||
}
|
||||
|
||||
private void initSharesView() {
|
||||
bind.sharesRecyclerView.setHasFixedSize(true);
|
||||
|
||||
shareHorizontalAdapter = new ShareHorizontalAdapter(this);
|
||||
bind.sharesRecyclerView.setAdapter(shareHorizontalAdapter);
|
||||
if (Preferences.isSharingEnabled()) {
|
||||
homeViewModel.getShares(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), shares -> {
|
||||
if (shares == null) {
|
||||
if (bind != null)
|
||||
bind.sharesPlaceholder.placeholder.setVisibility(View.VISIBLE);
|
||||
if (bind != null) bind.sharesSector.setVisibility(View.GONE);
|
||||
} else {
|
||||
if (bind != null) bind.sharesPlaceholder.placeholder.setVisibility(View.GONE);
|
||||
if (bind != null)
|
||||
bind.sharesSector.setVisibility(!shares.isEmpty() ? View.VISIBLE : View.GONE);
|
||||
if (bind != null)
|
||||
bind.sharesRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), UIUtil.getSpanCount(shares.size(), 10), GridLayoutManager.HORIZONTAL, false));
|
||||
|
||||
shareHorizontalAdapter.setItems(shares);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
SnapHelper starredTrackSnapHelper = new PagerSnapHelper();
|
||||
starredTrackSnapHelper.attachToRecyclerView(bind.sharesRecyclerView);
|
||||
|
||||
bind.sharesRecyclerView.addItemDecoration(
|
||||
new DotsIndicatorDecoration(
|
||||
getResources().getDimensionPixelSize(R.dimen.radius),
|
||||
getResources().getDimensionPixelSize(R.dimen.radius) * 4,
|
||||
getResources().getDimensionPixelSize(R.dimen.dots_height),
|
||||
requireContext().getResources().getColor(R.color.titleTextColor, null),
|
||||
requireContext().getResources().getColor(R.color.titleTextColor, null))
|
||||
);
|
||||
}
|
||||
|
||||
private void refreshSharesView() {
|
||||
final Handler handler = new Handler();
|
||||
final Runnable runnable = () -> {
|
||||
if (getView() != null && bind != null && Preferences.isSharingEnabled()) {
|
||||
homeViewModel.refreshShares(getViewLifecycleOwner());
|
||||
}
|
||||
};
|
||||
handler.postDelayed(runnable, 100);
|
||||
}
|
||||
|
||||
private void setSlideViewOffset(ViewPager2 viewPager, float pageOffset, float pageMargin) {
|
||||
viewPager.setPageTransformer((page, position) -> {
|
||||
float myOffset = position * -(2 * pageOffset + pageMargin);
|
||||
@@ -619,25 +735,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() {
|
||||
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
|
||||
}
|
||||
@@ -654,6 +751,8 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||
|
||||
if (mediaBrowserListenableFuture != null) {
|
||||
homeViewModel.getMediaInstantMix(getViewLifecycleOwner(), bundle.getParcelable(Constants.TRACK_OBJECT)).observe(getViewLifecycleOwner(), songs -> {
|
||||
MusicUtil.ratingFilter(songs);
|
||||
|
||||
if (songs != null && songs.size() > 0) {
|
||||
MediaManager.enqueue(mediaBrowserListenableFuture, songs, true);
|
||||
}
|
||||
@@ -693,6 +792,8 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||
|
||||
if (mediaBrowserListenableFuture != null) {
|
||||
homeViewModel.getArtistInstantMix(getViewLifecycleOwner(), bundle.getParcelable(Constants.ARTIST_OBJECT)).observe(getViewLifecycleOwner(), songs -> {
|
||||
MusicUtil.ratingFilter(songs);
|
||||
|
||||
if (songs.size() > 0) {
|
||||
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
|
||||
activity.setBottomSheetInPeek(true);
|
||||
@@ -702,6 +803,8 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||
} else if (bundle.containsKey(Constants.MEDIA_BEST_OF) && bundle.getBoolean(Constants.MEDIA_BEST_OF)) {
|
||||
if (mediaBrowserListenableFuture != null) {
|
||||
homeViewModel.getArtistBestOf(getViewLifecycleOwner(), bundle.getParcelable(Constants.ARTIST_OBJECT)).observe(getViewLifecycleOwner(), songs -> {
|
||||
MusicUtil.ratingFilter(songs);
|
||||
|
||||
if (songs.size() > 0) {
|
||||
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
|
||||
activity.setBottomSheetInPeek(true);
|
||||
@@ -722,4 +825,16 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||
public void onYearClick(Bundle bundle) {
|
||||
Navigation.findNavController(requireView()).navigate(R.id.songListPageFragment, bundle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onShareClick(Bundle bundle) {
|
||||
Share share = bundle.getParcelable(Constants.SHARE_OBJECT);
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(share.getUrl())).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onShareLongClick(Bundle bundle) {
|
||||
Navigation.findNavController(requireView()).navigate(R.id.shareBottomSheetDialog, bundle);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,13 +85,15 @@ public class IndexFragment extends Fragment implements ClickCallback {
|
||||
}
|
||||
|
||||
private void initDirectoryListView() {
|
||||
MusicFolder musicFolder = getArguments().getParcelable(Constants.MUSIC_FOLDER_OBJECT);
|
||||
|
||||
bind.indexRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
|
||||
bind.indexRecyclerView.setHasFixedSize(true);
|
||||
|
||||
musicIndexAdapter = new MusicIndexAdapter(this);
|
||||
bind.indexRecyclerView.setAdapter(musicIndexAdapter);
|
||||
|
||||
indexViewModel.getIndexes().observe(getViewLifecycleOwner(), indexes -> {
|
||||
indexViewModel.getIndexes(musicFolder != null ? musicFolder.getId() : null).observe(getViewLifecycleOwner(), indexes -> {
|
||||
if (indexes != null) {
|
||||
musicIndexAdapter.setItems(IndexUtil.getArtist(indexes));
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.cappielloantonio.tempo.ui.fragment;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -18,6 +19,7 @@ import com.cappielloantonio.tempo.R;
|
||||
import com.cappielloantonio.tempo.databinding.FragmentLibraryBinding;
|
||||
import com.cappielloantonio.tempo.helper.recyclerview.CustomLinearSnapHelper;
|
||||
import com.cappielloantonio.tempo.interfaces.ClickCallback;
|
||||
import com.cappielloantonio.tempo.interfaces.PlaylistCallback;
|
||||
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
||||
import com.cappielloantonio.tempo.ui.adapter.AlbumAdapter;
|
||||
import com.cappielloantonio.tempo.ui.adapter.ArtistAdapter;
|
||||
@@ -71,7 +73,7 @@ public class LibraryFragment extends Fragment implements ClickCallback {
|
||||
initAlbumView();
|
||||
initArtistView();
|
||||
initGenreView();
|
||||
initPlaylistSlideView();
|
||||
initPlaylistView();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -80,6 +82,12 @@ public class LibraryFragment extends Fragment implements ClickCallback {
|
||||
activity.setBottomNavigationBarVisibility(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
refreshPlaylistView();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
@@ -222,7 +230,7 @@ public class LibraryFragment extends Fragment implements ClickCallback {
|
||||
genreSnapHelper.attachToRecyclerView(bind.genreRecyclerView);
|
||||
}
|
||||
|
||||
private void initPlaylistSlideView() {
|
||||
private void initPlaylistView() {
|
||||
bind.playlistRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
|
||||
bind.playlistRecyclerView.setHasFixedSize(true);
|
||||
|
||||
@@ -244,6 +252,17 @@ public class LibraryFragment extends Fragment implements ClickCallback {
|
||||
});
|
||||
}
|
||||
|
||||
private void refreshPlaylistView() {
|
||||
final Handler handler = new Handler();
|
||||
|
||||
final Runnable runnable = () -> {
|
||||
if (getView() != null && bind != null && libraryViewModel != null)
|
||||
libraryViewModel.refreshPlaylistSample(getViewLifecycleOwner());
|
||||
};
|
||||
|
||||
handler.postDelayed(runnable, 100);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAlbumClick(Bundle bundle) {
|
||||
Navigation.findNavController(requireView()).navigate(R.id.albumPageFragment, bundle);
|
||||
@@ -276,7 +295,13 @@ public class LibraryFragment extends Fragment implements ClickCallback {
|
||||
|
||||
@Override
|
||||
public void onPlaylistLongClick(Bundle bundle) {
|
||||
PlaylistEditorDialog dialog = new PlaylistEditorDialog();
|
||||
PlaylistEditorDialog dialog = new PlaylistEditorDialog(new PlaylistCallback() {
|
||||
@Override
|
||||
public void onDismiss() {
|
||||
refreshPlaylistView();
|
||||
}
|
||||
});
|
||||
|
||||
dialog.setArguments(bundle);
|
||||
dialog.show(activity.getSupportFragmentManager(), null);
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ import com.google.android.material.elevation.SurfaceColors;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.common.util.concurrent.MoreExecutors;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
@OptIn(markerClass = UnstableApi.class)
|
||||
@@ -156,14 +157,18 @@ public class PlayerBottomSheetFragment extends Fragment {
|
||||
if (mediaMetadata.extras != null) {
|
||||
playerBottomSheetViewModel.setLiveMedia(getViewLifecycleOwner(), mediaMetadata.extras.getString("type"), mediaMetadata.extras.getString("id"));
|
||||
playerBottomSheetViewModel.setLiveArtist(getViewLifecycleOwner(), mediaMetadata.extras.getString("type"), mediaMetadata.extras.getString("artistId"));
|
||||
playerBottomSheetViewModel.setLiveDescription(mediaMetadata.extras.getString("description", null));
|
||||
|
||||
bind.playerHeaderLayout.playerHeaderMediaTitleLabel.setText(MusicUtil.getReadableString(mediaMetadata.extras.getString("title")));
|
||||
bind.playerHeaderLayout.playerHeaderMediaArtistLabel.setText(MusicUtil.getReadableString(mediaMetadata.extras.getString("artist")));
|
||||
|
||||
CustomGlideRequest.Builder
|
||||
.from(requireContext(), mediaMetadata.extras.getString("coverArtId"))
|
||||
.from(requireContext(), mediaMetadata.extras.getString("coverArtId"), CustomGlideRequest.ResourceType.Song)
|
||||
.build()
|
||||
.into(bind.playerHeaderLayout.playerHeaderMediaCoverImage);
|
||||
|
||||
bind.playerHeaderLayout.playerHeaderMediaTitleLabel.setVisibility(mediaMetadata.extras.getString("title") != null && !Objects.equals(mediaMetadata.extras.getString("title"), "") ? View.VISIBLE : View.GONE);
|
||||
bind.playerHeaderLayout.playerHeaderMediaArtistLabel.setVisibility(mediaMetadata.extras.getString("artist") != null && !Objects.equals(mediaMetadata.extras.getString("artist"), "") ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,6 +245,10 @@ public class PlayerBottomSheetFragment extends Fragment {
|
||||
}
|
||||
}
|
||||
|
||||
public void goToQueuePage() {
|
||||
bind.playerBodyLayout.playerBodyBottomSheetViewPager.setCurrentItem(1, true);
|
||||
}
|
||||
|
||||
private void defineProgressBarHandler(MediaBrowser mediaBrowser) {
|
||||
progressBarHandler = new Handler();
|
||||
progressBarRunnable = () -> {
|
||||
|
||||
@@ -6,12 +6,12 @@ import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import android.widget.ToggleButton;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.media3.common.MediaMetadata;
|
||||
@@ -29,15 +29,19 @@ import com.cappielloantonio.tempo.databinding.InnerFragmentPlayerControllerBindi
|
||||
import com.cappielloantonio.tempo.service.MediaService;
|
||||
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
||||
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.util.Constants;
|
||||
import com.cappielloantonio.tempo.util.MusicUtil;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
import com.cappielloantonio.tempo.viewmodel.PlayerBottomSheetViewModel;
|
||||
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.MoreExecutors;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@UnstableApi
|
||||
public class PlayerControllerFragment extends Fragment {
|
||||
private static final String TAG = "PlayerCoverFragment";
|
||||
@@ -51,10 +55,9 @@ public class PlayerControllerFragment extends Fragment {
|
||||
private ToggleButton skipSilenceToggleButton;
|
||||
private Chip playerMediaExtension;
|
||||
private TextView playerMediaBitrate;
|
||||
private ImageView playerMediaTranscodingIcon;
|
||||
private ImageView playerMediaTranscodingPriorityIcon;
|
||||
private Chip playerMediaTranscodedExtension;
|
||||
private TextView playerMediaTranscodedBitrate;
|
||||
private ConstraintLayout playerQuickActionView;
|
||||
private ImageButton playerOpenQueueButton;
|
||||
private ImageButton playerTrackInfo;
|
||||
|
||||
private MainActivity activity;
|
||||
private PlayerBottomSheetViewModel playerBottomSheetViewModel;
|
||||
@@ -70,10 +73,10 @@ public class PlayerControllerFragment extends Fragment {
|
||||
playerBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(PlayerBottomSheetViewModel.class);
|
||||
|
||||
init();
|
||||
initQuickActionView();
|
||||
initCoverLyricsSlideView();
|
||||
initMediaListenable();
|
||||
initArtistLabelButton();
|
||||
initTranscodingInfo();
|
||||
|
||||
return view;
|
||||
}
|
||||
@@ -106,10 +109,20 @@ public class PlayerControllerFragment extends Fragment {
|
||||
skipSilenceToggleButton = bind.getRoot().findViewById(R.id.player_skip_silence_toggle_button);
|
||||
playerMediaExtension = bind.getRoot().findViewById(R.id.player_media_extension);
|
||||
playerMediaBitrate = bind.getRoot().findViewById(R.id.player_media_bitrate);
|
||||
playerMediaTranscodingIcon = bind.getRoot().findViewById(R.id.player_media_transcoding_audio);
|
||||
playerMediaTranscodingPriorityIcon = bind.getRoot().findViewById(R.id.player_media_server_transcode_priority);
|
||||
playerMediaTranscodedExtension = bind.getRoot().findViewById(R.id.player_media_transcoded_extension);
|
||||
playerMediaTranscodedBitrate = bind.getRoot().findViewById(R.id.player_media_transcoded_bitrate);
|
||||
playerQuickActionView = bind.getRoot().findViewById(R.id.player_quick_action_view);
|
||||
playerOpenQueueButton = bind.getRoot().findViewById(R.id.player_open_queue_button);
|
||||
playerTrackInfo = bind.getRoot().findViewById(R.id.player_info_track);
|
||||
}
|
||||
|
||||
private void initQuickActionView() {
|
||||
playerQuickActionView.setBackgroundColor(SurfaceColors.getColorForElevation(requireContext(), 8));
|
||||
|
||||
playerOpenQueueButton.setOnClickListener(view -> {
|
||||
PlayerBottomSheetFragment playerBottomSheetFragment = (PlayerBottomSheetFragment) requireActivity().getSupportFragmentManager().findFragmentByTag("PlayerBottomSheet");
|
||||
if (playerBottomSheetFragment != null) {
|
||||
playerBottomSheetFragment.goToQueuePage();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void initializeBrowser() {
|
||||
@@ -155,14 +168,15 @@ public class PlayerControllerFragment extends Fragment {
|
||||
|
||||
playerMediaTitleLabel.setSelected(true);
|
||||
playerArtistNameLabel.setSelected(true);
|
||||
|
||||
playerMediaTitleLabel.setVisibility(mediaMetadata.title != null && !Objects.equals(mediaMetadata.title, "") ? View.VISIBLE : View.GONE);
|
||||
playerArtistNameLabel.setVisibility(mediaMetadata.artist != null && !Objects.equals(mediaMetadata.artist, "") ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
private void setMediaInfo(MediaMetadata mediaMetadata) {
|
||||
if (mediaMetadata.extras != null) {
|
||||
String extension = mediaMetadata.extras.getString("suffix", "Unknown format");
|
||||
String bitrate = mediaMetadata.extras.getInt("bitrate", 0) != 0
|
||||
? mediaMetadata.extras.getInt("bitrate", 0) + "kbps"
|
||||
: "Original";
|
||||
String bitrate = mediaMetadata.extras.getInt("bitrate", 0) != 0 ? mediaMetadata.extras.getInt("bitrate", 0) + "kbps" : "Original";
|
||||
|
||||
playerMediaExtension.setText(extension);
|
||||
|
||||
@@ -174,38 +188,18 @@ public class PlayerControllerFragment extends Fragment {
|
||||
}
|
||||
}
|
||||
|
||||
String transcodingExtension = MusicUtil.getTranscodingFormatPreference();
|
||||
String transcodingBitrate = Integer.parseInt(MusicUtil.getBitratePreference()) != 0
|
||||
? Integer.parseInt(MusicUtil.getBitratePreference()) + "kbps"
|
||||
: "Original";
|
||||
boolean isTranscodingExtension = !MusicUtil.getTranscodingFormatPreference().equals("raw");
|
||||
boolean isTranscodingBitrate = !MusicUtil.getBitratePreference().equals("0");
|
||||
|
||||
if (transcodingExtension.equals("raw") && transcodingBitrate.equals("Original")) {
|
||||
playerMediaTranscodingPriorityIcon.setVisibility(View.GONE);
|
||||
playerMediaTranscodingIcon.setVisibility(View.GONE);
|
||||
playerMediaTranscodedBitrate.setVisibility(View.GONE);
|
||||
playerMediaTranscodedExtension.setVisibility(View.GONE);
|
||||
} else {
|
||||
playerMediaTranscodingPriorityIcon.setVisibility(View.GONE);
|
||||
playerMediaTranscodingIcon.setVisibility(View.VISIBLE);
|
||||
playerMediaTranscodedBitrate.setVisibility(View.VISIBLE);
|
||||
playerMediaTranscodedExtension.setVisibility(View.VISIBLE);
|
||||
playerMediaTranscodedExtension.setText(transcodingExtension);
|
||||
playerMediaTranscodedBitrate.setText(transcodingBitrate);
|
||||
if (isTranscodingExtension || isTranscodingBitrate) {
|
||||
playerMediaExtension.setText("Transcoding");
|
||||
playerMediaBitrate.setText("requested");
|
||||
}
|
||||
|
||||
if (mediaMetadata.extras != null && mediaMetadata.extras.getString("uri", "").contains(Constants.DOWNLOAD_URI)) {
|
||||
playerMediaTranscodingPriorityIcon.setVisibility(View.GONE);
|
||||
playerMediaTranscodingIcon.setVisibility(View.GONE);
|
||||
playerMediaTranscodedBitrate.setVisibility(View.GONE);
|
||||
playerMediaTranscodedExtension.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (Preferences.isServerPrioritized() && mediaMetadata.extras != null && !mediaMetadata.extras.getString("uri", "").contains(Constants.DOWNLOAD_URI)) {
|
||||
playerMediaTranscodingPriorityIcon.setVisibility(View.VISIBLE);
|
||||
playerMediaTranscodingIcon.setVisibility(View.GONE);
|
||||
playerMediaTranscodedBitrate.setVisibility(View.GONE);
|
||||
playerMediaTranscodedExtension.setVisibility(View.GONE);
|
||||
}
|
||||
playerTrackInfo.setOnClickListener(view -> {
|
||||
TrackInfoDialog dialog = new TrackInfoDialog(mediaMetadata);
|
||||
dialog.show(activity.getSupportFragmentManager(), null);
|
||||
});
|
||||
}
|
||||
|
||||
private void setMediaControllerUI(MediaBrowser mediaBrowser) {
|
||||
@@ -222,6 +216,7 @@ public class PlayerControllerFragment extends Fragment {
|
||||
bind.getRoot().setRepeatToggleModes(RepeatModeUtil.REPEAT_TOGGLE_MODE_NONE);
|
||||
bind.getRoot().findViewById(R.id.player_playback_speed_button).setVisibility(View.VISIBLE);
|
||||
bind.getRoot().findViewById(R.id.player_skip_silence_toggle_button).setVisibility(View.VISIBLE);
|
||||
bind.getRoot().findViewById(R.id.button_favorite).setVisibility(View.GONE);
|
||||
setPlaybackParameters(mediaBrowser);
|
||||
break;
|
||||
case Constants.MEDIA_TYPE_RADIO:
|
||||
@@ -233,6 +228,7 @@ public class PlayerControllerFragment extends Fragment {
|
||||
bind.getRoot().setRepeatToggleModes(RepeatModeUtil.REPEAT_TOGGLE_MODE_NONE);
|
||||
bind.getRoot().findViewById(R.id.player_playback_speed_button).setVisibility(View.GONE);
|
||||
bind.getRoot().findViewById(R.id.player_skip_silence_toggle_button).setVisibility(View.GONE);
|
||||
bind.getRoot().findViewById(R.id.button_favorite).setVisibility(View.GONE);
|
||||
setPlaybackParameters(mediaBrowser);
|
||||
break;
|
||||
case Constants.MEDIA_TYPE_MUSIC:
|
||||
@@ -245,6 +241,7 @@ public class PlayerControllerFragment extends Fragment {
|
||||
bind.getRoot().setRepeatToggleModes(RepeatModeUtil.REPEAT_TOGGLE_MODE_ALL | RepeatModeUtil.REPEAT_TOGGLE_MODE_ONE);
|
||||
bind.getRoot().findViewById(R.id.player_playback_speed_button).setVisibility(View.GONE);
|
||||
bind.getRoot().findViewById(R.id.player_skip_silence_toggle_button).setVisibility(View.GONE);
|
||||
bind.getRoot().findViewById(R.id.button_favorite).setVisibility(View.VISIBLE);
|
||||
resetPlaybackParameters(mediaBrowser);
|
||||
break;
|
||||
}
|
||||
@@ -299,19 +296,12 @@ public class PlayerControllerFragment extends Fragment {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelable(Constants.ARTIST_OBJECT, artist);
|
||||
NavHostFragment.findNavController(this).navigate(R.id.artistPageFragment, bundle);
|
||||
activity.collapseBottomSheet();
|
||||
activity.collapseBottomSheetDelayed();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void initTranscodingInfo() {
|
||||
playerMediaTranscodingPriorityIcon.setOnLongClickListener(view -> {
|
||||
Toast.makeText(requireActivity(), R.string.settings_audio_transcode_priority_toast, Toast.LENGTH_SHORT).show();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void initPlaybackSpeedButton(MediaBrowser mediaBrowser) {
|
||||
playbackSpeedButton.setOnClickListener(view -> {
|
||||
float currentSpeed = Preferences.getPlaybackSpeed();
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.cappielloantonio.tempo.ui.fragment;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.transition.Fade;
|
||||
import android.transition.Transition;
|
||||
import android.transition.TransitionManager;
|
||||
@@ -39,6 +40,8 @@ public class PlayerCoverFragment extends Fragment {
|
||||
private InnerFragmentPlayerCoverBinding bind;
|
||||
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
||||
|
||||
private final Handler handler = new Handler();
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
bind = InnerFragmentPlayerCoverBinding.inflate(inflater, container, false);
|
||||
@@ -72,9 +75,22 @@ public class PlayerCoverFragment extends Fragment {
|
||||
bind = null;
|
||||
}
|
||||
|
||||
private void initTapButtonHideTransition() {
|
||||
bind.nowPlayingTapButton.setVisibility(View.VISIBLE);
|
||||
|
||||
handler.removeCallbacksAndMessages(null);
|
||||
|
||||
final Runnable runnable = () -> {
|
||||
if (bind != null) bind.nowPlayingTapButton.setVisibility(View.GONE);
|
||||
};
|
||||
|
||||
handler.postDelayed(runnable, 10000);
|
||||
}
|
||||
|
||||
private void initOverlay() {
|
||||
bind.nowPlayingSongCoverImageView.setOnClickListener(view -> toggleOverlayVisibility(true));
|
||||
bind.nowPlayingSongCoverButtonGroup.setOnClickListener(view -> toggleOverlayVisibility(false));
|
||||
bind.nowPlayingTapButton.setOnClickListener(view -> toggleOverlayVisibility(true));
|
||||
}
|
||||
|
||||
private void toggleOverlayVisibility(boolean isVisible) {
|
||||
@@ -84,9 +100,12 @@ public class PlayerCoverFragment extends Fragment {
|
||||
|
||||
TransitionManager.beginDelayedTransition(bind.getRoot(), transition);
|
||||
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.innerButtonBottomRightAlternative.setVisibility(Preferences.isSyncronizationEnabled() ? View.GONE : View.VISIBLE);
|
||||
|
||||
if (!isVisible) initTapButtonHideTransition();
|
||||
}
|
||||
|
||||
private void initInnerButton() {
|
||||
@@ -166,7 +185,7 @@ public class PlayerCoverFragment extends Fragment {
|
||||
|
||||
private void setCover(MediaMetadata mediaMetadata) {
|
||||
CustomGlideRequest.Builder
|
||||
.from(requireContext(), mediaMetadata.extras != null ? mediaMetadata.extras.getString("coverArtId") : null)
|
||||
.from(requireContext(), mediaMetadata.extras != null ? mediaMetadata.extras.getString("coverArtId") : null, CustomGlideRequest.ResourceType.Song)
|
||||
.build()
|
||||
.into(bind.nowPlayingSongCoverImageView);
|
||||
}
|
||||
|
||||
@@ -44,18 +44,25 @@ public class PlayerLyricsFragment extends Fragment {
|
||||
|
||||
private void initLyrics() {
|
||||
playerBottomSheetViewModel.getLiveLyrics().observe(getViewLifecycleOwner(), lyrics -> {
|
||||
if (bind != null) {
|
||||
if (lyrics == null || lyrics.trim().equals("")) {
|
||||
bind.nowPlayingSongLyricsTextView.setVisibility(View.GONE);
|
||||
bind.emptyDescriptionImageView.setVisibility(View.VISIBLE);
|
||||
bind.titleEmptyDescriptionLabel.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
bind.nowPlayingSongLyricsTextView.setText(MusicUtil.getReadableLyrics(lyrics));
|
||||
bind.nowPlayingSongLyricsTextView.setVisibility(View.VISIBLE);
|
||||
bind.emptyDescriptionImageView.setVisibility(View.GONE);
|
||||
bind.titleEmptyDescriptionLabel.setVisibility(View.GONE);
|
||||
playerBottomSheetViewModel.getLiveDescription().observe(getViewLifecycleOwner(), description -> {
|
||||
if (bind != null) {
|
||||
if (lyrics != null && !lyrics.trim().equals("")) {
|
||||
bind.nowPlayingSongLyricsTextView.setText(MusicUtil.getReadableLyrics(lyrics));
|
||||
bind.nowPlayingSongLyricsTextView.setVisibility(View.VISIBLE);
|
||||
bind.emptyDescriptionImageView.setVisibility(View.GONE);
|
||||
bind.titleEmptyDescriptionLabel.setVisibility(View.GONE);
|
||||
} else if (description != null && !description.trim().equals("")) {
|
||||
bind.nowPlayingSongLyricsTextView.setText(MusicUtil.getReadableLyrics(description));
|
||||
bind.nowPlayingSongLyricsTextView.setVisibility(View.VISIBLE);
|
||||
bind.emptyDescriptionImageView.setVisibility(View.GONE);
|
||||
bind.titleEmptyDescriptionLabel.setVisibility(View.GONE);
|
||||
} else {
|
||||
bind.nowPlayingSongLyricsTextView.setVisibility(View.GONE);
|
||||
bind.emptyDescriptionImageView.setVisibility(View.VISIBLE);
|
||||
bind.titleEmptyDescriptionLabel.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -25,12 +25,16 @@ import com.cappielloantonio.tempo.ui.adapter.PlayerSongQueueAdapter;
|
||||
import com.cappielloantonio.tempo.util.Constants;
|
||||
import com.cappielloantonio.tempo.viewmodel.PlayerBottomSheetViewModel;
|
||||
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.stream.Collectors;
|
||||
|
||||
@UnstableApi
|
||||
public class PlayerQueueFragment extends Fragment implements ClickCallback {
|
||||
private static final String TAG = "PlayerQueueFragment";
|
||||
|
||||
private InnerFragmentPlayerQueueBinding bind;
|
||||
|
||||
private PlayerBottomSheetViewModel playerBottomSheetViewModel;
|
||||
@@ -54,6 +58,7 @@ public class PlayerQueueFragment extends Fragment implements ClickCallback {
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
initializeBrowser();
|
||||
bindMediaController();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -83,6 +88,18 @@ public class PlayerQueueFragment extends Fragment implements ClickCallback {
|
||||
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
|
||||
}
|
||||
|
||||
private void bindMediaController() {
|
||||
mediaBrowserListenableFuture.addListener(() -> {
|
||||
try {
|
||||
MediaBrowser mediaBrowser = mediaBrowserListenableFuture.get();
|
||||
initShuffleButton(mediaBrowser);
|
||||
initCleanButton(mediaBrowser);
|
||||
} catch (Exception exception) {
|
||||
exception.printStackTrace();
|
||||
}
|
||||
}, MoreExecutors.directExecutor());
|
||||
}
|
||||
|
||||
private void setMediaBrowserListenableFuture() {
|
||||
playerSongQueueAdapter.setMediaBrowserListenableFuture(mediaBrowserListenableFuture);
|
||||
}
|
||||
@@ -151,6 +168,46 @@ public class PlayerQueueFragment extends Fragment implements ClickCallback {
|
||||
}).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 initCleanButton(MediaBrowser mediaBrowser) {
|
||||
bind.playerCleanQueueButton.setOnClickListener(view -> {
|
||||
int startPosition = mediaBrowser.getCurrentMediaItemIndex() + 1;
|
||||
int endPosition = playerSongQueueAdapter.getItems().size();
|
||||
|
||||
MediaManager.removeRange(mediaBrowserListenableFuture, playerSongQueueAdapter.getItems(), startPosition, endPosition);
|
||||
bind.playerQueueRecyclerView.getAdapter().notifyItemRangeRemoved(startPosition, endPosition);
|
||||
});
|
||||
}
|
||||
|
||||
private void updateNowPlayingItem() {
|
||||
playerSongQueueAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@@ -178,7 +178,7 @@ public class PlaylistCatalogueFragment extends Fragment implements ClickCallback
|
||||
|
||||
@Override
|
||||
public void onPlaylistLongClick(Bundle bundle) {
|
||||
PlaylistEditorDialog dialog = new PlaylistEditorDialog();
|
||||
PlaylistEditorDialog dialog = new PlaylistEditorDialog(null);
|
||||
dialog.setArguments(bundle);
|
||||
dialog.show(activity.getSupportFragmentManager(), null);
|
||||
hideKeyboard(requireView());
|
||||
|
||||
@@ -167,28 +167,28 @@ public class PlaylistPageFragment extends Fragment implements ClickCallback {
|
||||
|
||||
// Pic top-left
|
||||
CustomGlideRequest.Builder
|
||||
.from(requireContext(), songs.size() > 0 ? songs.get(0).getCoverArtId() : playlistPageViewModel.getPlaylist().getCoverArtId())
|
||||
.from(requireContext(), songs.size() > 0 ? songs.get(0).getCoverArtId() : playlistPageViewModel.getPlaylist().getCoverArtId(), CustomGlideRequest.ResourceType.Song)
|
||||
.build()
|
||||
.transform(new GranularRoundedCorners(CustomGlideRequest.CORNER_RADIUS, 0, 0, 0))
|
||||
.into(bind.playlistCoverImageViewTopLeft);
|
||||
|
||||
// Pic top-right
|
||||
CustomGlideRequest.Builder
|
||||
.from(requireContext(), songs.size() > 1 ? songs.get(1).getCoverArtId() : playlistPageViewModel.getPlaylist().getCoverArtId())
|
||||
.from(requireContext(), songs.size() > 1 ? songs.get(1).getCoverArtId() : playlistPageViewModel.getPlaylist().getCoverArtId(), CustomGlideRequest.ResourceType.Song)
|
||||
.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())
|
||||
.from(requireContext(), songs.size() > 2 ? songs.get(2).getCoverArtId() : playlistPageViewModel.getPlaylist().getCoverArtId(), CustomGlideRequest.ResourceType.Song)
|
||||
.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())
|
||||
.from(requireContext(), songs.size() > 3 ? songs.get(3).getCoverArtId() : playlistPageViewModel.getPlaylist().getCoverArtId(), CustomGlideRequest.ResourceType.Song)
|
||||
.build()
|
||||
.transform(new GranularRoundedCorners(0, 0, CustomGlideRequest.CORNER_RADIUS, 0))
|
||||
.into(bind.playlistCoverImageViewBottomRight);
|
||||
@@ -200,7 +200,7 @@ public class PlaylistPageFragment extends Fragment implements ClickCallback {
|
||||
bind.songRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
|
||||
bind.songRecyclerView.setHasFixedSize(true);
|
||||
|
||||
songHorizontalAdapter = new SongHorizontalAdapter(this, true);
|
||||
songHorizontalAdapter = new SongHorizontalAdapter(this, true, false);
|
||||
bind.songRecyclerView.setAdapter(songHorizontalAdapter);
|
||||
|
||||
playlistPageViewModel.getPlaylistSongLiveList().observe(getViewLifecycleOwner(), songs -> songHorizontalAdapter.setItems(songs));
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user