mirror of
https://github.com/CappielloAntonio/tempo.git
synced 2026-01-30 22:32:07 +00:00
Compare commits
134 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
efb6e72636 | ||
|
|
af83ffd608 | ||
|
|
0201077bc4 | ||
|
|
91d91d3024 | ||
|
|
9784a2b6c5 | ||
|
|
87a3301912 | ||
|
|
295795edc9 | ||
|
|
6120ab66ba | ||
|
|
7bea180c58 | ||
|
|
a29cee488e | ||
|
|
74b4b04693 | ||
|
|
b8b9c80bdc | ||
|
|
a50fc74117 | ||
|
|
c1af438a3a | ||
|
|
80b251cddc | ||
|
|
7d9a48818e | ||
|
|
ca3da0839b | ||
|
|
cf463d8fa1 | ||
|
|
635fdc4c5c | ||
|
|
3e913931c4 | ||
|
|
46420da038 | ||
|
|
d5b7619dd1 | ||
|
|
89b39123da | ||
|
|
5a43137984 | ||
|
|
761b07450f | ||
|
|
a0b67a06f4 | ||
|
|
59d7adb66d | ||
|
|
0c2f0d23cd | ||
|
|
dc201e6c8f | ||
|
|
9bf3399371 | ||
|
|
28565b691a | ||
|
|
7c6faf66c1 | ||
|
|
b160274859 | ||
|
|
3a1ced65d5 | ||
|
|
afeb72803d | ||
|
|
600c28c2a3 | ||
|
|
ec799fff96 | ||
|
|
60da08cdbc | ||
|
|
bdda3743db | ||
|
|
59fcf11ae3 | ||
|
|
77c80b7695 | ||
|
|
13f168f78c | ||
|
|
324eed7e02 | ||
|
|
4a99c7e9b1 | ||
|
|
01e5917642 | ||
|
|
ae00f4279e | ||
|
|
642c69eb96 | ||
|
|
ac69361735 | ||
|
|
e24efc4948 | ||
|
|
f8ad18ed5a | ||
|
|
17345372a2 | ||
|
|
db76494525 | ||
|
|
e87eda2757 | ||
|
|
06e2729aca | ||
|
|
37ffb88d67 | ||
|
|
3d4437151a | ||
|
|
af1961b185 | ||
|
|
c983e33522 | ||
|
|
b18daec708 | ||
|
|
6d20995e70 | ||
|
|
14cacd1bbc | ||
|
|
9d7acdb892 | ||
|
|
05325913f7 | ||
|
|
5733dca68a | ||
|
|
838d4496e5 | ||
|
|
4967363116 | ||
|
|
fc61308be5 | ||
|
|
5c4a292542 |
33
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
33
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
---
|
||||
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.
|
||||
39
.github/ISSUE_TEMPLATE/crash-report.md
vendored
Normal file
39
.github/ISSUE_TEMPLATE/crash-report.md
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
---
|
||||
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 ```).
|
||||
|
||||
**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.
|
||||
16
.github/workflows/github_release.yml
vendored
16
.github/workflows/github_release.yml
vendored
@@ -6,19 +6,15 @@ on:
|
||||
- '[0-9]+.[0-9]+.[0-9]+'
|
||||
|
||||
jobs:
|
||||
setup:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Setup JDK 8
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: 8
|
||||
|
||||
build:
|
||||
needs: setup
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Setup JDK 17
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: '17'
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Cache Gradle and wrapper
|
||||
|
||||
2
.idea/gradle.xml
generated
2
.idea/gradle.xml
generated
@@ -7,7 +7,7 @@
|
||||
<option name="testRunner" value="GRADLE" />
|
||||
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="gradleJvm" value="11" />
|
||||
<option name="gradleJvm" value="17" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
|
||||
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@@ -191,7 +191,7 @@
|
||||
</map>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="17" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
|
||||
@@ -33,12 +33,8 @@ Tempo does not rely on magic algorithms to decide what you should listen to. Ins
|
||||
<img src="mockup/feat/6_screenshot.png" width=200>
|
||||
<img src="mockup/feat/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,8 +3,8 @@ apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-parcelize'
|
||||
|
||||
android {
|
||||
compileSdkVersion 34
|
||||
buildToolsVersion '33.0.0'
|
||||
compileSdk 34
|
||||
buildToolsVersion "34.0.0"
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 24
|
||||
@@ -28,8 +28,8 @@ android {
|
||||
tempo {
|
||||
dimension "default"
|
||||
applicationId 'com.cappielloantonio.tempo'
|
||||
versionCode 14
|
||||
versionName '3.4.7'
|
||||
versionCode 21
|
||||
versionName '3.5.6'
|
||||
}
|
||||
|
||||
notquitemy {
|
||||
@@ -71,28 +71,29 @@ dependencies {
|
||||
// AndroidX
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.2.0'
|
||||
implementation 'androidx.preference:preference-ktx:1.2.0'
|
||||
implementation 'androidx.navigation:navigation-fragment-ktx:2.6.0'
|
||||
implementation 'androidx.navigation:navigation-ui-ktx:2.6.0'
|
||||
implementation 'androidx.preference:preference-ktx:1.2.1'
|
||||
implementation 'androidx.navigation:navigation-fragment-ktx:2.7.2'
|
||||
implementation 'androidx.navigation:navigation-ui-ktx:2.7.2'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.3.1'
|
||||
implementation 'androidx.room:room-runtime:2.5.2'
|
||||
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'
|
||||
|
||||
// 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.0'
|
||||
implementation 'androidx.media3:media3-common:1.1.0'
|
||||
implementation 'androidx.media3:media3-exoplayer:1.1.0'
|
||||
implementation 'androidx.media3:media3-ui:1.1.0'
|
||||
tempoImplementation 'androidx.media3:media3-cast:1.1.0'
|
||||
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'
|
||||
|
||||
annotationProcessor 'com.github.bumptech.glide:compiler:4.15.1'
|
||||
annotationProcessor 'com.github.bumptech.glide:compiler:4.16.0'
|
||||
annotationProcessor 'androidx.room:room-compiler:2.5.2'
|
||||
|
||||
// Retrofit
|
||||
|
||||
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,797 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 3,
|
||||
"identityHash": "6ea111217793c58d54eabb1190dd92ec",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "queue",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `track_order` INTEGER NOT NULL, `last_play` INTEGER NOT NULL, `playing_changed` INTEGER NOT NULL, `stream_id` TEXT, `parent_id` TEXT, `is_dir` INTEGER NOT NULL, `title` TEXT, `album` TEXT, `artist` TEXT, `track` INTEGER, `year` INTEGER, `genre` TEXT, `cover_art_id` TEXT, `size` INTEGER, `content_type` TEXT, `suffix` TEXT, `transcoding_content_type` TEXT, `transcoded_suffix` TEXT, `duration` INTEGER, `bitrate` INTEGER, `path` TEXT, `is_video` INTEGER NOT NULL, `user_rating` INTEGER, `average_rating` REAL, `play_count` INTEGER, `disc_number` INTEGER, `created` INTEGER, `starred` INTEGER, `album_id` TEXT, `artist_id` TEXT, `type` TEXT, `bookmark_position` INTEGER, `original_width` INTEGER, `original_height` INTEGER, PRIMARY KEY(`track_order`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "trackOrder",
|
||||
"columnName": "track_order",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastPlay",
|
||||
"columnName": "last_play",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "playingChanged",
|
||||
"columnName": "playing_changed",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "streamId",
|
||||
"columnName": "stream_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "parentId",
|
||||
"columnName": "parent_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "isDir",
|
||||
"columnName": "is_dir",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "title",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "album",
|
||||
"columnName": "album",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "artist",
|
||||
"columnName": "artist",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "track",
|
||||
"columnName": "track",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "year",
|
||||
"columnName": "year",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "genre",
|
||||
"columnName": "genre",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "coverArtId",
|
||||
"columnName": "cover_art_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "size",
|
||||
"columnName": "size",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "contentType",
|
||||
"columnName": "content_type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "suffix",
|
||||
"columnName": "suffix",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "transcodedContentType",
|
||||
"columnName": "transcoding_content_type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "transcodedSuffix",
|
||||
"columnName": "transcoded_suffix",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "duration",
|
||||
"columnName": "duration",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "bitrate",
|
||||
"columnName": "bitrate",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "path",
|
||||
"columnName": "path",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "isVideo",
|
||||
"columnName": "is_video",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "userRating",
|
||||
"columnName": "user_rating",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "averageRating",
|
||||
"columnName": "average_rating",
|
||||
"affinity": "REAL",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "playCount",
|
||||
"columnName": "play_count",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "discNumber",
|
||||
"columnName": "disc_number",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "created",
|
||||
"columnName": "created",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "starred",
|
||||
"columnName": "starred",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "albumId",
|
||||
"columnName": "album_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "artistId",
|
||||
"columnName": "artist_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "type",
|
||||
"columnName": "type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "bookmarkPosition",
|
||||
"columnName": "bookmark_position",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "originalWidth",
|
||||
"columnName": "original_width",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "originalHeight",
|
||||
"columnName": "original_height",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"track_order"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "server",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `server_name` TEXT NOT NULL, `username` TEXT NOT NULL, `password` TEXT NOT NULL, `address` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, `low_security` INTEGER NOT NULL DEFAULT false, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "serverId",
|
||||
"columnName": "id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "serverName",
|
||||
"columnName": "server_name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "username",
|
||||
"columnName": "username",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "password",
|
||||
"columnName": "password",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "address",
|
||||
"columnName": "address",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "timestamp",
|
||||
"columnName": "timestamp",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isLowSecurity",
|
||||
"columnName": "low_security",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "recent_search",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`search` TEXT NOT NULL, PRIMARY KEY(`search`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "search",
|
||||
"columnName": "search",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"search"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "download",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `playlist_id` TEXT, `playlist_name` TEXT, `download_state` INTEGER NOT NULL DEFAULT 1, `download_uri` TEXT DEFAULT '', `parent_id` TEXT, `is_dir` INTEGER NOT NULL, `title` TEXT, `album` TEXT, `artist` TEXT, `track` INTEGER, `year` INTEGER, `genre` TEXT, `cover_art_id` TEXT, `size` INTEGER, `content_type` TEXT, `suffix` TEXT, `transcoding_content_type` TEXT, `transcoded_suffix` TEXT, `duration` INTEGER, `bitrate` INTEGER, `path` TEXT, `is_video` INTEGER NOT NULL, `user_rating` INTEGER, `average_rating` REAL, `play_count` INTEGER, `disc_number` INTEGER, `created` INTEGER, `starred` INTEGER, `album_id` TEXT, `artist_id` TEXT, `type` TEXT, `bookmark_position` INTEGER, `original_width` INTEGER, `original_height` INTEGER, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "playlistId",
|
||||
"columnName": "playlist_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "playlistName",
|
||||
"columnName": "playlist_name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "downloadState",
|
||||
"columnName": "download_state",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "1"
|
||||
},
|
||||
{
|
||||
"fieldPath": "downloadUri",
|
||||
"columnName": "download_uri",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "''"
|
||||
},
|
||||
{
|
||||
"fieldPath": "parentId",
|
||||
"columnName": "parent_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "isDir",
|
||||
"columnName": "is_dir",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "title",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "album",
|
||||
"columnName": "album",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "artist",
|
||||
"columnName": "artist",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "track",
|
||||
"columnName": "track",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "year",
|
||||
"columnName": "year",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "genre",
|
||||
"columnName": "genre",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "coverArtId",
|
||||
"columnName": "cover_art_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "size",
|
||||
"columnName": "size",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "contentType",
|
||||
"columnName": "content_type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "suffix",
|
||||
"columnName": "suffix",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "transcodedContentType",
|
||||
"columnName": "transcoding_content_type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "transcodedSuffix",
|
||||
"columnName": "transcoded_suffix",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "duration",
|
||||
"columnName": "duration",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "bitrate",
|
||||
"columnName": "bitrate",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "path",
|
||||
"columnName": "path",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "isVideo",
|
||||
"columnName": "is_video",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "userRating",
|
||||
"columnName": "user_rating",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "averageRating",
|
||||
"columnName": "average_rating",
|
||||
"affinity": "REAL",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "playCount",
|
||||
"columnName": "play_count",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "discNumber",
|
||||
"columnName": "disc_number",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "created",
|
||||
"columnName": "created",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "starred",
|
||||
"columnName": "starred",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "albumId",
|
||||
"columnName": "album_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "artistId",
|
||||
"columnName": "artist_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "type",
|
||||
"columnName": "type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "bookmarkPosition",
|
||||
"columnName": "bookmark_position",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "originalWidth",
|
||||
"columnName": "original_width",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "originalHeight",
|
||||
"columnName": "original_height",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "chronology",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, `server` TEXT, `parent_id` TEXT, `is_dir` INTEGER NOT NULL, `title` TEXT, `album` TEXT, `artist` TEXT, `track` INTEGER, `year` INTEGER, `genre` TEXT, `cover_art_id` TEXT, `size` INTEGER, `content_type` TEXT, `suffix` TEXT, `transcoding_content_type` TEXT, `transcoded_suffix` TEXT, `duration` INTEGER, `bitrate` INTEGER, `path` TEXT, `is_video` INTEGER NOT NULL, `user_rating` INTEGER, `average_rating` REAL, `play_count` INTEGER, `disc_number` INTEGER, `created` INTEGER, `starred` INTEGER, `album_id` TEXT, `artist_id` TEXT, `type` TEXT, `bookmark_position` INTEGER, `original_width` INTEGER, `original_height` INTEGER, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "timestamp",
|
||||
"columnName": "timestamp",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "server",
|
||||
"columnName": "server",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "parentId",
|
||||
"columnName": "parent_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "isDir",
|
||||
"columnName": "is_dir",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "title",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "album",
|
||||
"columnName": "album",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "artist",
|
||||
"columnName": "artist",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "track",
|
||||
"columnName": "track",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "year",
|
||||
"columnName": "year",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "genre",
|
||||
"columnName": "genre",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "coverArtId",
|
||||
"columnName": "cover_art_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "size",
|
||||
"columnName": "size",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "contentType",
|
||||
"columnName": "content_type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "suffix",
|
||||
"columnName": "suffix",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "transcodedContentType",
|
||||
"columnName": "transcoding_content_type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "transcodedSuffix",
|
||||
"columnName": "transcoded_suffix",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "duration",
|
||||
"columnName": "duration",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "bitrate",
|
||||
"columnName": "bitrate",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "path",
|
||||
"columnName": "path",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "isVideo",
|
||||
"columnName": "is_video",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "userRating",
|
||||
"columnName": "user_rating",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "averageRating",
|
||||
"columnName": "average_rating",
|
||||
"affinity": "REAL",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "playCount",
|
||||
"columnName": "play_count",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "discNumber",
|
||||
"columnName": "disc_number",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "created",
|
||||
"columnName": "created",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "starred",
|
||||
"columnName": "starred",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "albumId",
|
||||
"columnName": "album_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "artistId",
|
||||
"columnName": "artist_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "type",
|
||||
"columnName": "type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "bookmarkPosition",
|
||||
"columnName": "bookmark_position",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "originalWidth",
|
||||
"columnName": "original_width",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "originalHeight",
|
||||
"columnName": "original_height",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "favorite",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`timestamp` INTEGER NOT NULL, `songId` TEXT, `albumId` TEXT, `artistId` TEXT, `toStar` INTEGER NOT NULL, PRIMARY KEY(`timestamp`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "timestamp",
|
||||
"columnName": "timestamp",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "songId",
|
||||
"columnName": "songId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "albumId",
|
||||
"columnName": "albumId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "artistId",
|
||||
"columnName": "artistId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "toStar",
|
||||
"columnName": "toStar",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"timestamp"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '6ea111217793c58d54eabb1190dd92ec')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,50 +1,71 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
||||
|
||||
<application
|
||||
android:name="App"
|
||||
android:allowBackup="false"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:localeConfig="@xml/locale_config"
|
||||
android:roundIcon="@mipmap/ic_launcher"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme.SplashScreen"
|
||||
android:usesCleartextTraffic="true"
|
||||
android:allowBackup="false">
|
||||
android:usesCleartextTraffic="true">
|
||||
|
||||
<meta-data
|
||||
android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
|
||||
android:value="androidx.media3.cast.DefaultCastOptionsProvider"/>
|
||||
android:value="androidx.media3.cast.DefaultCastOptionsProvider" />
|
||||
|
||||
<meta-data
|
||||
android:name="androidx.car.app.TintableAttributionIcon"
|
||||
android:resource="@drawable/ic_graphic_eq" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.activity.MainActivity"
|
||||
android:windowSoftInputMode="adjustPan|adjustResize"
|
||||
android:exported="true">
|
||||
android:exported="true"
|
||||
android:windowSoftInputMode="adjustPan|adjustResize">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<service
|
||||
android:name=".service.MediaService"
|
||||
android:foregroundServiceType="mediaPlayback"
|
||||
android:exported="true">
|
||||
android:exported="true"
|
||||
android:foregroundServiceType="mediaPlayback">
|
||||
<intent-filter>
|
||||
<action android:name="androidx.media3.session.MediaLibraryService" />
|
||||
<action android:name="androidx.media3.session.MediaBrowserService" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
<service android:name=".service.DownloaderService"
|
||||
android:exported="true">
|
||||
|
||||
<service
|
||||
android:name=".service.DownloaderService"
|
||||
android:exported="true"
|
||||
android:foregroundServiceType="dataSync">
|
||||
<intent-filter>
|
||||
<action android:name="androidx.media3.exoplayer.downloadService.action.RESTART"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<action android:name="androidx.media3.exoplayer.downloadService.action.RESTART" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<service
|
||||
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
|
||||
android:enabled="false"
|
||||
android:exported="false">
|
||||
<meta-data
|
||||
android:name="autoStoreLocales"
|
||||
android:value="true" />
|
||||
</service>
|
||||
</application>
|
||||
</manifest>
|
||||
@@ -10,7 +10,6 @@ import com.cappielloantonio.tempo.helper.ThemeHelper;
|
||||
import com.cappielloantonio.tempo.subsonic.Subsonic;
|
||||
import com.cappielloantonio.tempo.subsonic.SubsonicPreferences;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
import com.google.android.material.color.DynamicColors;
|
||||
|
||||
public class App extends Application {
|
||||
private static App instance;
|
||||
@@ -22,7 +21,6 @@ public class App extends Application {
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
DynamicColors.applyToActivitiesIfAvailable(this);
|
||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
|
||||
String themePref = sharedPreferences.getString(Preferences.THEME, ThemeHelper.DEFAULT_MODE);
|
||||
ThemeHelper.applyTheme(themePref);
|
||||
|
||||
@@ -22,9 +22,9 @@ import com.cappielloantonio.tempo.model.RecentSearch;
|
||||
import com.cappielloantonio.tempo.model.Server;
|
||||
|
||||
@Database(
|
||||
version = 2,
|
||||
version = 3,
|
||||
entities = {Queue.class, Server.class, RecentSearch.class, Download.class, Chronology.class, Favorite.class},
|
||||
autoMigrations = {@AutoMigration(from = 1, to = 2)}
|
||||
autoMigrations = {@AutoMigration(from = 2, to = 3)}
|
||||
)
|
||||
@TypeConverters({DateConverters.class})
|
||||
public abstract class AppDatabase extends RoomDatabase {
|
||||
|
||||
@@ -15,6 +15,9 @@ public interface DownloadDao {
|
||||
@Query("SELECT * FROM download WHERE download_state = 1 ORDER BY artist, album, track ASC")
|
||||
LiveData<List<Download>> getAll();
|
||||
|
||||
@Query("SELECT * FROM download WHERE id = :id")
|
||||
Download getOne(String id);
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
void insert(Download download);
|
||||
|
||||
|
||||
@@ -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,7 +18,9 @@ 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;
|
||||
|
||||
import java.util.Map;
|
||||
@@ -27,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();
|
||||
|
||||
@@ -46,7 +88,7 @@ public class CustomGlideRequest {
|
||||
uri.append("getCoverArt");
|
||||
|
||||
if (params.containsKey("u") && params.get("u") != null)
|
||||
uri.append("?u=").append(params.get("u"));
|
||||
uri.append("?u=").append(Util.encode(params.get("u")));
|
||||
if (params.containsKey("p") && params.get("p") != null)
|
||||
uri.append("&p=").append(params.get("p"));
|
||||
if (params.containsKey("s") && params.get("s") != null)
|
||||
@@ -71,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,5 @@ public interface ClickCallback {
|
||||
default void onMusicFolderClick(Bundle bundle) {}
|
||||
default void onMusicDirectoryClick(Bundle bundle) {}
|
||||
default void onMusicIndexClick(Bundle bundle) {}
|
||||
default void onDownloadGroupLongClick(Bundle bundle) {}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.cappielloantonio.tempo.interfaces;
|
||||
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
|
||||
@Keep
|
||||
public interface DialogClickCallback {
|
||||
default void onPositiveClick() {}
|
||||
|
||||
default void onNegativeClick() {}
|
||||
|
||||
default void onNeutralClick() {}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.cappielloantonio.tempo.interfaces;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
|
||||
@Keep
|
||||
public interface PlaylistCallback {
|
||||
default void onDismiss() {}
|
||||
}
|
||||
@@ -20,6 +20,9 @@ class Download(@PrimaryKey override val id: String) : Child(id) {
|
||||
@ColumnInfo(name = "download_state", defaultValue = "1")
|
||||
var downloadState: Int = 0
|
||||
|
||||
@ColumnInfo(name = "download_uri", defaultValue = "")
|
||||
var downloadUri: String? = null
|
||||
|
||||
constructor(child: Child) : this(child.id) {
|
||||
parentId = child.parentId
|
||||
isDir = child.isDir
|
||||
@@ -52,4 +55,10 @@ class Download(@PrimaryKey override val id: String) : Child(id) {
|
||||
originalWidth = child.originalWidth
|
||||
originalHeight = child.originalHeight
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Keep
|
||||
data class DownloadStack(
|
||||
var id: String,
|
||||
var view: String?
|
||||
)
|
||||
@@ -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,8 +63,12 @@ public class ArtistRepository {
|
||||
if (response.isSuccessful() && response.body() != null) {
|
||||
List<ArtistID3> artists = new ArrayList<>();
|
||||
|
||||
for (IndexID3 index : response.body().getSubsonicResponse().getArtists().getIndices()) {
|
||||
artists.addAll(index.getArtists());
|
||||
if(response.body().getSubsonicResponse().getArtists() != null && response.body().getSubsonicResponse().getArtists().getIndices() != null) {
|
||||
for (IndexID3 index : response.body().getSubsonicResponse().getArtists().getIndices()) {
|
||||
if(index != null && index.getArtists() != null) {
|
||||
artists.addAll(index.getArtists());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (random) {
|
||||
|
||||
@@ -4,8 +4,11 @@ import androidx.lifecycle.LiveData;
|
||||
|
||||
import com.cappielloantonio.tempo.database.AppDatabase;
|
||||
import com.cappielloantonio.tempo.database.dao.DownloadDao;
|
||||
import com.cappielloantonio.tempo.database.dao.FavoriteDao;
|
||||
import com.cappielloantonio.tempo.model.Download;
|
||||
import com.cappielloantonio.tempo.model.Favorite;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class DownloadRepository {
|
||||
@@ -15,6 +18,43 @@ public class DownloadRepository {
|
||||
return downloadDao.getAll();
|
||||
}
|
||||
|
||||
public Download getDownload(String id) {
|
||||
Download download = null;
|
||||
|
||||
GetDownloadThreadSafe getDownloadThreadSafe = new GetDownloadThreadSafe(downloadDao, id);
|
||||
Thread thread = new Thread(getDownloadThreadSafe);
|
||||
thread.start();
|
||||
|
||||
try {
|
||||
thread.join();
|
||||
download = getDownloadThreadSafe.getDownload();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return download;
|
||||
}
|
||||
|
||||
private static class GetDownloadThreadSafe implements Runnable {
|
||||
private final DownloadDao downloadDao;
|
||||
private final String id;
|
||||
private Download download;
|
||||
|
||||
public GetDownloadThreadSafe(DownloadDao downloadDao, String id) {
|
||||
this.downloadDao = downloadDao;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
download = downloadDao.getOne(id);
|
||||
}
|
||||
|
||||
public Download getDownload() {
|
||||
return download;
|
||||
}
|
||||
}
|
||||
|
||||
public void insert(Download download) {
|
||||
InsertThreadSafe insert = new InsertThreadSafe(downloadDao, download);
|
||||
Thread thread = new Thread(insert);
|
||||
|
||||
@@ -95,12 +95,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ public class SongRepository {
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
Log.d(TAG, "onFailure: ");
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
@@ -205,7 +205,7 @@ public class SongRepository {
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
Log.d(TAG, "onFailure: ");
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -77,6 +77,8 @@ public class DownloaderManager {
|
||||
}
|
||||
|
||||
public void download(MediaItem mediaItem, com.cappielloantonio.tempo.model.Download download) {
|
||||
download.setDownloadUri(mediaItem.requestMetadata.mediaUri.toString());
|
||||
|
||||
DownloadService.sendAddDownload(context, DownloaderService.class, buildDownloadRequest(mediaItem), false);
|
||||
insertDatabase(download);
|
||||
}
|
||||
@@ -89,6 +91,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());
|
||||
}
|
||||
|
||||
public void remove(List<MediaItem> mediaItems, List<com.cappielloantonio.tempo.model.Download> downloads) {
|
||||
@@ -97,6 +100,12 @@ public class DownloaderManager {
|
||||
}
|
||||
}
|
||||
|
||||
public void removeAll() {
|
||||
DownloadService.sendRemoveAllDownloads(context, DownloaderService.class, false);
|
||||
deleteAllDatabase();
|
||||
DownloadUtil.eraseDownloadFolder(context);
|
||||
}
|
||||
|
||||
private void loadDownloads() {
|
||||
try (DownloadCursor loadedDownloads = downloadIndex.getDownloads()) {
|
||||
while (loadedDownloads.moveToNext()) {
|
||||
@@ -108,6 +117,11 @@ public class DownloaderManager {
|
||||
}
|
||||
}
|
||||
|
||||
public static String getDownloadNotificationMessage(String id) {
|
||||
com.cappielloantonio.tempo.model.Download download = getDownloadRepository().getDownload(id);
|
||||
return download != null ? download.getTitle() : null;
|
||||
}
|
||||
|
||||
private static DownloadRepository getDownloadRepository() {
|
||||
return new DownloadRepository();
|
||||
}
|
||||
@@ -120,6 +134,10 @@ public class DownloaderManager {
|
||||
getDownloadRepository().delete(id);
|
||||
}
|
||||
|
||||
public static void deleteAllDatabase() {
|
||||
getDownloadRepository().deleteAll();
|
||||
}
|
||||
|
||||
public static void updateDatabase(String id) {
|
||||
getDownloadRepository().update(id);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media3.common.util.NotificationUtil;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.exoplayer.offline.Download;
|
||||
import androidx.media3.exoplayer.offline.DownloadManager;
|
||||
import androidx.media3.exoplayer.offline.DownloadNotificationHelper;
|
||||
@@ -55,12 +54,37 @@ public class DownloaderService extends androidx.media3.exoplayer.offline.Downloa
|
||||
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
|
||||
@@ -68,10 +92,14 @@ public class DownloaderService extends androidx.media3.exoplayer.offline.Downloa
|
||||
Notification notification;
|
||||
|
||||
if (download.state == Download.STATE_COMPLETED) {
|
||||
notification = notificationHelper.buildDownloadCompletedNotification(context, R.drawable.ic_check_circle, null, Util.fromUtf8Bytes(download.request.data));
|
||||
notification = notificationHelper.buildDownloadCompletedNotification(context, R.drawable.ic_check_circle, null, DownloaderManager.getDownloadNotificationMessage(download.request.id));
|
||||
notification = Notification.Builder.recoverBuilder(context, notification).setGroup(DownloadUtil.DOWNLOAD_NOTIFICATION_SUCCESSFUL_GROUP).build();
|
||||
NotificationUtil.setNotification(this.context, successfulDownloadGroupNotificationId, successfulDownloadGroupNotification);
|
||||
DownloaderManager.updateDatabase(download.request.id);
|
||||
} else if (download.state == Download.STATE_FAILED) {
|
||||
notification = notificationHelper.buildDownloadFailedNotification(context, R.drawable.ic_error, null, Util.fromUtf8Bytes(download.request.data));
|
||||
notification = notificationHelper.buildDownloadFailedNotification(context, R.drawable.ic_error, null, DownloaderManager.getDownloadNotificationMessage(download.request.id));
|
||||
notification = Notification.Builder.recoverBuilder(context, notification).setGroup(DownloadUtil.DOWNLOAD_NOTIFICATION_FAILED_GROUP).build();
|
||||
NotificationUtil.setNotification(this.context, failedDownloadGroupNotificationId, failedDownloadGroupNotification);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -204,6 +204,22 @@ public class MediaManager {
|
||||
}
|
||||
}
|
||||
|
||||
public static void shuffle(ListenableFuture<MediaBrowser> mediaBrowserListenableFuture, List<Child> media, int startIndex, int endIndex) {
|
||||
if (mediaBrowserListenableFuture != null) {
|
||||
mediaBrowserListenableFuture.addListener(() -> {
|
||||
try {
|
||||
if (mediaBrowserListenableFuture.isDone()) {
|
||||
mediaBrowserListenableFuture.get().removeMediaItems(startIndex, endIndex + 1);
|
||||
mediaBrowserListenableFuture.get().addMediaItems(MappingUtil.mapMediaItems(media).subList(startIndex, endIndex + 1));
|
||||
swapDatabase(media);
|
||||
}
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}, MoreExecutors.directExecutor());
|
||||
}
|
||||
}
|
||||
|
||||
public static void swap(ListenableFuture<MediaBrowser> mediaBrowserListenableFuture, List<Child> media, int from, int to) {
|
||||
if (mediaBrowserListenableFuture != null) {
|
||||
mediaBrowserListenableFuture.addListener(() -> {
|
||||
|
||||
@@ -7,5 +7,5 @@ import com.google.gson.annotations.SerializedName
|
||||
@Keep
|
||||
class ApiResponse {
|
||||
@SerializedName("subsonic-response")
|
||||
var subsonicResponse: SubsonicResponse? = null
|
||||
lateinit var subsonicResponse: SubsonicResponse;
|
||||
}
|
||||
@@ -31,6 +31,7 @@ import com.cappielloantonio.tempo.util.Preferences;
|
||||
import com.cappielloantonio.tempo.viewmodel.MainViewModel;
|
||||
import com.google.android.material.bottomnavigation.BottomNavigationView;
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior;
|
||||
import com.google.android.material.color.DynamicColors;
|
||||
import com.google.common.util.concurrent.MoreExecutors;
|
||||
|
||||
import java.util.Objects;
|
||||
@@ -51,10 +52,10 @@ public class MainActivity extends BaseActivity {
|
||||
|
||||
ConnectivityStatusBroadcastReceiver connectivityStatusBroadcastReceiver;
|
||||
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
SplashScreen.installSplashScreen(this);
|
||||
DynamicColors.applyToActivityIfAvailable(this);
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
@@ -118,6 +119,8 @@ public class MainActivity extends BaseActivity {
|
||||
fragmentManager.beginTransaction().replace(R.id.player_bottom_sheet, new PlayerBottomSheetFragment(), "PlayerBottomSheet").commit();
|
||||
|
||||
setBottomSheetInPeek(mainViewModel.isQueueLoaded());
|
||||
|
||||
collapseBottomSheet();
|
||||
}
|
||||
|
||||
public void setBottomSheetInPeek(Boolean isVisible) {
|
||||
|
||||
@@ -98,6 +98,7 @@ public class BaseActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
private void setNavigationBarColor() {
|
||||
getWindow().setNavigationBarColor(SurfaceColors.getColorForElevation(this, 10));
|
||||
getWindow().setNavigationBarColor(SurfaceColors.getColorForElevation(this, 8));
|
||||
getWindow().setStatusBarColor(SurfaceColors.getColorForElevation(this, 0));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -77,7 +77,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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -11,25 +11,35 @@ import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.cappielloantonio.tempo.R;
|
||||
import com.cappielloantonio.tempo.databinding.ItemHorizontalDownloadBinding;
|
||||
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
||||
import com.cappielloantonio.tempo.interfaces.ClickCallback;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||
import com.cappielloantonio.tempo.util.Constants;
|
||||
import com.cappielloantonio.tempo.util.MusicUtil;
|
||||
import com.cappielloantonio.tempo.util.Util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@UnstableApi
|
||||
public class DownloadHorizontalAdapter extends RecyclerView.Adapter<DownloadHorizontalAdapter.ViewHolder> {
|
||||
private final ClickCallback click;
|
||||
|
||||
private String view;
|
||||
private String filterKey;
|
||||
private String filterValue;
|
||||
|
||||
private List<Child> songs;
|
||||
private List<Child> grouped;
|
||||
|
||||
public DownloadHorizontalAdapter(ClickCallback click) {
|
||||
this.click = click;
|
||||
this.view = Constants.DOWNLOAD_TYPE_TRACK;
|
||||
this.songs = Collections.emptyList();
|
||||
this.grouped = Collections.emptyList();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@@ -41,31 +51,43 @@ public class DownloadHorizontalAdapter extends RecyclerView.Adapter<DownloadHori
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(ViewHolder holder, int position) {
|
||||
Child song = songs.get(position);
|
||||
|
||||
holder.item.downloadedSongTitleTextView.setText(MusicUtil.getReadableString(song.getTitle()));
|
||||
holder.item.downloadedSongArtistTextView.setText(holder.itemView.getContext().getString(R.string.song_subtitle_formatter, MusicUtil.getReadableString(song.getArtist()), MusicUtil.getReadableDurationString(song.getDuration(), false)));
|
||||
holder.item.downloadedSongAlbumTextView.setText(MusicUtil.getReadableString(song.getAlbum()));
|
||||
|
||||
if (position > 0 && songs.get(position - 1) != null && !Objects.equals(songs.get(position - 1).getAlbum(), songs.get(position).getAlbum())) {
|
||||
holder.item.divider.setPadding(0, (int) holder.itemView.getContext().getResources().getDimension(R.dimen.downloaded_item_padding), 0, 0);
|
||||
} else {
|
||||
if (position > 0) holder.item.divider.setVisibility(View.GONE);
|
||||
switch (view) {
|
||||
case Constants.DOWNLOAD_TYPE_TRACK:
|
||||
initTrackLayout(holder, position);
|
||||
break;
|
||||
case Constants.DOWNLOAD_TYPE_ALBUM:
|
||||
initAlbumLayout(holder, position);
|
||||
break;
|
||||
case Constants.DOWNLOAD_TYPE_ARTIST:
|
||||
initArtistLayout(holder, position);
|
||||
break;
|
||||
case Constants.DOWNLOAD_TYPE_GENRE:
|
||||
initGenreLayout(holder, position);
|
||||
break;
|
||||
case Constants.DOWNLOAD_TYPE_YEAR:
|
||||
initYearLayout(holder, position);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return songs.size();
|
||||
return grouped.size();
|
||||
}
|
||||
|
||||
public void setItems(List<Child> songs) {
|
||||
public void setItems(String view, String filterKey, String filterValue, List<Child> songs) {
|
||||
this.view = filterValue != null ? view : filterKey;
|
||||
this.filterKey = filterKey;
|
||||
this.filterValue = filterValue;
|
||||
|
||||
this.songs = songs;
|
||||
this.grouped = groupSong(songs);
|
||||
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public Child getItem(int id) {
|
||||
return songs.get(id);
|
||||
return grouped.get(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -78,6 +100,145 @@ public class DownloadHorizontalAdapter extends RecyclerView.Adapter<DownloadHori
|
||||
return position;
|
||||
}
|
||||
|
||||
private List<Child> groupSong(List<Child> songs) {
|
||||
switch (view) {
|
||||
case Constants.DOWNLOAD_TYPE_TRACK:
|
||||
return filterSong(filterKey, filterValue, songs.stream().filter(song -> Objects.nonNull(song.getId())).filter(Util.distinctByKey(Child::getId)).collect(Collectors.toList()));
|
||||
case Constants.DOWNLOAD_TYPE_ALBUM:
|
||||
return filterSong(filterKey, filterValue, songs.stream().filter(song -> Objects.nonNull(song.getAlbumId())).filter(Util.distinctByKey(Child::getAlbumId)).collect(Collectors.toList()));
|
||||
case Constants.DOWNLOAD_TYPE_ARTIST:
|
||||
return filterSong(filterKey, filterValue, songs.stream().filter(song -> Objects.nonNull(song.getArtistId())).filter(Util.distinctByKey(Child::getArtistId)).collect(Collectors.toList()));
|
||||
case Constants.DOWNLOAD_TYPE_GENRE:
|
||||
return filterSong(filterKey, filterValue, songs.stream().filter(song -> Objects.nonNull(song.getGenre())).filter(Util.distinctByKey(Child::getGenre)).collect(Collectors.toList()));
|
||||
case Constants.DOWNLOAD_TYPE_YEAR:
|
||||
return filterSong(filterKey, filterValue, songs.stream().filter(song -> Objects.nonNull(song.getYear())).filter(Util.distinctByKey(Child::getYear)).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
private List<Child> filterSong(String filterKey, String filterValue, List<Child> songs) {
|
||||
if (filterValue != null) {
|
||||
switch (filterKey) {
|
||||
case Constants.DOWNLOAD_TYPE_TRACK:
|
||||
return songs.stream().filter(child -> child.getId().equals(filterValue)).collect(Collectors.toList());
|
||||
case Constants.DOWNLOAD_TYPE_ALBUM:
|
||||
return songs.stream().filter(child -> Objects.equals(child.getAlbumId(), filterValue)).collect(Collectors.toList());
|
||||
case Constants.DOWNLOAD_TYPE_GENRE:
|
||||
return songs.stream().filter(child -> Objects.equals(child.getGenre(), filterValue)).collect(Collectors.toList());
|
||||
case Constants.DOWNLOAD_TYPE_YEAR:
|
||||
return songs.stream().filter(child -> Objects.equals(child.getYear(), Integer.valueOf(filterValue))).collect(Collectors.toList());
|
||||
case Constants.DOWNLOAD_TYPE_ARTIST:
|
||||
return songs.stream().filter(child -> Objects.equals(child.getArtistId(), filterValue)).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
||||
return songs;
|
||||
}
|
||||
|
||||
private String countSong(String filterKey, String filterValue, List<Child> songs) {
|
||||
if (filterValue != null) {
|
||||
switch (filterKey) {
|
||||
case Constants.DOWNLOAD_TYPE_TRACK:
|
||||
return String.valueOf(songs.stream().filter(child -> child.getId().equals(filterValue)).count());
|
||||
case Constants.DOWNLOAD_TYPE_ALBUM:
|
||||
return String.valueOf(songs.stream().filter(child -> Objects.equals(child.getAlbumId(), filterValue)).count());
|
||||
case Constants.DOWNLOAD_TYPE_GENRE:
|
||||
return String.valueOf(songs.stream().filter(child -> Objects.equals(child.getGenre(), filterValue)).count());
|
||||
case Constants.DOWNLOAD_TYPE_YEAR:
|
||||
return String.valueOf(songs.stream().filter(child -> Objects.equals(child.getYear(), Integer.valueOf(filterValue))).count());
|
||||
case Constants.DOWNLOAD_TYPE_ARTIST:
|
||||
return String.valueOf(songs.stream().filter(child -> Objects.equals(child.getArtistId(), filterValue)).count());
|
||||
}
|
||||
}
|
||||
|
||||
return "0";
|
||||
}
|
||||
|
||||
private void initTrackLayout(ViewHolder holder, int position) {
|
||||
Child song = grouped.get(position);
|
||||
|
||||
holder.item.downloadedItemTitleTextView.setText(MusicUtil.getReadableString(song.getTitle()));
|
||||
holder.item.downloadedItemSubtitleTextView.setText(holder.itemView.getContext().getString(R.string.song_subtitle_formatter, MusicUtil.getReadableString(song.getArtist()), MusicUtil.getReadableDurationString(song.getDuration(), false)));
|
||||
holder.item.downloadedItemPreTextView.setText(MusicUtil.getReadableString(song.getAlbum()));
|
||||
|
||||
CustomGlideRequest.Builder
|
||||
.from(holder.itemView.getContext(), song.getCoverArtId(), CustomGlideRequest.ResourceType.Song)
|
||||
.build()
|
||||
.into(holder.item.itemCoverImageView);
|
||||
|
||||
holder.item.itemCoverImageView.setVisibility(View.VISIBLE);
|
||||
holder.item.downloadedItemMoreButton.setVisibility(View.VISIBLE);
|
||||
holder.item.divider.setVisibility(View.VISIBLE);
|
||||
|
||||
if (position > 0 && grouped.get(position - 1) != null && !Objects.equals(grouped.get(position - 1).getAlbum(), grouped.get(position).getAlbum())) {
|
||||
holder.item.divider.setPadding(0, (int) holder.itemView.getContext().getResources().getDimension(R.dimen.downloaded_item_padding), 0, 0);
|
||||
} else {
|
||||
if (position > 0) holder.item.divider.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void initAlbumLayout(ViewHolder holder, int position) {
|
||||
Child song = grouped.get(position);
|
||||
|
||||
holder.item.downloadedItemTitleTextView.setText(MusicUtil.getReadableString(song.getAlbum()));
|
||||
holder.item.downloadedItemSubtitleTextView.setText(holder.itemView.getContext().getString(R.string.download_item_single_subtitle_formatter, countSong(Constants.DOWNLOAD_TYPE_ALBUM, song.getAlbumId(), songs)));
|
||||
holder.item.downloadedItemPreTextView.setText(MusicUtil.getReadableString(song.getArtist()));
|
||||
|
||||
CustomGlideRequest.Builder
|
||||
.from(holder.itemView.getContext(), song.getCoverArtId(), CustomGlideRequest.ResourceType.Song)
|
||||
.build()
|
||||
.into(holder.item.itemCoverImageView);
|
||||
|
||||
holder.item.itemCoverImageView.setVisibility(View.VISIBLE);
|
||||
holder.item.downloadedItemMoreButton.setVisibility(View.VISIBLE);
|
||||
holder.item.divider.setVisibility(View.VISIBLE);
|
||||
|
||||
if (position > 0 && grouped.get(position - 1) != null && !Objects.equals(grouped.get(position - 1).getArtist(), grouped.get(position).getArtist())) {
|
||||
holder.item.divider.setPadding(0, (int) holder.itemView.getContext().getResources().getDimension(R.dimen.downloaded_item_padding), 0, 0);
|
||||
} else {
|
||||
if (position > 0) holder.item.divider.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void initArtistLayout(ViewHolder holder, int position) {
|
||||
Child song = grouped.get(position);
|
||||
|
||||
holder.item.downloadedItemTitleTextView.setText(MusicUtil.getReadableString(song.getArtist()));
|
||||
holder.item.downloadedItemSubtitleTextView.setText(holder.itemView.getContext().getString(R.string.download_item_single_subtitle_formatter, countSong(Constants.DOWNLOAD_TYPE_ARTIST, song.getArtistId(), songs)));
|
||||
|
||||
CustomGlideRequest.Builder
|
||||
.from(holder.itemView.getContext(), song.getCoverArtId(), CustomGlideRequest.ResourceType.Song)
|
||||
.build()
|
||||
.into(holder.item.itemCoverImageView);
|
||||
|
||||
holder.item.itemCoverImageView.setVisibility(View.VISIBLE);
|
||||
holder.item.downloadedItemMoreButton.setVisibility(View.VISIBLE);
|
||||
holder.item.divider.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
private void initGenreLayout(ViewHolder holder, int position) {
|
||||
Child song = grouped.get(position);
|
||||
|
||||
holder.item.downloadedItemTitleTextView.setText(MusicUtil.getReadableString(song.getGenre()));
|
||||
holder.item.downloadedItemSubtitleTextView.setText(holder.itemView.getContext().getString(R.string.download_item_single_subtitle_formatter, countSong(Constants.DOWNLOAD_TYPE_GENRE, song.getGenre(), songs)));
|
||||
|
||||
holder.item.itemCoverImageView.setVisibility(View.GONE);
|
||||
holder.item.downloadedItemMoreButton.setVisibility(View.VISIBLE);
|
||||
holder.item.divider.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
private void initYearLayout(ViewHolder holder, int position) {
|
||||
Child song = grouped.get(position);
|
||||
|
||||
holder.item.downloadedItemTitleTextView.setText(String.valueOf(song.getYear()));
|
||||
holder.item.downloadedItemSubtitleTextView.setText(holder.itemView.getContext().getString(R.string.download_item_single_subtitle_formatter, countSong(Constants.DOWNLOAD_TYPE_YEAR, song.getYear().toString(), songs)));
|
||||
|
||||
holder.item.itemCoverImageView.setVisibility(View.GONE);
|
||||
holder.item.downloadedItemMoreButton.setVisibility(View.VISIBLE);
|
||||
holder.item.divider.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
public class ViewHolder extends RecyclerView.ViewHolder {
|
||||
ItemHorizontalDownloadBinding item;
|
||||
|
||||
@@ -86,28 +247,72 @@ public class DownloadHorizontalAdapter extends RecyclerView.Adapter<DownloadHori
|
||||
|
||||
this.item = item;
|
||||
|
||||
item.downloadedSongTitleTextView.setSelected(true);
|
||||
item.downloadedSongArtistTextView.setSelected(true);
|
||||
item.downloadedItemTitleTextView.setSelected(true);
|
||||
item.downloadedItemSubtitleTextView.setSelected(true);
|
||||
|
||||
itemView.setOnClickListener(v -> onClick());
|
||||
itemView.setOnLongClickListener(v -> onLongClick());
|
||||
|
||||
item.downloadedSongMoreButton.setOnClickListener(v -> onLongClick());
|
||||
item.downloadedItemMoreButton.setOnClickListener(v -> onLongClick());
|
||||
}
|
||||
|
||||
public void onClick() {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelableArrayList(Constants.TRACKS_OBJECT, new ArrayList<>(songs));
|
||||
bundle.putInt(Constants.ITEM_POSITION, getBindingAdapterPosition());
|
||||
|
||||
click.onMediaClick(bundle);
|
||||
switch (view) {
|
||||
case Constants.DOWNLOAD_TYPE_TRACK:
|
||||
bundle.putParcelableArrayList(Constants.TRACKS_OBJECT, new ArrayList<>(grouped));
|
||||
bundle.putInt(Constants.ITEM_POSITION, getBindingAdapterPosition());
|
||||
click.onMediaClick(bundle);
|
||||
break;
|
||||
case Constants.DOWNLOAD_TYPE_ALBUM:
|
||||
bundle.putString(Constants.DOWNLOAD_TYPE_ALBUM, grouped.get(getBindingAdapterPosition()).getAlbumId());
|
||||
click.onAlbumClick(bundle);
|
||||
break;
|
||||
case Constants.DOWNLOAD_TYPE_ARTIST:
|
||||
bundle.putString(Constants.DOWNLOAD_TYPE_ARTIST, grouped.get(getBindingAdapterPosition()).getArtistId());
|
||||
click.onArtistClick(bundle);
|
||||
break;
|
||||
case Constants.DOWNLOAD_TYPE_GENRE:
|
||||
bundle.putString(Constants.DOWNLOAD_TYPE_GENRE, grouped.get(getBindingAdapterPosition()).getGenre());
|
||||
click.onGenreClick(bundle);
|
||||
break;
|
||||
case Constants.DOWNLOAD_TYPE_YEAR:
|
||||
bundle.putString(Constants.DOWNLOAD_TYPE_YEAR, grouped.get(getBindingAdapterPosition()).getYear().toString());
|
||||
click.onYearClick(bundle);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean onLongClick() {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelable(Constants.TRACK_OBJECT, songs.get(getBindingAdapterPosition()));
|
||||
ArrayList<Child> filteredSongs = new ArrayList<>();
|
||||
|
||||
click.onMediaLongClick(bundle);
|
||||
Bundle bundle = new Bundle();
|
||||
|
||||
switch (view) {
|
||||
case Constants.DOWNLOAD_TYPE_TRACK:
|
||||
filteredSongs.add(grouped.get(getBindingAdapterPosition()));
|
||||
break;
|
||||
case Constants.DOWNLOAD_TYPE_ALBUM:
|
||||
filteredSongs.addAll(filterSong(Constants.DOWNLOAD_TYPE_ALBUM, grouped.get(getBindingAdapterPosition()).getAlbumId(), songs));
|
||||
break;
|
||||
case Constants.DOWNLOAD_TYPE_ARTIST:
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.cappielloantonio.tempo.ui.adapter;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -42,10 +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.INVISIBLE);
|
||||
holder.item.musicDirectoryPlayButton.setVisibility(child.isDir() ? View.INVISIBLE : View.VISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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.getOriginalImageUrl(), 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);
|
||||
|
||||
|
||||
@@ -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,8 +49,8 @@ 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.trackNumberTextView.setText(String.valueOf(song.getTrack()));
|
||||
holder.item.searchResultSongSubtitleTextView.setText(holder.itemView.getContext().getString(R.string.song_subtitle_formatter, MusicUtil.getReadableString(this.showAlbum ? song.getAlbum() : song.getArtist()), MusicUtil.getReadableDurationString(song.getDuration() != null ? song.getDuration() : 0, false)));
|
||||
holder.item.trackNumberTextView.setText(MusicUtil.getReadableTrackNumber(holder.itemView.getContext(), song.getTrack()));
|
||||
|
||||
if (DownloadUtil.getDownloadTracker(holder.itemView.getContext()).isDownloaded(song.getId())) {
|
||||
holder.item.searchResultDowanloadIndicatorImageView.setVisibility(View.VISIBLE);
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -80,6 +81,16 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAd
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return position;
|
||||
}
|
||||
|
||||
public Child getItem(int id) {
|
||||
return songs.get(id);
|
||||
}
|
||||
@@ -104,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);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.cappielloantonio.tempo.ui.dialog;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.os.Bundle;
|
||||
import android.widget.Button;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.OptIn;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
|
||||
import com.cappielloantonio.tempo.R;
|
||||
import com.cappielloantonio.tempo.databinding.DialogDeleteDownloadStorageBinding;
|
||||
import com.cappielloantonio.tempo.interfaces.DialogClickCallback;
|
||||
import com.cappielloantonio.tempo.util.DownloadUtil;
|
||||
|
||||
@OptIn(markerClass = UnstableApi.class)
|
||||
public class DeleteDownloadStorageDialog extends DialogFragment {
|
||||
private DialogDeleteDownloadStorageBinding bind;
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
bind = DialogDeleteDownloadStorageBinding.inflate(getLayoutInflater());
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
|
||||
builder.setView(bind.getRoot())
|
||||
.setTitle(R.string.delete_download_storage_dialog_title)
|
||||
.setPositiveButton(R.string.delete_download_storage_dialog_positive_button, null)
|
||||
.setNegativeButton(R.string.delete_download_storage_dialog_negative_button, null);
|
||||
|
||||
return builder.create();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
setButtonAction();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
bind = null;
|
||||
}
|
||||
|
||||
private void setButtonAction() {
|
||||
AlertDialog dialog = ((AlertDialog) getDialog());
|
||||
|
||||
if (dialog != null) {
|
||||
Button positiveButton = dialog.getButton(Dialog.BUTTON_POSITIVE);
|
||||
positiveButton.setOnClickListener(v -> {
|
||||
DownloadUtil.getDownloadTracker(requireContext()).removeAll();
|
||||
dialog.dismiss();
|
||||
});
|
||||
|
||||
Button negativeButton = dialog.getButton(Dialog.BUTTON_NEGATIVE);
|
||||
negativeButton.setOnClickListener(v -> {
|
||||
dialog.dismiss();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package com.cappielloantonio.tempo.ui.dialog;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.os.Bundle;
|
||||
import android.widget.Button;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.OptIn;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
|
||||
import com.cappielloantonio.tempo.R;
|
||||
import com.cappielloantonio.tempo.databinding.DialogDownloadStorageBinding;
|
||||
import com.cappielloantonio.tempo.interfaces.DialogClickCallback;
|
||||
import com.cappielloantonio.tempo.util.DownloadUtil;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
|
||||
@OptIn(markerClass = UnstableApi.class)
|
||||
public class DownloadStorageDialog extends DialogFragment {
|
||||
private DialogDownloadStorageBinding bind;
|
||||
|
||||
private final DialogClickCallback dialogClickCallback;
|
||||
|
||||
public DownloadStorageDialog(DialogClickCallback dialogClickCallback) {
|
||||
this.dialogClickCallback = dialogClickCallback;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
bind = DialogDownloadStorageBinding.inflate(getLayoutInflater());
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
|
||||
builder.setView(bind.getRoot())
|
||||
.setTitle(R.string.download_storage_dialog_title)
|
||||
.setPositiveButton(R.string.download_storage_external_dialog_positive_button, null)
|
||||
.setNegativeButton(R.string.download_storage_internal_dialog_negative_button, null);
|
||||
|
||||
return builder.create();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
setButtonAction();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
bind = null;
|
||||
}
|
||||
|
||||
private void setButtonAction() {
|
||||
AlertDialog dialog = ((AlertDialog) getDialog());
|
||||
|
||||
if (dialog != null) {
|
||||
Button positiveButton = dialog.getButton(Dialog.BUTTON_POSITIVE);
|
||||
positiveButton.setOnClickListener(v -> {
|
||||
int currentPreference = Preferences.getDownloadStoragePreference();
|
||||
int newPreference = 1;
|
||||
|
||||
if (currentPreference != newPreference) {
|
||||
Preferences.setDownloadStoragePreference(newPreference);
|
||||
DownloadUtil.getDownloadTracker(requireContext()).removeAll();
|
||||
dialogClickCallback.onPositiveClick();
|
||||
}
|
||||
|
||||
dialog.dismiss();
|
||||
});
|
||||
|
||||
Button negativeButton = dialog.getButton(Dialog.BUTTON_NEGATIVE);
|
||||
negativeButton.setOnClickListener(v -> {
|
||||
int currentPreference = Preferences.getDownloadStoragePreference();
|
||||
int newPreference = 0;
|
||||
|
||||
if (currentPreference != newPreference) {
|
||||
Preferences.setDownloadStoragePreference(newPreference);
|
||||
DownloadUtil.getDownloadTracker(requireContext()).removeAll();
|
||||
dialogClickCallback.onNegativeClick();
|
||||
}
|
||||
|
||||
dialog.dismiss();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -67,7 +67,7 @@ public class PlaylistChooserDialog extends DialogFragment implements ClickCallba
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelable(Constants.TRACK_OBJECT, playlistChooserViewModel.getSongToAdd());
|
||||
|
||||
PlaylistEditorDialog dialog = new PlaylistEditorDialog();
|
||||
PlaylistEditorDialog dialog = new PlaylistEditorDialog(null);
|
||||
dialog.setArguments(bundle);
|
||||
dialog.show(requireActivity().getSupportFragmentManager(), null);
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ 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;
|
||||
@@ -25,10 +26,15 @@ import java.util.Objects;
|
||||
public class PlaylistEditorDialog extends DialogFragment {
|
||||
private DialogPlaylistEditorBinding bind;
|
||||
private PlaylistEditorViewModel playlistEditorViewModel;
|
||||
private PlaylistCallback playlistCallback;
|
||||
|
||||
private String playlistName;
|
||||
private PlaylistDialogSongHorizontalAdapter playlistDialogSongHorizontalAdapter;
|
||||
|
||||
public PlaylistEditorDialog(PlaylistCallback playlistCallback) {
|
||||
this.playlistCallback = playlistCallback;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
@@ -85,13 +91,13 @@ public class PlaylistEditorDialog extends DialogFragment {
|
||||
playlistEditorViewModel.updatePlaylist(playlistName);
|
||||
}
|
||||
|
||||
Objects.requireNonNull(getDialog()).dismiss();
|
||||
dialogDismiss();
|
||||
}
|
||||
});
|
||||
|
||||
((AlertDialog) Objects.requireNonNull(getDialog())).getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener(v -> {
|
||||
playlistEditorViewModel.deletePlaylist();
|
||||
Objects.requireNonNull(getDialog()).dismiss();
|
||||
dialogDismiss();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -102,7 +108,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 +165,9 @@ public class PlaylistEditorDialog extends DialogFragment {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void dialogDismiss() {
|
||||
Objects.requireNonNull(getDialog()).dismiss();
|
||||
playlistCallback.onDismiss();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
package com.cappielloantonio.tempo.ui.dialog;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.media3.common.MediaMetadata;
|
||||
|
||||
import com.cappielloantonio.tempo.R;
|
||||
import com.cappielloantonio.tempo.databinding.DialogTrackInfoBinding;
|
||||
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
||||
import com.cappielloantonio.tempo.util.Constants;
|
||||
import com.cappielloantonio.tempo.util.MusicUtil;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
|
||||
public class TrackInfoDialog extends DialogFragment {
|
||||
private static final String TAG = "TrackInfoDialog";
|
||||
|
||||
private DialogTrackInfoBinding bind;
|
||||
private MediaMetadata mediaMetadata;
|
||||
|
||||
public TrackInfoDialog(MediaMetadata mediaMetadata) {
|
||||
this.mediaMetadata = mediaMetadata;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
bind = DialogTrackInfoBinding.inflate(getLayoutInflater());
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
|
||||
builder.setView(bind.getRoot())
|
||||
.setPositiveButton(R.string.track_info_dialog_positive_button, (dialog, id) -> dialog.cancel());
|
||||
|
||||
return builder.create();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
|
||||
setTrackInfo();
|
||||
setTrackTranscodingInfo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
bind = null;
|
||||
}
|
||||
|
||||
private void setTrackInfo() {
|
||||
bind.trakTitleInfoTextView.setText(mediaMetadata.title);
|
||||
bind.trakArtistInfoTextView.setText(mediaMetadata.artist);
|
||||
|
||||
if (mediaMetadata.extras != null) {
|
||||
CustomGlideRequest.Builder
|
||||
.from(requireContext(), mediaMetadata.extras.getString("coverArtId", ""), 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
|
||||
@@ -119,7 +119,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);
|
||||
|
||||
@@ -164,7 +164,7 @@ 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) {
|
||||
|
||||
@@ -3,10 +3,14 @@ package com.cappielloantonio.tempo.ui.fragment;
|
||||
import android.content.ComponentName;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
@@ -19,14 +23,21 @@ 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.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.util.Constants;
|
||||
import com.cappielloantonio.tempo.util.DownloadUtil;
|
||||
import com.cappielloantonio.tempo.util.MappingUtil;
|
||||
import com.cappielloantonio.tempo.viewmodel.DirectoryViewModel;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@UnstableApi
|
||||
public class DirectoryFragment extends Fragment implements ClickCallback {
|
||||
private static final String TAG = "DirectoryFragment";
|
||||
@@ -39,6 +50,18 @@ public class DirectoryFragment extends Fragment implements ClickCallback {
|
||||
|
||||
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
inflater.inflate(R.menu.directory_page_menu, menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
activity = (MainActivity) getActivity();
|
||||
@@ -71,6 +94,24 @@ public class DirectoryFragment extends Fragment implements ClickCallback {
|
||||
bind = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||
if (item.getItemId() == R.id.action_download_directory) {
|
||||
directoryViewModel.loadMusicDirectory(getArguments().getString(Constants.MUSIC_DIRECTORY_ID)).observe(getViewLifecycleOwner(), directory -> {
|
||||
if (isVisible() && getActivity() != null) {
|
||||
List<Child> songs = directory.getChildren().stream().filter(child -> !child.isDir()).collect(Collectors.toList());
|
||||
DownloadUtil.getDownloadTracker(requireContext()).download(
|
||||
MappingUtil.mapDownloads(songs),
|
||||
songs.stream().map(Download::new).collect(Collectors.toList())
|
||||
);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void initAppBar() {
|
||||
activity.setSupportActionBar(bind.toolbar);
|
||||
|
||||
|
||||
@@ -5,7 +5,9 @@ import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
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;
|
||||
@@ -19,19 +21,25 @@ import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import com.cappielloantonio.tempo.R;
|
||||
import com.cappielloantonio.tempo.databinding.FragmentDownloadBinding;
|
||||
import com.cappielloantonio.tempo.interfaces.ClickCallback;
|
||||
import com.cappielloantonio.tempo.model.DownloadStack;
|
||||
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.DownloadHorizontalAdapter;
|
||||
import com.cappielloantonio.tempo.util.Constants;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
import com.cappielloantonio.tempo.viewmodel.DownloadViewModel;
|
||||
import com.google.android.material.appbar.MaterialToolbar;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
|
||||
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;
|
||||
@@ -59,7 +67,7 @@ public class DownloadFragment extends Fragment implements ClickCallback {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
initAppBar();
|
||||
initDownloadedSongView();
|
||||
initDownloadedView();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -90,11 +98,12 @@ public class DownloadFragment extends Fragment implements ClickCallback {
|
||||
Objects.requireNonNull(materialToolbar.getOverflowIcon()).setTint(requireContext().getResources().getColor(R.color.titleTextColor, null));
|
||||
}
|
||||
|
||||
private void initDownloadedSongView() {
|
||||
bind.downloadedTracksRecyclerView.setHasFixedSize(true);
|
||||
private void initDownloadedView() {
|
||||
bind.downloadedRecyclerView.setHasFixedSize(true);
|
||||
|
||||
downloadHorizontalAdapter = new DownloadHorizontalAdapter(this);
|
||||
bind.downloadedTracksRecyclerView.setAdapter(downloadHorizontalAdapter);
|
||||
bind.downloadedRecyclerView.setAdapter(downloadHorizontalAdapter);
|
||||
|
||||
downloadViewModel.getDownloadedTracks(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), songs -> {
|
||||
if (songs != null) {
|
||||
if (songs.isEmpty()) {
|
||||
@@ -102,26 +111,109 @@ public class DownloadFragment extends Fragment implements ClickCallback {
|
||||
bind.emptyDownloadLayout.setVisibility(View.VISIBLE);
|
||||
bind.fragmentDownloadNestedScrollView.setVisibility(View.GONE);
|
||||
|
||||
bind.downloadDownloadedTracksPlaceholder.placeholder.setVisibility(View.VISIBLE);
|
||||
bind.downloadDownloadedTracksSector.setVisibility(View.GONE);
|
||||
bind.downloadDownloadedPlaceholder.placeholder.setVisibility(View.VISIBLE);
|
||||
bind.downloadDownloadedSector.setVisibility(View.GONE);
|
||||
|
||||
bind.downloadedGroupByImageView.setVisibility(View.GONE);
|
||||
}
|
||||
} else {
|
||||
if (bind != null) {
|
||||
bind.emptyDownloadLayout.setVisibility(View.GONE);
|
||||
bind.fragmentDownloadNestedScrollView.setVisibility(View.VISIBLE);
|
||||
|
||||
bind.downloadDownloadedTracksPlaceholder.placeholder.setVisibility(View.GONE);
|
||||
bind.downloadDownloadedTracksSector.setVisibility(View.VISIBLE);
|
||||
bind.downloadDownloadedPlaceholder.placeholder.setVisibility(View.GONE);
|
||||
bind.downloadDownloadedSector.setVisibility(View.VISIBLE);
|
||||
|
||||
bind.downloadedTracksRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
|
||||
bind.downloadedGroupByImageView.setVisibility(View.VISIBLE);
|
||||
|
||||
downloadHorizontalAdapter.setItems(songs);
|
||||
finishDownloadView(songs);
|
||||
}
|
||||
}
|
||||
|
||||
if (bind != null) bind.loadingProgressBar.setVisibility(View.GONE);
|
||||
}
|
||||
});
|
||||
|
||||
bind.downloadedGroupByImageView.setOnClickListener(view -> showPopupMenu(view, R.menu.download_popup_menu));
|
||||
bind.downloadedGoBackImageView.setOnClickListener(view -> downloadViewModel.popViewStack());
|
||||
}
|
||||
|
||||
private void finishDownloadView(List<Child> songs) {
|
||||
downloadViewModel.getViewStack().observe(getViewLifecycleOwner(), stack -> {
|
||||
bind.downloadedRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
|
||||
|
||||
DownloadStack lastLevel = stack.get(stack.size() - 1);
|
||||
|
||||
switch (lastLevel.getId()) {
|
||||
case Constants.DOWNLOAD_TYPE_TRACK:
|
||||
downloadHorizontalAdapter.setItems(Constants.DOWNLOAD_TYPE_TRACK, lastLevel.getId(), lastLevel.getView(), songs);
|
||||
break;
|
||||
case Constants.DOWNLOAD_TYPE_ALBUM:
|
||||
downloadHorizontalAdapter.setItems(Constants.DOWNLOAD_TYPE_TRACK, lastLevel.getId(), lastLevel.getView(), songs);
|
||||
break;
|
||||
case Constants.DOWNLOAD_TYPE_ARTIST:
|
||||
downloadHorizontalAdapter.setItems(Constants.DOWNLOAD_TYPE_ALBUM, lastLevel.getId(), lastLevel.getView(), songs);
|
||||
break;
|
||||
case Constants.DOWNLOAD_TYPE_GENRE:
|
||||
downloadHorizontalAdapter.setItems(Constants.DOWNLOAD_TYPE_TRACK, lastLevel.getId(), lastLevel.getView(), songs);
|
||||
break;
|
||||
case Constants.DOWNLOAD_TYPE_YEAR:
|
||||
downloadHorizontalAdapter.setItems(Constants.DOWNLOAD_TYPE_TRACK, lastLevel.getId(), lastLevel.getView(), songs);
|
||||
break;
|
||||
}
|
||||
|
||||
bind.downloadedGoBackImageView.setVisibility(stack.size() > 1 ? View.VISIBLE : View.GONE);
|
||||
|
||||
setupBackPressing(stack.size());
|
||||
});
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void showPopupMenu(View view, int menuResource) {
|
||||
PopupMenu popup = new PopupMenu(requireContext(), view);
|
||||
popup.getMenuInflater().inflate(menuResource, popup.getMenu());
|
||||
|
||||
popup.setOnMenuItemClickListener(menuItem -> {
|
||||
if (menuItem.getItemId() == R.id.menu_download_group_by_track) {
|
||||
downloadViewModel.initViewStack(new DownloadStack(Constants.DOWNLOAD_TYPE_TRACK, null));
|
||||
Preferences.setDefaultDownloadViewType(Constants.DOWNLOAD_TYPE_TRACK);
|
||||
return true;
|
||||
} else if (menuItem.getItemId() == R.id.menu_download_group_by_album) {
|
||||
downloadViewModel.initViewStack(new DownloadStack(Constants.DOWNLOAD_TYPE_ALBUM, null));
|
||||
Preferences.setDefaultDownloadViewType(Constants.DOWNLOAD_TYPE_ALBUM);
|
||||
return true;
|
||||
} else if (menuItem.getItemId() == R.id.menu_download_group_by_artist) {
|
||||
downloadViewModel.initViewStack(new DownloadStack(Constants.DOWNLOAD_TYPE_ARTIST, null));
|
||||
Preferences.setDefaultDownloadViewType(Constants.DOWNLOAD_TYPE_ARTIST);
|
||||
return true;
|
||||
} else if (menuItem.getItemId() == R.id.menu_download_group_by_genre) {
|
||||
downloadViewModel.initViewStack(new DownloadStack(Constants.DOWNLOAD_TYPE_GENRE, null));
|
||||
Preferences.setDefaultDownloadViewType(Constants.DOWNLOAD_TYPE_GENRE);
|
||||
return true;
|
||||
} else if (menuItem.getItemId() == R.id.menu_download_group_by_year) {
|
||||
downloadViewModel.initViewStack(new DownloadStack(Constants.DOWNLOAD_TYPE_YEAR, null));
|
||||
Preferences.setDefaultDownloadViewType(Constants.DOWNLOAD_TYPE_YEAR);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
popup.show();
|
||||
}
|
||||
|
||||
private void initializeMediaBrowser() {
|
||||
@@ -132,6 +224,26 @@ public class DownloadFragment extends Fragment implements ClickCallback {
|
||||
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onYearClick(Bundle bundle) {
|
||||
downloadViewModel.pushViewStack(new DownloadStack(Constants.DOWNLOAD_TYPE_YEAR, bundle.getString(Constants.DOWNLOAD_TYPE_YEAR)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGenreClick(Bundle bundle) {
|
||||
downloadViewModel.pushViewStack(new DownloadStack(Constants.DOWNLOAD_TYPE_GENRE, bundle.getString(Constants.DOWNLOAD_TYPE_GENRE)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onArtistClick(Bundle bundle) {
|
||||
downloadViewModel.pushViewStack(new DownloadStack(Constants.DOWNLOAD_TYPE_ARTIST, bundle.getString(Constants.DOWNLOAD_TYPE_ARTIST)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAlbumClick(Bundle bundle) {
|
||||
downloadViewModel.pushViewStack(new DownloadStack(Constants.DOWNLOAD_TYPE_ALBUM, bundle.getString(Constants.DOWNLOAD_TYPE_ALBUM)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMediaClick(Bundle bundle) {
|
||||
MediaManager.startQueue(mediaBrowserListenableFuture, bundle.getParcelableArrayList(Constants.TRACKS_OBJECT), bundle.getInt(Constants.ITEM_POSITION));
|
||||
@@ -142,4 +254,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -290,11 +289,14 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||
bind.discoverSongViewPager.setOffscreenPageLimit(1);
|
||||
homeViewModel.getDiscoverSongSample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), 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);
|
||||
}
|
||||
@@ -311,11 +313,14 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||
bind.similarTracksRecyclerView.setAdapter(similarMusicAdapter);
|
||||
homeViewModel.getStarredTracksSample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), 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 +338,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 +363,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 +390,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 +409,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 +448,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 +483,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 +520,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 +556,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 +581,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 +607,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 +632,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);
|
||||
}
|
||||
@@ -619,25 +664,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();
|
||||
}
|
||||
|
||||
@@ -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,12 @@ public class LibraryFragment extends Fragment implements ClickCallback {
|
||||
});
|
||||
}
|
||||
|
||||
private void refreshPlaylistView() {
|
||||
final Handler handler = new Handler();
|
||||
final Runnable runnable = () -> libraryViewModel.refreshPlaylistSample(getViewLifecycleOwner());
|
||||
handler.postDelayed(runnable, 100);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAlbumClick(Bundle bundle) {
|
||||
Navigation.findNavController(requireView()).navigate(R.id.albumPageFragment, bundle);
|
||||
@@ -276,7 +290,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);
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ public class PlayerBottomSheetFragment extends Fragment {
|
||||
}
|
||||
|
||||
private void customizeBottomSheetBackground() {
|
||||
bind.playerHeaderLayout.getRoot().setBackgroundColor(SurfaceColors.getColorForElevation(requireContext(), 2));
|
||||
bind.playerHeaderLayout.getRoot().setBackgroundColor(SurfaceColors.getColorForElevation(requireContext(), 8));
|
||||
}
|
||||
|
||||
private void customizeBottomSheetAction() {
|
||||
@@ -161,7 +161,7 @@ public class PlayerBottomSheetFragment extends Fragment {
|
||||
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);
|
||||
}
|
||||
@@ -240,6 +240,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,12 +29,14 @@ 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;
|
||||
|
||||
@@ -51,10 +53,8 @@ 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 playerTrackInfo;
|
||||
|
||||
private MainActivity activity;
|
||||
private PlayerBottomSheetViewModel playerBottomSheetViewModel;
|
||||
@@ -70,10 +70,10 @@ public class PlayerControllerFragment extends Fragment {
|
||||
playerBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(PlayerBottomSheetViewModel.class);
|
||||
|
||||
init();
|
||||
initQuickActionView();
|
||||
initCoverLyricsSlideView();
|
||||
initMediaListenable();
|
||||
initArtistLabelButton();
|
||||
initTranscodingInfo();
|
||||
|
||||
return view;
|
||||
}
|
||||
@@ -106,10 +106,19 @@ 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);
|
||||
playerTrackInfo = bind.getRoot().findViewById(R.id.player_info_track);
|
||||
}
|
||||
|
||||
private void initQuickActionView() {
|
||||
playerQuickActionView.setBackgroundColor(SurfaceColors.getColorForElevation(requireContext(), 8));
|
||||
|
||||
playerQuickActionView.setOnClickListener(view -> {
|
||||
PlayerBottomSheetFragment playerBottomSheetFragment = (PlayerBottomSheetFragment) requireActivity().getSupportFragmentManager().findFragmentByTag("PlayerBottomSheet");
|
||||
if (playerBottomSheetFragment != null) {
|
||||
playerBottomSheetFragment.goToQueuePage();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void initializeBrowser() {
|
||||
@@ -160,9 +169,7 @@ public class PlayerControllerFragment extends Fragment {
|
||||
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 +181,10 @@ public class PlayerControllerFragment extends Fragment {
|
||||
}
|
||||
}
|
||||
|
||||
String transcodingExtension = MusicUtil.getTranscodingFormatPreference();
|
||||
String transcodingBitrate = Integer.parseInt(MusicUtil.getBitratePreference()) != 0
|
||||
? Integer.parseInt(MusicUtil.getBitratePreference()) + "kbps"
|
||||
: "Original";
|
||||
|
||||
if (transcodingExtension.equals("raw") && transcodingBitrate.equals("Original")) {
|
||||
playerMediaTranscodingPriorityIcon.setVisibility(View.GONE);
|
||||
playerMediaTranscodingIcon.setVisibility(View.GONE);
|
||||
playerMediaTranscodedBitrate.setVisibility(View.GONE);
|
||||
playerMediaTranscodedExtension.setVisibility(View.GONE);
|
||||
} else {
|
||||
playerMediaTranscodingPriorityIcon.setVisibility(View.GONE);
|
||||
playerMediaTranscodingIcon.setVisibility(View.VISIBLE);
|
||||
playerMediaTranscodedBitrate.setVisibility(View.VISIBLE);
|
||||
playerMediaTranscodedExtension.setVisibility(View.VISIBLE);
|
||||
playerMediaTranscodedExtension.setText(transcodingExtension);
|
||||
playerMediaTranscodedBitrate.setText(transcodingBitrate);
|
||||
}
|
||||
|
||||
if (mediaMetadata.extras != null && mediaMetadata.extras.getString("uri", "").contains(Constants.DOWNLOAD_URI)) {
|
||||
playerMediaTranscodingPriorityIcon.setVisibility(View.GONE);
|
||||
playerMediaTranscodingIcon.setVisibility(View.GONE);
|
||||
playerMediaTranscodedBitrate.setVisibility(View.GONE);
|
||||
playerMediaTranscodedExtension.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (Preferences.isServerPrioritized() && mediaMetadata.extras != null && !mediaMetadata.extras.getString("uri", "").contains(Constants.DOWNLOAD_URI)) {
|
||||
playerMediaTranscodingPriorityIcon.setVisibility(View.VISIBLE);
|
||||
playerMediaTranscodingIcon.setVisibility(View.GONE);
|
||||
playerMediaTranscodedBitrate.setVisibility(View.GONE);
|
||||
playerMediaTranscodedExtension.setVisibility(View.GONE);
|
||||
}
|
||||
playerTrackInfo.setOnClickListener(view -> {
|
||||
TrackInfoDialog dialog = new TrackInfoDialog(mediaMetadata);
|
||||
dialog.show(activity.getSupportFragmentManager(), null);
|
||||
});
|
||||
}
|
||||
|
||||
private void setMediaControllerUI(MediaBrowser mediaBrowser) {
|
||||
@@ -305,13 +284,6 @@ public class PlayerControllerFragment extends Fragment {
|
||||
});
|
||||
}
|
||||
|
||||
private void initTranscodingInfo() {
|
||||
playerMediaTranscodingPriorityIcon.setOnLongClickListener(view -> {
|
||||
Toast.makeText(requireActivity(), R.string.settings_audio_transcode_priority_toast, Toast.LENGTH_SHORT).show();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void initPlaybackSpeedButton(MediaBrowser mediaBrowser) {
|
||||
playbackSpeedButton.setOnClickListener(view -> {
|
||||
float currentSpeed = Preferences.getPlaybackSpeed();
|
||||
|
||||
@@ -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,19 @@ public class PlayerCoverFragment extends Fragment {
|
||||
bind = null;
|
||||
}
|
||||
|
||||
private void initTapButtonHideTransition() {
|
||||
bind.nowPlayingTapButton.setVisibility(View.VISIBLE);
|
||||
|
||||
handler.removeCallbacksAndMessages(null);
|
||||
|
||||
final Runnable runnable = () -> bind.nowPlayingTapButton.setVisibility(View.GONE);
|
||||
handler.postDelayed(runnable, 10000);
|
||||
}
|
||||
|
||||
private void initOverlay() {
|
||||
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 +97,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 +182,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);
|
||||
}
|
||||
|
||||
@@ -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,17 @@ public class PlayerQueueFragment extends Fragment implements ClickCallback {
|
||||
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
|
||||
}
|
||||
|
||||
private void bindMediaController() {
|
||||
mediaBrowserListenableFuture.addListener(() -> {
|
||||
try {
|
||||
MediaBrowser mediaBrowser = mediaBrowserListenableFuture.get();
|
||||
initShuffleButton(mediaBrowser);
|
||||
} catch (Exception exception) {
|
||||
exception.printStackTrace();
|
||||
}
|
||||
}, MoreExecutors.directExecutor());
|
||||
}
|
||||
|
||||
private void setMediaBrowserListenableFuture() {
|
||||
playerSongQueueAdapter.setMediaBrowserListenableFuture(mediaBrowserListenableFuture);
|
||||
}
|
||||
@@ -151,6 +167,36 @@ 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 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());
|
||||
|
||||
@@ -162,33 +162,33 @@ public class PlaylistPageFragment extends Fragment implements ClickCallback {
|
||||
|
||||
private void initBackCover() {
|
||||
playlistPageViewModel.getPlaylistSongLiveList().observe(requireActivity(), songs -> {
|
||||
if (bind != null) {
|
||||
if (bind != null && songs != null && !songs.isEmpty()) {
|
||||
Collections.shuffle(songs);
|
||||
|
||||
// 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));
|
||||
|
||||
@@ -112,7 +112,7 @@ public class SearchFragment extends Fragment implements ClickCallback {
|
||||
bind.searchResultTracksRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
|
||||
bind.searchResultTracksRecyclerView.setHasFixedSize(true);
|
||||
|
||||
songHorizontalAdapter = new SongHorizontalAdapter(this, true);
|
||||
songHorizontalAdapter = new SongHorizontalAdapter(this, true, false);
|
||||
bind.searchResultTracksRecyclerView.setAdapter(songHorizontalAdapter);
|
||||
}
|
||||
|
||||
@@ -242,7 +242,6 @@ public class SearchFragment extends Fragment implements ClickCallback {
|
||||
}
|
||||
|
||||
private boolean isQueryValid(String query) {
|
||||
Log.d(TAG, "isQueryValid()");
|
||||
return !query.equals("") && query.trim().length() > 2;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@ import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.OptIn;
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
import androidx.core.os.LocaleListCompat;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.preference.ListPreference;
|
||||
@@ -21,12 +23,19 @@ import androidx.preference.PreferenceFragmentCompat;
|
||||
import com.cappielloantonio.tempo.BuildConfig;
|
||||
import com.cappielloantonio.tempo.R;
|
||||
import com.cappielloantonio.tempo.helper.ThemeHelper;
|
||||
import com.cappielloantonio.tempo.interfaces.DialogClickCallback;
|
||||
import com.cappielloantonio.tempo.interfaces.ScanCallback;
|
||||
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
||||
import com.cappielloantonio.tempo.ui.dialog.DeleteDownloadStorageDialog;
|
||||
import com.cappielloantonio.tempo.ui.dialog.DownloadStorageDialog;
|
||||
import com.cappielloantonio.tempo.ui.dialog.StarredSyncDialog;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
import com.cappielloantonio.tempo.util.UIUtil;
|
||||
import com.cappielloantonio.tempo.viewmodel.SettingViewModel;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
@OptIn(markerClass = UnstableApi.class)
|
||||
public class SettingsFragment extends PreferenceFragmentCompat {
|
||||
private static final String TAG = "SettingsFragment";
|
||||
@@ -72,39 +81,16 @@ public class SettingsFragment extends PreferenceFragmentCompat {
|
||||
super.onResume();
|
||||
|
||||
checkEqualizer();
|
||||
checkStorage();
|
||||
|
||||
findPreference("version").setSummary(BuildConfig.VERSION_NAME);
|
||||
setAppLanguage();
|
||||
setVersion();
|
||||
|
||||
findPreference("logout").setOnPreferenceClickListener(preference -> {
|
||||
activity.quit();
|
||||
return true;
|
||||
});
|
||||
|
||||
findPreference("scan_library").setOnPreferenceClickListener(preference -> {
|
||||
settingViewModel.launchScan(new ScanCallback() {
|
||||
@Override
|
||||
public void onError(Exception exception) {
|
||||
findPreference("scan_library").setSummary(exception.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(boolean isScanning, long count) {
|
||||
getScanStatus();
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
findPreference("sync_starred_tracks_for_offline_use").setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
if (newValue instanceof Boolean) {
|
||||
if ((Boolean) newValue) {
|
||||
StarredSyncDialog dialog = new StarredSyncDialog();
|
||||
dialog.show(activity.getSupportFragmentManager(), null);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
actionLogout();
|
||||
actionScan();
|
||||
actionSyncStarredTracks();
|
||||
actionChangeDownloadStorage();
|
||||
actionDeleteDownloadStorage();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -144,6 +130,110 @@ public class SettingsFragment extends PreferenceFragmentCompat {
|
||||
}
|
||||
}
|
||||
|
||||
private void checkStorage() {
|
||||
Preference storage = findPreference("download_storage");
|
||||
|
||||
if (storage == null) return;
|
||||
|
||||
try {
|
||||
if (requireContext().getExternalFilesDirs(null)[1] == null) {
|
||||
storage.setVisible(false);
|
||||
} else {
|
||||
storage.setSummary(Preferences.getDownloadStoragePreference() == 0 ? R.string.download_storage_internal_dialog_negative_button : R.string.download_storage_external_dialog_positive_button);
|
||||
}
|
||||
} catch (Exception exception) {
|
||||
storage.setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void setAppLanguage() {
|
||||
ListPreference localePref = (ListPreference) findPreference("language");
|
||||
|
||||
Map<String, String> locales = UIUtil.getLangPreferenceDropdownEntries(requireContext());
|
||||
|
||||
CharSequence[] entries = locales.keySet().toArray(new CharSequence[locales.size()]);
|
||||
CharSequence[] entryValues = locales.values().toArray(new CharSequence[locales.size()]);
|
||||
|
||||
localePref.setEntries(entries);
|
||||
localePref.setEntryValues(entryValues);
|
||||
|
||||
localePref.setDefaultValue(entryValues[0]);
|
||||
localePref.setSummary(Locale.forLanguageTag(localePref.getValue()).getDisplayLanguage());
|
||||
|
||||
localePref.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
LocaleListCompat appLocale = LocaleListCompat.forLanguageTags((String) newValue);
|
||||
AppCompatDelegate.setApplicationLocales(appLocale);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void setVersion() {
|
||||
findPreference("version").setSummary(BuildConfig.VERSION_NAME);
|
||||
}
|
||||
|
||||
private void actionLogout() {
|
||||
findPreference("logout").setOnPreferenceClickListener(preference -> {
|
||||
activity.quit();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void actionScan() {
|
||||
findPreference("scan_library").setOnPreferenceClickListener(preference -> {
|
||||
settingViewModel.launchScan(new ScanCallback() {
|
||||
@Override
|
||||
public void onError(Exception exception) {
|
||||
findPreference("scan_library").setSummary(exception.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(boolean isScanning, long count) {
|
||||
getScanStatus();
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void actionSyncStarredTracks() {
|
||||
findPreference("sync_starred_tracks_for_offline_use").setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
if (newValue instanceof Boolean) {
|
||||
if ((Boolean) newValue) {
|
||||
StarredSyncDialog dialog = new StarredSyncDialog();
|
||||
dialog.show(activity.getSupportFragmentManager(), null);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void actionChangeDownloadStorage() {
|
||||
findPreference("download_storage").setOnPreferenceClickListener(preference -> {
|
||||
DownloadStorageDialog dialog = new DownloadStorageDialog(new DialogClickCallback() {
|
||||
@Override
|
||||
public void onPositiveClick() {
|
||||
findPreference("download_storage").setSummary(R.string.download_storage_external_dialog_positive_button);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNegativeClick() {
|
||||
findPreference("download_storage").setSummary(R.string.download_storage_internal_dialog_negative_button);
|
||||
}
|
||||
});
|
||||
dialog.show(activity.getSupportFragmentManager(), null);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void actionDeleteDownloadStorage() {
|
||||
findPreference("delete_download_storage").setOnPreferenceClickListener(preference -> {
|
||||
DeleteDownloadStorageDialog dialog = new DeleteDownloadStorageDialog();
|
||||
dialog.show(activity.getSupportFragmentManager(), null);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void getScanStatus() {
|
||||
settingViewModel.getScanStatus(new ScanCallback() {
|
||||
@Override
|
||||
|
||||
@@ -166,7 +166,7 @@ public class SongListPageFragment extends Fragment implements ClickCallback {
|
||||
bind.songListRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
|
||||
bind.songListRecyclerView.setHasFixedSize(true);
|
||||
|
||||
songHorizontalAdapter = new SongHorizontalAdapter(this, true);
|
||||
songHorizontalAdapter = new SongHorizontalAdapter(this, true, false);
|
||||
bind.songListRecyclerView.setAdapter(songHorizontalAdapter);
|
||||
songListPageViewModel.getSongList().observe(getViewLifecycleOwner(), songs -> {
|
||||
isLoading = false;
|
||||
|
||||
@@ -79,7 +79,7 @@ public class AlbumBottomSheetDialog extends BottomSheetDialogFragment implements
|
||||
private void init(View view) {
|
||||
ImageView coverAlbum = view.findViewById(R.id.album_cover_image_view);
|
||||
CustomGlideRequest.Builder
|
||||
.from(requireContext(), albumBottomSheetViewModel.getAlbum().getCoverArtId())
|
||||
.from(requireContext(), albumBottomSheetViewModel.getAlbum().getCoverArtId(), CustomGlideRequest.ResourceType.Album)
|
||||
.build()
|
||||
.into(coverAlbum);
|
||||
|
||||
@@ -94,7 +94,6 @@ public class AlbumBottomSheetDialog extends BottomSheetDialogFragment implements
|
||||
favoriteToggle.setChecked(albumBottomSheetViewModel.getAlbum().getStarred() != null);
|
||||
favoriteToggle.setOnClickListener(v -> {
|
||||
albumBottomSheetViewModel.setFavorite();
|
||||
dismissBottomSheet();
|
||||
});
|
||||
|
||||
TextView playRadio = view.findViewById(R.id.play_radio_text_view);
|
||||
@@ -148,8 +147,6 @@ public class AlbumBottomSheetDialog extends BottomSheetDialogFragment implements
|
||||
}));
|
||||
|
||||
TextView downloadAll = view.findViewById(R.id.download_all_text_view);
|
||||
TextView removeAll = view.findViewById(R.id.remove_all_text_view);
|
||||
|
||||
albumBottomSheetViewModel.getAlbumTracks().observe(getViewLifecycleOwner(), songs -> {
|
||||
List<MediaItem> mediaItems = MappingUtil.mapDownloads(songs);
|
||||
List<Download> downloads = songs.stream().map(Download::new).collect(Collectors.toList());
|
||||
@@ -158,17 +155,21 @@ public class AlbumBottomSheetDialog extends BottomSheetDialogFragment implements
|
||||
DownloadUtil.getDownloadTracker(requireContext()).download(mediaItems, downloads);
|
||||
dismissBottomSheet();
|
||||
});
|
||||
|
||||
if (DownloadUtil.getDownloadTracker(requireContext()).areDownloaded(mediaItems)) {
|
||||
removeAll.setOnClickListener(v -> {
|
||||
DownloadUtil.getDownloadTracker(requireContext()).remove(mediaItems, downloads);
|
||||
dismissBottomSheet();
|
||||
});
|
||||
} else {
|
||||
removeAll.setVisibility(View.GONE);
|
||||
}
|
||||
});
|
||||
|
||||
TextView removeAll = view.findViewById(R.id.remove_all_text_view);
|
||||
albumBottomSheetViewModel.getAlbumTracks().observe(getViewLifecycleOwner(), songs -> {
|
||||
List<MediaItem> mediaItems = MappingUtil.mapDownloads(songs);
|
||||
List<Download> downloads = songs.stream().map(Download::new).collect(Collectors.toList());
|
||||
|
||||
removeAll.setOnClickListener(v -> {
|
||||
DownloadUtil.getDownloadTracker(requireContext()).remove(mediaItems, downloads);
|
||||
dismissBottomSheet();
|
||||
});
|
||||
});
|
||||
|
||||
initDownloadUI(removeAll);
|
||||
|
||||
TextView goToArtist = view.findViewById(R.id.go_to_artist_text_view);
|
||||
goToArtist.setOnClickListener(v -> albumBottomSheetViewModel.getArtist().observe(getViewLifecycleOwner(), artist -> {
|
||||
if (artist != null) {
|
||||
@@ -192,6 +193,16 @@ public class AlbumBottomSheetDialog extends BottomSheetDialogFragment implements
|
||||
dismiss();
|
||||
}
|
||||
|
||||
private void initDownloadUI(TextView removeAll) {
|
||||
albumBottomSheetViewModel.getAlbumTracks().observe(getViewLifecycleOwner(), songs -> {
|
||||
List<MediaItem> mediaItems = MappingUtil.mapDownloads(songs);
|
||||
|
||||
if (DownloadUtil.getDownloadTracker(requireContext()).areDownloaded(mediaItems)) {
|
||||
removeAll.setVisibility(View.VISIBLE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void initializeMediaBrowser() {
|
||||
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ public class ArtistBottomSheetDialog extends BottomSheetDialogFragment implement
|
||||
private void init(View view) {
|
||||
ImageView coverArtist = view.findViewById(R.id.artist_cover_image_view);
|
||||
CustomGlideRequest.Builder
|
||||
.from(requireContext(), artistBottomSheetViewModel.getArtist().getCoverArtId())
|
||||
.from(requireContext(), artistBottomSheetViewModel.getArtist().getCoverArtId(), CustomGlideRequest.ResourceType.Artist)
|
||||
.build()
|
||||
.into(coverArtist);
|
||||
|
||||
@@ -82,7 +82,6 @@ public class ArtistBottomSheetDialog extends BottomSheetDialogFragment implement
|
||||
favoriteToggle.setChecked(artistBottomSheetViewModel.getArtist().getStarred() != null);
|
||||
favoriteToggle.setOnClickListener(v -> {
|
||||
artistBottomSheetViewModel.setFavorite();
|
||||
dismissBottomSheet();
|
||||
});
|
||||
|
||||
TextView playRadio = view.findViewById(R.id.play_radio_text_view);
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
package com.cappielloantonio.tempo.ui.fragment.bottomsheetdialog;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media3.common.MediaItem;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.session.MediaBrowser;
|
||||
import androidx.media3.session.SessionToken;
|
||||
|
||||
import com.cappielloantonio.tempo.R;
|
||||
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
||||
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.util.Constants;
|
||||
import com.cappielloantonio.tempo.util.DownloadUtil;
|
||||
import com.cappielloantonio.tempo.util.MappingUtil;
|
||||
import com.cappielloantonio.tempo.util.MusicUtil;
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@UnstableApi
|
||||
public class DownloadedBottomSheetDialog extends BottomSheetDialogFragment implements View.OnClickListener {
|
||||
private List<Child> songs;
|
||||
private String groupTitle;
|
||||
private String groupSubtitle;
|
||||
|
||||
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.bottom_sheet_downloaded_dialog, container, false);
|
||||
|
||||
songs = this.requireArguments().getParcelableArrayList(Constants.DOWNLOAD_GROUP);
|
||||
groupTitle = this.requireArguments().getString(Constants.DOWNLOAD_GROUP_TITLE);
|
||||
groupSubtitle = this.requireArguments().getString(Constants.DOWNLOAD_GROUP_SUBTITLE);
|
||||
|
||||
initUI(view);
|
||||
init(view);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
|
||||
initializeMediaBrowser();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
releaseMediaBrowser();
|
||||
super.onStop();
|
||||
}
|
||||
|
||||
private void initUI(View view) {
|
||||
TextView playRandom = view.findViewById(R.id.play_random_text_view);
|
||||
playRandom.setVisibility(songs.size() > 1 ? View.VISIBLE : View.GONE);
|
||||
|
||||
TextView remove = view.findViewById(R.id.remove_all_text_view);
|
||||
remove.setText(songs.size() > 1 ? getText(R.string.downloaded_bottom_sheet_remove_all) : getText(R.string.downloaded_bottom_sheet_remove));
|
||||
}
|
||||
|
||||
private void init(View view) {
|
||||
ImageView coverAlbum = view.findViewById(R.id.group_cover_image_view);
|
||||
CustomGlideRequest.Builder.from(requireContext(), songs.get(new Random().nextInt(songs.size())).getCoverArtId(), CustomGlideRequest.ResourceType.Unknown).build().into(coverAlbum);
|
||||
|
||||
TextView groupTitleView = view.findViewById(R.id.group_title_text_view);
|
||||
groupTitleView.setText(MusicUtil.getReadableString(this.groupTitle));
|
||||
groupTitleView.setSelected(true);
|
||||
|
||||
TextView groupSubtitleView = view.findViewById(R.id.group_subtitle_text_view);
|
||||
groupSubtitleView.setText(MusicUtil.getReadableString(this.groupSubtitle));
|
||||
groupSubtitleView.setSelected(true);
|
||||
|
||||
TextView playRandom = view.findViewById(R.id.play_random_text_view);
|
||||
playRandom.setOnClickListener(v -> {
|
||||
Collections.shuffle(songs);
|
||||
|
||||
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
|
||||
((MainActivity) requireActivity()).setBottomSheetInPeek(true);
|
||||
|
||||
dismissBottomSheet();
|
||||
});
|
||||
|
||||
TextView playNext = view.findViewById(R.id.play_next_text_view);
|
||||
playNext.setOnClickListener(v -> {
|
||||
MediaManager.enqueue(mediaBrowserListenableFuture, songs, true);
|
||||
((MainActivity) requireActivity()).setBottomSheetInPeek(true);
|
||||
|
||||
dismissBottomSheet();
|
||||
});
|
||||
|
||||
TextView addToQueue = view.findViewById(R.id.add_to_queue_text_view);
|
||||
addToQueue.setOnClickListener(v -> {
|
||||
MediaManager.enqueue(mediaBrowserListenableFuture, songs, false);
|
||||
((MainActivity) requireActivity()).setBottomSheetInPeek(true);
|
||||
|
||||
dismissBottomSheet();
|
||||
});
|
||||
|
||||
TextView removeAll = view.findViewById(R.id.remove_all_text_view);
|
||||
removeAll.setOnClickListener(v -> {
|
||||
List<MediaItem> mediaItems = MappingUtil.mapDownloads(songs);
|
||||
List<Download> downloads = songs.stream().map(Download::new).collect(Collectors.toList());
|
||||
|
||||
DownloadUtil.getDownloadTracker(requireContext()).remove(mediaItems, downloads);
|
||||
|
||||
dismissBottomSheet();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
dismissBottomSheet();
|
||||
}
|
||||
|
||||
private void dismissBottomSheet() {
|
||||
dismiss();
|
||||
}
|
||||
|
||||
private void initializeMediaBrowser() {
|
||||
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
|
||||
}
|
||||
|
||||
private void releaseMediaBrowser() {
|
||||
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
|
||||
}
|
||||
}
|
||||
@@ -63,7 +63,7 @@ public class PodcastChannelBottomSheetDialog extends BottomSheetDialogFragment i
|
||||
ImageView coverPodcast = view.findViewById(R.id.podcast_cover_image_view);
|
||||
|
||||
CustomGlideRequest.Builder
|
||||
.from(requireContext(), podcastChannelBottomSheetViewModel.getPodcastChannel().getCoverArtId())
|
||||
.from(requireContext(), podcastChannelBottomSheetViewModel.getPodcastChannel().getCoverArtId(), CustomGlideRequest.ResourceType.Podcast)
|
||||
.build()
|
||||
.into(coverPodcast);
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ public class PodcastEpisodeBottomSheetDialog extends BottomSheetDialogFragment i
|
||||
ImageView coverPodcast = view.findViewById(R.id.podcast_cover_image_view);
|
||||
|
||||
CustomGlideRequest.Builder
|
||||
.from(requireContext(), podcastEpisodeBottomSheetViewModel.getPodcastEpisode().getCoverArtId())
|
||||
.from(requireContext(), podcastEpisodeBottomSheetViewModel.getPodcastEpisode().getCoverArtId(), CustomGlideRequest.ResourceType.Podcast)
|
||||
.build()
|
||||
.into(coverPodcast);
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ public class SongBottomSheetDialog extends BottomSheetDialogFragment implements
|
||||
private void init(View view) {
|
||||
ImageView coverSong = view.findViewById(R.id.song_cover_image_view);
|
||||
CustomGlideRequest.Builder
|
||||
.from(requireContext(), songBottomSheetViewModel.getSong().getCoverArtId())
|
||||
.from(requireContext(), songBottomSheetViewModel.getSong().getCoverArtId(), CustomGlideRequest.ResourceType.Song)
|
||||
.build()
|
||||
.into(coverSong);
|
||||
|
||||
@@ -90,7 +90,6 @@ public class SongBottomSheetDialog extends BottomSheetDialogFragment implements
|
||||
favoriteToggle.setChecked(songBottomSheetViewModel.getSong().getStarred() != null);
|
||||
favoriteToggle.setOnClickListener(v -> {
|
||||
songBottomSheetViewModel.setFavorite(requireContext());
|
||||
dismissBottomSheet();
|
||||
});
|
||||
favoriteToggle.setOnLongClickListener(v -> {
|
||||
Bundle bundle = new Bundle();
|
||||
@@ -216,7 +215,6 @@ public class SongBottomSheetDialog extends BottomSheetDialogFragment implements
|
||||
|
||||
private void initDownloadUI(TextView download, TextView remove) {
|
||||
if (DownloadUtil.getDownloadTracker(requireContext()).isDownloaded(song.getId())) {
|
||||
download.setVisibility(View.GONE);
|
||||
remove.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
download.setVisibility(View.VISIBLE);
|
||||
|
||||
@@ -76,6 +76,16 @@ object Constants {
|
||||
|
||||
const val DOWNLOAD_URI = "rest/download"
|
||||
|
||||
const val DOWNLOAD_TYPE_TRACK = "download_type_track"
|
||||
const val DOWNLOAD_TYPE_ALBUM = "download_type_album"
|
||||
const val DOWNLOAD_TYPE_ARTIST = "download_type_artist"
|
||||
const val DOWNLOAD_TYPE_GENRE = "download_type_genre"
|
||||
const val DOWNLOAD_TYPE_YEAR = "download_type_year"
|
||||
|
||||
const val DOWNLOAD_GROUP = "download_group"
|
||||
const val DOWNLOAD_GROUP_TITLE = "download_group_title"
|
||||
const val DOWNLOAD_GROUP_SUBTITLE = "download_group_subtitle"
|
||||
|
||||
const val PLAYABLE_MEDIA_LIMIT = 100
|
||||
const val PRE_PLAYABLE_MEDIA = 15
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
package com.cappielloantonio.tempo.util;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.database.DatabaseProvider;
|
||||
import androidx.media3.database.StandaloneDatabaseProvider;
|
||||
@@ -23,12 +25,15 @@ import java.io.File;
|
||||
import java.net.CookieHandler;
|
||||
import java.net.CookieManager;
|
||||
import java.net.CookiePolicy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
@UnstableApi
|
||||
public final class DownloadUtil {
|
||||
|
||||
public static final String DOWNLOAD_NOTIFICATION_CHANNEL_ID = "download_channel";
|
||||
public static final String DOWNLOAD_NOTIFICATION_SUCCESSFUL_GROUP = "com.cappielloantonio.tempo.SuccessfulDownload";
|
||||
public static final String DOWNLOAD_NOTIFICATION_FAILED_GROUP = "com.cappielloantonio.tempo.FailedDownload";
|
||||
|
||||
private static final String DOWNLOAD_CONTENT_DIRECTORY = "downloads";
|
||||
|
||||
@@ -127,9 +132,19 @@ public final class DownloadUtil {
|
||||
|
||||
private static synchronized File getDownloadDirectory(Context context) {
|
||||
if (downloadDirectory == null) {
|
||||
downloadDirectory = context.getExternalFilesDir(null);
|
||||
if (downloadDirectory == null) {
|
||||
downloadDirectory = context.getFilesDir();
|
||||
if (Preferences.getDownloadStoragePreference() == 0) {
|
||||
downloadDirectory = context.getExternalFilesDirs(null)[0];
|
||||
if (downloadDirectory == null) {
|
||||
downloadDirectory = context.getFilesDir();
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
downloadDirectory = context.getExternalFilesDirs(null)[1];
|
||||
} catch (Exception exception) {
|
||||
downloadDirectory = context.getExternalFilesDirs(null)[0];
|
||||
Preferences.setDownloadStoragePreference(0);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,4 +158,41 @@ public final class DownloadUtil {
|
||||
.setCacheWriteDataSinkFactory(null)
|
||||
.setFlags(CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR);
|
||||
}
|
||||
|
||||
public static synchronized void eraseDownloadFolder(Context context) {
|
||||
File directory = getDownloadDirectory(context);
|
||||
|
||||
ArrayList<File> files = listFiles(directory, new ArrayList<>());
|
||||
|
||||
for (File file : files) {
|
||||
file.delete();
|
||||
}
|
||||
}
|
||||
|
||||
private static synchronized ArrayList<File> listFiles(File directory, ArrayList<File> files) {
|
||||
if (directory.isDirectory()) {
|
||||
File[] list = directory.listFiles();
|
||||
|
||||
if (list != null) {
|
||||
for (File file : list) {
|
||||
if (file.isFile() && file.getName().toLowerCase().endsWith(".exo")) {
|
||||
files.add(file);
|
||||
} else if (file.isDirectory()) {
|
||||
listFiles(file, files);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
public static Notification buildGroupSummaryNotification(Context context, String channelId, String groupId, int icon, String title) {
|
||||
return new NotificationCompat.Builder(context, channelId)
|
||||
.setContentTitle(title)
|
||||
.setSmallIcon(icon)
|
||||
.setGroup(groupId)
|
||||
.setGroupSummary(true)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,9 @@ import androidx.media3.common.MimeTypes;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
|
||||
import com.cappielloantonio.tempo.App;
|
||||
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
||||
import com.cappielloantonio.tempo.model.Download;
|
||||
import com.cappielloantonio.tempo.repository.DownloadRepository;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||
import com.cappielloantonio.tempo.subsonic.models.InternetRadioStation;
|
||||
import com.cappielloantonio.tempo.subsonic.models.PodcastEpisode;
|
||||
@@ -31,6 +34,7 @@ public class MappingUtil {
|
||||
|
||||
public static MediaItem mapMediaItem(Child media) {
|
||||
Uri uri = getUri(media);
|
||||
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(media.getCoverArtId(), Preferences.getImageSize()));
|
||||
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString("id", media.getId());
|
||||
@@ -54,8 +58,8 @@ public class MappingUtil {
|
||||
bundle.putBoolean("isVideo", media.isVideo());
|
||||
bundle.putInt("userRating", media.getUserRating() != null ? media.getUserRating() : 0);
|
||||
bundle.putDouble("averageRating", media.getAverageRating() != null ? media.getAverageRating() : 0);
|
||||
bundle.putLong("playCount", media.getPlayCount() != null ? media.getTrack() : 0);
|
||||
bundle.putInt("discNumber", media.getDiscNumber() != null ? media.getTrack() : 0);
|
||||
bundle.putLong("playCount", media.getPlayCount() != null ? media.getPlayCount() : 0);
|
||||
bundle.putInt("discNumber", media.getDiscNumber() != null ? media.getDiscNumber() : 0);
|
||||
bundle.putLong("created", media.getCreated() != null ? media.getCreated().getTime() : 0);
|
||||
bundle.putLong("starred", media.getStarred() != null ? media.getStarred().getTime() : 0);
|
||||
bundle.putString("albumId", media.getAlbumId());
|
||||
@@ -71,11 +75,12 @@ public class MappingUtil {
|
||||
.setMediaMetadata(
|
||||
new MediaMetadata.Builder()
|
||||
.setTitle(MusicUtil.getReadableString(media.getTitle()))
|
||||
.setTrackNumber(media.getTrack())
|
||||
.setDiscNumber(media.getDiscNumber())
|
||||
.setReleaseYear(media.getYear())
|
||||
.setTrackNumber(media.getTrack() != null ? media.getTrack() : 0)
|
||||
.setDiscNumber(media.getDiscNumber() != null ? media.getDiscNumber() : 0)
|
||||
.setReleaseYear(media.getYear() != null ? media.getYear() : 0)
|
||||
.setAlbumTitle(MusicUtil.getReadableString(media.getAlbum()))
|
||||
.setArtist(MusicUtil.getReadableString(media.getArtist()))
|
||||
.setArtworkUri(artworkUri)
|
||||
.setExtras(bundle)
|
||||
.build()
|
||||
)
|
||||
@@ -106,20 +111,20 @@ public class MappingUtil {
|
||||
.setMediaMetadata(
|
||||
new MediaMetadata.Builder()
|
||||
.setTitle(MusicUtil.getReadableString(media.getTitle()))
|
||||
.setTrackNumber(media.getTrack())
|
||||
.setDiscNumber(media.getDiscNumber())
|
||||
.setReleaseYear(media.getYear())
|
||||
.setTrackNumber(media.getTrack() != null ? media.getTrack() : 0)
|
||||
.setDiscNumber(media.getDiscNumber() != null ? media.getDiscNumber() : 0)
|
||||
.setReleaseYear(media.getYear() != null ? media.getYear() : 0)
|
||||
.setAlbumTitle(MusicUtil.getReadableString(media.getAlbum()))
|
||||
.setArtist(MusicUtil.getReadableString(media.getArtist()))
|
||||
.build()
|
||||
)
|
||||
.setRequestMetadata(
|
||||
new MediaItem.RequestMetadata.Builder()
|
||||
.setMediaUri(MusicUtil.getDownloadUri(media.getId()))
|
||||
.setMediaUri(Preferences.preferTranscodedDownload() ? MusicUtil.getTranscodedDownloadUri(media.getId()) : MusicUtil.getDownloadUri(media.getId()))
|
||||
.build()
|
||||
)
|
||||
.setMimeType(MimeTypes.BASE_TYPE_AUDIO)
|
||||
.setUri(MusicUtil.getDownloadUri(media.getId()))
|
||||
.setUri(Preferences.preferTranscodedDownload() ? MusicUtil.getTranscodedDownloadUri(media.getId()) : MusicUtil.getDownloadUri(media.getId()))
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -155,6 +160,7 @@ public class MappingUtil {
|
||||
|
||||
public static MediaItem mapMediaItem(PodcastEpisode podcastEpisode) {
|
||||
Uri uri = getUri(podcastEpisode);
|
||||
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(podcastEpisode.getCoverArtId(), Preferences.getImageSize()));
|
||||
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString("id", podcastEpisode.getId());
|
||||
@@ -178,8 +184,8 @@ public class MappingUtil {
|
||||
bundle.putBoolean("isVideo", podcastEpisode.isVideo());
|
||||
bundle.putInt("userRating", podcastEpisode.getUserRating() != null ? podcastEpisode.getUserRating() : 0);
|
||||
bundle.putDouble("averageRating", podcastEpisode.getAverageRating() != null ? podcastEpisode.getAverageRating() : 0);
|
||||
bundle.putLong("playCount", podcastEpisode.getPlayCount() != null ? podcastEpisode.getTrack() : 0);
|
||||
bundle.putInt("discNumber", podcastEpisode.getDiscNumber() != null ? podcastEpisode.getTrack() : 0);
|
||||
bundle.putLong("playCount", podcastEpisode.getPlayCount() != null ? podcastEpisode.getPlayCount() : 0);
|
||||
bundle.putInt("discNumber", podcastEpisode.getDiscNumber() != null ? podcastEpisode.getDiscNumber() : 0);
|
||||
bundle.putLong("created", podcastEpisode.getCreated() != null ? podcastEpisode.getCreated().getTime() : 0);
|
||||
bundle.putLong("starred", podcastEpisode.getStarred() != null ? podcastEpisode.getStarred().getTime() : 0);
|
||||
bundle.putString("albumId", podcastEpisode.getAlbumId());
|
||||
@@ -190,16 +196,17 @@ public class MappingUtil {
|
||||
bundle.putInt("originalHeight", podcastEpisode.getOriginalHeight() != null ? podcastEpisode.getOriginalHeight() : 0);
|
||||
bundle.putString("uri", uri.toString());
|
||||
|
||||
return new MediaItem.Builder()
|
||||
MediaItem item = new MediaItem.Builder()
|
||||
.setMediaId(podcastEpisode.getId())
|
||||
.setMediaMetadata(
|
||||
new MediaMetadata.Builder()
|
||||
.setTitle(MusicUtil.getReadableString(podcastEpisode.getTitle()))
|
||||
.setTrackNumber(podcastEpisode.getTrack())
|
||||
.setDiscNumber(podcastEpisode.getDiscNumber())
|
||||
.setReleaseYear(podcastEpisode.getYear())
|
||||
.setTrackNumber(podcastEpisode.getTrack() != null ? podcastEpisode.getTrack() : 0)
|
||||
.setDiscNumber(podcastEpisode.getDiscNumber() != null ? podcastEpisode.getDiscNumber() : 0)
|
||||
.setReleaseYear(podcastEpisode.getYear() != null ? podcastEpisode.getYear() : 0)
|
||||
.setAlbumTitle(MusicUtil.getReadableString(podcastEpisode.getAlbum()))
|
||||
.setArtist(MusicUtil.getReadableString(podcastEpisode.getArtist()))
|
||||
.setArtworkUri(artworkUri)
|
||||
.setExtras(bundle)
|
||||
.build()
|
||||
)
|
||||
@@ -209,20 +216,33 @@ public class MappingUtil {
|
||||
.setExtras(bundle)
|
||||
.build()
|
||||
)
|
||||
/* .setClippingConfiguration(
|
||||
new MediaItem.ClippingConfiguration.Builder()
|
||||
.setStartPositionMs(0)
|
||||
.setEndPositionMs(podcastEpisode.getDuration() * 1000)
|
||||
.build()
|
||||
) */
|
||||
.setMimeType(MimeTypes.BASE_TYPE_AUDIO)
|
||||
.setUri(uri)
|
||||
.build();
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
private static Uri getUri(Child media) {
|
||||
return DownloadUtil.getDownloadTracker(App.getContext()).isDownloaded(media.getId())
|
||||
? MusicUtil.getDownloadUri(media.getId())
|
||||
? getDownloadUri(media.getId())
|
||||
: MusicUtil.getStreamUri(media.getId());
|
||||
}
|
||||
|
||||
private static Uri getUri(PodcastEpisode podcastEpisode) {
|
||||
return DownloadUtil.getDownloadTracker(App.getContext()).isDownloaded(podcastEpisode.getId())
|
||||
? MusicUtil.getDownloadUri(podcastEpisode.getId())
|
||||
: MusicUtil.getStreamUri(podcastEpisode.getId());
|
||||
return DownloadUtil.getDownloadTracker(App.getContext()).isDownloaded(podcastEpisode.getStreamId())
|
||||
? getDownloadUri(podcastEpisode.getStreamId())
|
||||
: MusicUtil.getStreamUri(podcastEpisode.getStreamId());
|
||||
}
|
||||
|
||||
private static Uri getDownloadUri(String id) {
|
||||
Download download = new DownloadRepository().getDownload(id);
|
||||
return download != null && !download.getDownloadUri().isEmpty() ? Uri.parse(download.getDownloadUri()) : MusicUtil.getDownloadUri(id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,13 @@ import android.text.Html;
|
||||
import android.util.Log;
|
||||
|
||||
import com.cappielloantonio.tempo.App;
|
||||
import com.cappielloantonio.tempo.R;
|
||||
import com.cappielloantonio.tempo.model.Download;
|
||||
import com.cappielloantonio.tempo.repository.DownloadRepository;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||
|
||||
import java.text.CharacterIterator;
|
||||
import java.text.StringCharacterIterator;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
@@ -29,7 +34,7 @@ public class MusicUtil {
|
||||
uri.append("stream");
|
||||
|
||||
if (params.containsKey("u") && params.get("u") != null)
|
||||
uri.append("?u=").append(params.get("u"));
|
||||
uri.append("?u=").append(Util.encode(params.get("u")));
|
||||
if (params.containsKey("p") && params.get("p") != null)
|
||||
uri.append("&p=").append(params.get("p"));
|
||||
if (params.containsKey("s") && params.get("s") != null)
|
||||
@@ -54,15 +59,49 @@ public class MusicUtil {
|
||||
}
|
||||
|
||||
public static Uri getDownloadUri(String id) {
|
||||
StringBuilder uri = new StringBuilder();
|
||||
|
||||
Download download = new DownloadRepository().getDownload(id);
|
||||
|
||||
if (download == null || download.getDownloadUri().isEmpty()) {
|
||||
Map<String, String> params = App.getSubsonicClientInstance(false).getParams();
|
||||
|
||||
uri.append(App.getSubsonicClientInstance(false).getUrl());
|
||||
uri.append("download");
|
||||
|
||||
if (params.containsKey("u") && params.get("u") != null)
|
||||
uri.append("?u=").append(Util.encode(params.get("u")));
|
||||
if (params.containsKey("p") && params.get("p") != null)
|
||||
uri.append("&p=").append(params.get("p"));
|
||||
if (params.containsKey("s") && params.get("s") != null)
|
||||
uri.append("&s=").append(params.get("s"));
|
||||
if (params.containsKey("t") && params.get("t") != null)
|
||||
uri.append("&t=").append(params.get("t"));
|
||||
if (params.containsKey("v") && params.get("v") != null)
|
||||
uri.append("&v=").append(params.get("v"));
|
||||
if (params.containsKey("c") && params.get("c") != null)
|
||||
uri.append("&c=").append(params.get("c"));
|
||||
|
||||
uri.append("&id=").append(id);
|
||||
} else {
|
||||
uri.append(download.getDownloadUri());
|
||||
}
|
||||
|
||||
Log.d(TAG, "getDownloadUri: " + uri);
|
||||
|
||||
return Uri.parse(uri.toString());
|
||||
}
|
||||
|
||||
public static Uri getTranscodedDownloadUri(String id) {
|
||||
Map<String, String> params = App.getSubsonicClientInstance(false).getParams();
|
||||
|
||||
StringBuilder uri = new StringBuilder();
|
||||
|
||||
uri.append(App.getSubsonicClientInstance(false).getUrl());
|
||||
uri.append("download");
|
||||
uri.append("stream");
|
||||
|
||||
if (params.containsKey("u") && params.get("u") != null)
|
||||
uri.append("?u=").append(params.get("u"));
|
||||
uri.append("?u=").append(Util.encode(params.get("u")));
|
||||
if (params.containsKey("p") && params.get("p") != null)
|
||||
uri.append("&p=").append(params.get("p"));
|
||||
if (params.containsKey("s") && params.get("s") != null)
|
||||
@@ -74,13 +113,19 @@ public class MusicUtil {
|
||||
if (params.containsKey("c") && params.get("c") != null)
|
||||
uri.append("&c=").append(params.get("c"));
|
||||
|
||||
if (!Preferences.isServerPrioritizedInTranscodedDownload())
|
||||
uri.append("&maxBitRate=").append(getBitratePreferenceForDownload());
|
||||
if (!Preferences.isServerPrioritizedInTranscodedDownload())
|
||||
uri.append("&format=").append(getTranscodingFormatPreferenceForDownload());
|
||||
|
||||
uri.append("&id=").append(id);
|
||||
|
||||
Log.d(TAG, "getDownloadUri: " + uri);
|
||||
Log.d(TAG, "getTranscodedDownloadUri: " + uri);
|
||||
|
||||
return Uri.parse(uri.toString());
|
||||
}
|
||||
|
||||
|
||||
public static String getReadableDurationString(long duration, boolean millis) {
|
||||
long minutes;
|
||||
long seconds;
|
||||
@@ -122,6 +167,14 @@ public class MusicUtil {
|
||||
return "";
|
||||
}
|
||||
|
||||
public static String getReadableTrackNumber(Context context, Integer trackNumber) {
|
||||
if (trackNumber != null) {
|
||||
return String.valueOf(trackNumber);
|
||||
}
|
||||
|
||||
return context.getString(R.string.label_placeholder);
|
||||
}
|
||||
|
||||
public static String forceReadableString(String string) {
|
||||
if (string != null) {
|
||||
return getReadableString(string)
|
||||
@@ -160,6 +213,27 @@ public class MusicUtil {
|
||||
return readableStrings;
|
||||
}
|
||||
|
||||
public static String getReadableByteCount(long bytes) {
|
||||
long absB = bytes == Long.MIN_VALUE ? Long.MAX_VALUE : Math.abs(bytes);
|
||||
|
||||
if (absB < 1024) {
|
||||
return bytes + " B";
|
||||
}
|
||||
|
||||
long value = absB;
|
||||
|
||||
CharacterIterator ci = new StringCharacterIterator("KMGTPE");
|
||||
|
||||
for (int i = 40; i >= 0 && absB > 0xfffccccccccccccL >> i; i -= 10) {
|
||||
value >>= 10;
|
||||
ci.next();
|
||||
}
|
||||
|
||||
value *= Long.signum(bytes);
|
||||
|
||||
return String.format("%.1f %ciB", value / 1024.0, ci.current());
|
||||
}
|
||||
|
||||
public static String passwordHexEncoding(String plainPassword) {
|
||||
return "enc:" + plainPassword.chars().mapToObj(Integer::toHexString).collect(Collectors.joining());
|
||||
}
|
||||
@@ -196,6 +270,19 @@ public class MusicUtil {
|
||||
}
|
||||
}
|
||||
|
||||
public static String getBitratePreferenceForDownload() {
|
||||
String audioTranscodeFormat = getTranscodingFormatPreferenceForDownload();
|
||||
|
||||
if (audioTranscodeFormat.equals("raw"))
|
||||
return "0";
|
||||
|
||||
return Preferences.getBitrateTranscodedDownload();
|
||||
}
|
||||
|
||||
public static String getTranscodingFormatPreferenceForDownload() {
|
||||
return Preferences.getAudioTranscodeFormatTranscodedDownload();
|
||||
}
|
||||
|
||||
public static List<Child> limitPlayableMedia(List<Child> toLimit, int position) {
|
||||
if (!toLimit.isEmpty() && toLimit.size() > Constants.PLAYABLE_MEDIA_LIMIT) {
|
||||
int from = position < Constants.PRE_PLAYABLE_MEDIA ? 0 : position - Constants.PRE_PLAYABLE_MEDIA;
|
||||
@@ -207,8 +294,12 @@ public class MusicUtil {
|
||||
return toLimit;
|
||||
}
|
||||
|
||||
public static int getPlayableMediaPosition(int initialPosition) {
|
||||
return initialPosition > Constants.PLAYABLE_MEDIA_LIMIT ? Constants.PRE_PLAYABLE_MEDIA : initialPosition;
|
||||
public static int getPlayableMediaPosition(List<Child> toLimit, int position) {
|
||||
if (!toLimit.isEmpty() && toLimit.size() > Constants.PLAYABLE_MEDIA_LIMIT) {
|
||||
return Math.min(position, Constants.PRE_PLAYABLE_MEDIA);
|
||||
}
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
private static ConnectivityManager getConnectivityManager() {
|
||||
|
||||
@@ -33,6 +33,12 @@ object Preferences {
|
||||
private const val MUSIC_DIRECTORY_SECTION_VISIBILITY = "music_directory_section_visibility"
|
||||
private const val REPLAY_GAIN_MODE = "replay_gain_mode"
|
||||
private const val AUDIO_TRANSCODE_PRIORITY = "audio_transcode_priority"
|
||||
private const val DOWNLOAD_STORAGE = "download_storage"
|
||||
private const val DEFAULT_DOWNLOAD_VIEW_TYPE = "default_download_view_type"
|
||||
private const val AUDIO_TRANSCODE_DOWNLOAD = "audio_transcode_download"
|
||||
private const val AUDIO_TRANSCODE_DOWNLOAD_PRIORITY = "audio_transcode_download_priority"
|
||||
private const val MAX_BITRATE_DOWNLOAD = "max_bitrate_download"
|
||||
private const val AUDIO_TRANSCODE_FORMAT_DOWNLOAD = "audio_transcode_format_download"
|
||||
|
||||
@JvmStatic
|
||||
fun getServer(): String? {
|
||||
@@ -258,4 +264,53 @@ object Preferences {
|
||||
fun isServerPrioritized(): Boolean {
|
||||
return App.getInstance().preferences.getBoolean(AUDIO_TRANSCODE_PRIORITY, false)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getDownloadStoragePreference(): Int {
|
||||
return App.getInstance().preferences.getString(DOWNLOAD_STORAGE, "0")!!.toInt()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun setDownloadStoragePreference(storagePreference: Int) {
|
||||
return App.getInstance().preferences.edit().putString(
|
||||
DOWNLOAD_STORAGE,
|
||||
storagePreference.toString()
|
||||
).apply()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getDefaultDownloadViewType(): String {
|
||||
return App.getInstance().preferences.getString(
|
||||
DEFAULT_DOWNLOAD_VIEW_TYPE,
|
||||
Constants.DOWNLOAD_TYPE_TRACK
|
||||
)!!
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun setDefaultDownloadViewType(viewType: String) {
|
||||
return App.getInstance().preferences.edit().putString(
|
||||
DEFAULT_DOWNLOAD_VIEW_TYPE,
|
||||
viewType
|
||||
).apply()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun preferTranscodedDownload(): Boolean {
|
||||
return App.getInstance().preferences.getBoolean(AUDIO_TRANSCODE_DOWNLOAD, false)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun isServerPrioritizedInTranscodedDownload(): Boolean {
|
||||
return App.getInstance().preferences.getBoolean(AUDIO_TRANSCODE_DOWNLOAD_PRIORITY, false)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getBitrateTranscodedDownload(): String {
|
||||
return App.getInstance().preferences.getString(MAX_BITRATE_DOWNLOAD, "0")!!
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getAudioTranscodeFormatTranscodedDownload(): String {
|
||||
return App.getInstance().preferences.getString(AUDIO_TRANSCODE_FORMAT_DOWNLOAD, "raw")!!
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.cappielloantonio.tempo.util;
|
||||
|
||||
import androidx.annotation.OptIn;
|
||||
import androidx.media3.common.MediaItem;
|
||||
import androidx.media3.common.Metadata;
|
||||
import androidx.media3.common.Tracks;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
@@ -61,6 +62,9 @@ public class ReplayGainUtil {
|
||||
}
|
||||
}
|
||||
|
||||
if (gains.size() == 0) gains.add(0, new ReplayGain());
|
||||
if (gains.size() == 1) gains.add(1, new ReplayGain());
|
||||
|
||||
return gains;
|
||||
}
|
||||
|
||||
@@ -105,13 +109,67 @@ public class ReplayGainUtil {
|
||||
}
|
||||
|
||||
private static void applyReplayGain(ExoPlayer player, List<ReplayGain> gains) {
|
||||
if (Objects.equals(Preferences.getReplayGainMode(), "disabled") || gains.size() == 0) {
|
||||
setReplayGain(player, 0f);
|
||||
} else if (Objects.equals(Preferences.getReplayGainMode(), "track")) {
|
||||
setReplayGain(player, gains.get(0).getTrackGain() != 0f ? gains.get(0).getTrackGain() : gains.get(0).getAlbumGain());
|
||||
} else if (Objects.equals(Preferences.getReplayGainMode(), "album")) {
|
||||
setReplayGain(player, gains.get(0).getAlbumGain() != 0f ? gains.get(0).getAlbumGain() : gains.get(0).getTrackGain());
|
||||
if (Objects.equals(Preferences.getReplayGainMode(), "disabled") || gains == null || gains.isEmpty()) {
|
||||
setNoReplayGain(player);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Objects.equals(Preferences.getReplayGainMode(), "auto")) {
|
||||
if (areTracksConsecutive(player)) {
|
||||
setAutoReplayGain(player, gains);
|
||||
} else {
|
||||
setTrackReplayGain(player, gains);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (Objects.equals(Preferences.getReplayGainMode(), "track")) {
|
||||
setTrackReplayGain(player, gains);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Objects.equals(Preferences.getReplayGainMode(), "album")) {
|
||||
setAlbumReplayGain(player, gains);
|
||||
return;
|
||||
}
|
||||
|
||||
setNoReplayGain(player);
|
||||
}
|
||||
|
||||
private static void setNoReplayGain(ExoPlayer player) {
|
||||
setReplayGain(player, 0f);
|
||||
}
|
||||
|
||||
private static void setTrackReplayGain(ExoPlayer player, List<ReplayGain> gains) {
|
||||
float trackGain = gains.get(0).getTrackGain() != 0f ? gains.get(0).getTrackGain() : gains.get(1).getTrackGain();
|
||||
|
||||
setReplayGain(player, trackGain != 0f ? trackGain : 0f);
|
||||
}
|
||||
|
||||
private static void setAlbumReplayGain(ExoPlayer player, List<ReplayGain> gains) {
|
||||
float albumGain = gains.get(0).getAlbumGain() != 0f ? gains.get(0).getAlbumGain() : gains.get(1).getAlbumGain();
|
||||
|
||||
setReplayGain(player, albumGain != 0f ? albumGain : 0f);
|
||||
}
|
||||
|
||||
private static void setAutoReplayGain(ExoPlayer player, List<ReplayGain> gains) {
|
||||
float albumGain = gains.get(0).getAlbumGain() != 0f ? gains.get(0).getAlbumGain() : gains.get(1).getAlbumGain();
|
||||
float trackGain = gains.get(0).getTrackGain() != 0f ? gains.get(0).getTrackGain() : gains.get(1).getTrackGain();
|
||||
|
||||
setReplayGain(player, albumGain != 0f ? albumGain : trackGain);
|
||||
}
|
||||
|
||||
private static boolean areTracksConsecutive(ExoPlayer player) {
|
||||
MediaItem currentMediaItem = player.getCurrentMediaItem();
|
||||
int currentMediaItemIndex = player.getCurrentMediaItemIndex();
|
||||
MediaItem pastMediaItem = currentMediaItemIndex > 0 ? player.getMediaItemAt(currentMediaItemIndex - 1) : null;
|
||||
|
||||
return currentMediaItem != null &&
|
||||
pastMediaItem != null &&
|
||||
pastMediaItem.mediaMetadata.albumTitle != null &&
|
||||
currentMediaItem.mediaMetadata.albumTitle != null &&
|
||||
pastMediaItem.mediaMetadata.albumTitle.toString().equals(currentMediaItem.mediaMetadata.albumTitle.toString());
|
||||
}
|
||||
|
||||
private static void setReplayGain(ExoPlayer player, float gain) {
|
||||
|
||||
@@ -5,8 +5,21 @@ import android.content.res.TypedArray;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.InsetDrawable;
|
||||
|
||||
import androidx.core.os.LocaleListCompat;
|
||||
import androidx.recyclerview.widget.DividerItemDecoration;
|
||||
|
||||
import com.cappielloantonio.tempo.R;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
public class UIUtil {
|
||||
public static int getSpanCount(int itemCount, int maxSpan) {
|
||||
int itemSize = itemCount == 0 ? 1 : itemCount;
|
||||
@@ -31,4 +44,44 @@ public class UIUtil {
|
||||
|
||||
return itemDecoration;
|
||||
}
|
||||
|
||||
private static LocaleListCompat getLocalesFromResources(Context context) {
|
||||
final List<String> tagsList = new ArrayList<>();
|
||||
|
||||
XmlPullParser xpp = context.getResources().getXml(R.xml.locale_config);
|
||||
|
||||
try {
|
||||
while (xpp.getEventType() != XmlPullParser.END_DOCUMENT) {
|
||||
String tagName = xpp.getName();
|
||||
|
||||
if (xpp.getEventType() == XmlPullParser.START_TAG) {
|
||||
if ("locale".equals(tagName) && xpp.getAttributeCount() > 0 && xpp.getAttributeName(0).equals("name")) {
|
||||
tagsList.add(xpp.getAttributeValue(0));
|
||||
}
|
||||
}
|
||||
|
||||
xpp.next();
|
||||
}
|
||||
} catch (XmlPullParserException | IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return LocaleListCompat.forLanguageTags(String.join(",", tagsList));
|
||||
}
|
||||
|
||||
public static Map<String, String> getLangPreferenceDropdownEntries(Context context) {
|
||||
LocaleListCompat localeList = getLocalesFromResources(context);
|
||||
|
||||
Map<String, String> map = new HashMap<>();
|
||||
|
||||
for (int i = 0; i < localeList.size(); i++) {
|
||||
Locale locale = localeList.get(i);
|
||||
|
||||
if (locale != null) {
|
||||
map.put(Util.toPascalCase(locale.getDisplayName()), locale.toLanguageTag());
|
||||
}
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
||||
64
app/src/main/java/com/cappielloantonio/tempo/util/Util.java
Normal file
64
app/src/main/java/com/cappielloantonio/tempo/util/Util.java
Normal file
@@ -0,0 +1,64 @@
|
||||
package com.cappielloantonio.tempo.util;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public class Util {
|
||||
public static <T> Predicate<T> distinctByKey(Function<? super T, Object> keyExtractor) {
|
||||
try {
|
||||
Map<Object, Boolean> uniqueMap = new ConcurrentHashMap<>();
|
||||
return t -> uniqueMap.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
|
||||
} catch (NullPointerException exception) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static String toPascalCase(String name) {
|
||||
if (name == null || name.isEmpty()) {
|
||||
return name;
|
||||
}
|
||||
|
||||
StringBuilder pascalCase = new StringBuilder();
|
||||
|
||||
char newChar;
|
||||
boolean toUpper = false;
|
||||
char[] charArray = name.toCharArray();
|
||||
|
||||
for (int ctr = 0; ctr <= charArray.length - 1; ctr++) {
|
||||
if (ctr == 0) {
|
||||
newChar = Character.toUpperCase(charArray[ctr]);
|
||||
pascalCase = new StringBuilder(Character.toString(newChar));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (charArray[ctr] == '_') {
|
||||
toUpper = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (toUpper) {
|
||||
newChar = Character.toUpperCase(charArray[ctr]);
|
||||
pascalCase.append(newChar);
|
||||
toUpper = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
pascalCase.append(charArray[ctr]);
|
||||
}
|
||||
|
||||
return pascalCase.toString();
|
||||
}
|
||||
|
||||
public static String encode(String value) {
|
||||
try {
|
||||
return URLEncoder.encode(value, StandardCharsets.UTF_8.toString());
|
||||
} catch (UnsupportedEncodingException ex) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.cappielloantonio.tempo.viewmodel;
|
||||
|
||||
import android.app.Application;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.AndroidViewModel;
|
||||
@@ -8,27 +9,55 @@ import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import com.cappielloantonio.tempo.model.DownloadStack;
|
||||
import com.cappielloantonio.tempo.repository.DownloadRepository;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class DownloadViewModel extends AndroidViewModel {
|
||||
private static final String TAG = "HomeViewModel";
|
||||
private static final String TAG = "DownloadViewModel";
|
||||
|
||||
private final DownloadRepository downloadRepository;
|
||||
|
||||
private final MutableLiveData<List<Child>> downloadedTrackSample = new MutableLiveData<>(null);
|
||||
private final MutableLiveData<ArrayList<DownloadStack>> viewStack = new MutableLiveData<>(null);
|
||||
|
||||
public DownloadViewModel(@NonNull Application application) {
|
||||
super(application);
|
||||
|
||||
downloadRepository = new DownloadRepository();
|
||||
|
||||
initViewStack(new DownloadStack(Preferences.getDefaultDownloadViewType(), null));
|
||||
}
|
||||
|
||||
public LiveData<List<Child>> getDownloadedTracks(LifecycleOwner owner) {
|
||||
downloadRepository.getLiveDownload().observe(owner, downloads -> downloadedTrackSample.postValue(downloads.stream().map(download -> (Child) download).collect(Collectors.toList())));
|
||||
return downloadedTrackSample;
|
||||
}
|
||||
|
||||
public LiveData<ArrayList<DownloadStack>> getViewStack() {
|
||||
return viewStack;
|
||||
}
|
||||
|
||||
public void initViewStack(DownloadStack level) {
|
||||
ArrayList<DownloadStack> stack = new ArrayList<>();
|
||||
stack.add(level);
|
||||
viewStack.setValue(stack);
|
||||
}
|
||||
|
||||
public void pushViewStack(DownloadStack level) {
|
||||
ArrayList<DownloadStack> stack = viewStack.getValue();
|
||||
stack.add(level);
|
||||
viewStack.setValue(stack);
|
||||
}
|
||||
|
||||
public void popViewStack() {
|
||||
ArrayList<DownloadStack> stack = viewStack.getValue();
|
||||
stack.remove(stack.size() - 1);
|
||||
viewStack.setValue(stack);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,8 +23,8 @@ public class IndexViewModel extends AndroidViewModel {
|
||||
directoryRepository = new DirectoryRepository();
|
||||
}
|
||||
|
||||
public MutableLiveData<Indexes> getIndexes() {
|
||||
return directoryRepository.getIndexes(null, null);
|
||||
public MutableLiveData<Indexes> getIndexes(String musicFolderId) {
|
||||
return directoryRepository.getIndexes(musicFolderId, null);
|
||||
}
|
||||
|
||||
public String getMusicFolderName() {
|
||||
|
||||
@@ -37,8 +37,7 @@ public class PlaylistEditorViewModel extends AndroidViewModel {
|
||||
}
|
||||
|
||||
public void updatePlaylist(String name) {
|
||||
playlistRepository.deletePlaylist(toEdit.getId());
|
||||
playlistRepository.createPlaylist(toEdit.getId(), name, getPlaylistSongIds());
|
||||
playlistRepository.updatePlaylist(toEdit.getId(), name, getPlaylistSongIds());
|
||||
}
|
||||
|
||||
public void deletePlaylist() {
|
||||
|
||||
@@ -71,11 +71,17 @@ public class SongListPageViewModel extends AndroidViewModel {
|
||||
public void getSongsByPage(LifecycleOwner owner) {
|
||||
switch (title) {
|
||||
case Constants.MEDIA_BY_GENRE:
|
||||
int page = (songList.getValue() != null ? songList.getValue().size() : 0) / 100;
|
||||
int songCount = songList.getValue() != null ? songList.getValue().size() : 0;
|
||||
|
||||
if (songCount > 0 && songCount % 100 != 0) return;
|
||||
|
||||
int page = songCount / 100;
|
||||
songRepository.getSongsByGenre(genre.getGenre(), page).observe(owner, children -> {
|
||||
List<Child> currentMedia = songList.getValue();
|
||||
currentMedia.addAll(children);
|
||||
songList.setValue(currentMedia);
|
||||
if (children != null && !children.isEmpty()) {
|
||||
List<Child> currentMedia = songList.getValue();
|
||||
currentMedia.addAll(children);
|
||||
songList.setValue(currentMedia);
|
||||
}
|
||||
});
|
||||
break;
|
||||
case Constants.MEDIA_BY_ARTIST:
|
||||
|
||||
9
app/src/main/res/drawable/ic_info_stream.xml
Normal file
9
app/src/main/res/drawable/ic_info_stream.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="@color/titleTextColor"
|
||||
android:pathData="M440,680L520,680L520,440L440,440L440,680ZM480,360Q497,360 508.5,348.5Q520,337 520,320Q520,303 508.5,291.5Q497,280 480,280Q463,280 451.5,291.5Q440,303 440,320Q440,337 451.5,348.5Q463,360 480,360ZM480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880ZM480,800Q614,800 707,707Q800,614 800,480Q800,346 707,253Q614,160 480,160Q346,160 253,253Q160,346 160,480Q160,614 253,707Q346,800 480,800ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Z"/>
|
||||
</vector>
|
||||
17
app/src/main/res/drawable/ic_placeholder_album.xml
Normal file
17
app/src/main/res/drawable/ic_placeholder_album.xml
Normal file
@@ -0,0 +1,17 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:name="vector"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:name="path"
|
||||
android:fillColor="?attr/colorSurfaceVariant"
|
||||
android:pathData="M 0 0 L 24 0 L 24 24 L 0 24 Z"
|
||||
android:strokeWidth="1" />
|
||||
<path
|
||||
android:name="path_1"
|
||||
android:fillColor="?attr/colorOnSurfaceVariant"
|
||||
android:pathData="M 12 5 C 8.136 5 5 8.136 5 12 C 5 15.864 8.136 19 12 19 C 15.864 19 19 15.864 19 12 C 19 8.136 15.864 5 12 5 Z M 12 15.15 C 10.257 15.15 8.85 13.743 8.85 12 C 8.85 10.257 10.257 8.85 12 8.85 C 13.743 8.85 15.15 10.257 15.15 12 C 15.15 13.743 13.743 15.15 12 15.15 Z M 12 11.3 C 11.615 11.3 11.3 11.615 11.3 12 C 11.3 12.385 11.615 12.7 12 12.7 C 12.385 12.7 12.7 12.385 12.7 12 C 12.7 11.615 12.385 11.3 12 11.3 Z"
|
||||
android:strokeWidth="1" />
|
||||
</vector>
|
||||
18
app/src/main/res/drawable/ic_placeholder_artist.xml
Normal file
18
app/src/main/res/drawable/ic_placeholder_artist.xml
Normal file
@@ -0,0 +1,18 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:name="vector"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:name="path"
|
||||
android:fillColor="?attr/colorSurfaceVariant"
|
||||
android:pathData="M 0 0 L 24 0 L 24 24 L 0 24 Z"
|
||||
android:strokeWidth="1" />
|
||||
<path
|
||||
android:name="path_1"
|
||||
android:fillColor="?attr/colorOnSurfaceVariant"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M 14.531 12.601 C 15.273 13.096 15.793 13.767 15.793 14.661 L 15.793 16.258 L 17.961 16.258 L 17.961 14.661 C 17.961 13.501 16.026 12.814 14.531 12.601 Z M 8.207 9.871 C 8.207 9.307 8.435 8.765 8.842 8.366 C 9.248 7.967 9.8 7.742 10.374 7.742 C 10.949 7.742 11.501 7.967 11.907 8.366 C 12.313 8.765 12.542 9.307 12.542 9.871 C 12.542 10.435 12.313 10.978 11.907 11.376 C 11.501 11.775 10.949 12 10.374 12 C 9.8 12 9.248 11.775 8.842 11.376 C 8.435 10.978 8.207 10.435 8.207 9.871 M 13.626 12 C 14.823 12 15.793 11.047 15.793 9.871 C 15.793 8.695 14.823 7.742 13.626 7.742 C 13.371 7.742 13.133 7.795 12.905 7.87 C 13.355 8.418 13.626 9.115 13.626 9.871 C 13.626 10.627 13.355 11.324 12.905 11.872 C 13.133 11.947 13.371 12 13.626 12 Z M 10.374 12.532 C 8.927 12.532 6.039 13.245 6.039 14.661 L 6.039 16.258 L 14.71 16.258 L 14.71 14.661 C 14.71 13.245 11.821 12.532 10.374 12.532 Z"
|
||||
android:strokeWidth="1" />
|
||||
</vector>
|
||||
17
app/src/main/res/drawable/ic_placeholder_directory.xml
Normal file
17
app/src/main/res/drawable/ic_placeholder_directory.xml
Normal file
@@ -0,0 +1,17 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:name="vector"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:name="path"
|
||||
android:fillColor="?attr/colorSurfaceVariant"
|
||||
android:pathData="M 0 0 L 24 0 L 24 24 L 0 24 Z"
|
||||
android:strokeWidth="1" />
|
||||
<path
|
||||
android:name="path_1"
|
||||
android:fillColor="?attr/colorOnSurfaceVariant"
|
||||
android:pathData="M 10.834 7.335 L 7.335 7.335 C 6.694 7.335 6.175 7.86 6.175 8.502 L 6.169 15.498 C 6.169 16.14 6.694 16.665 7.335 16.665 L 16.665 16.665 C 17.306 16.665 17.831 16.14 17.831 15.498 L 17.831 9.668 C 17.831 9.026 17.306 8.502 16.665 8.502 L 12 8.502 Z"
|
||||
android:strokeWidth="1" />
|
||||
</vector>
|
||||
17
app/src/main/res/drawable/ic_placeholder_folder.xml
Normal file
17
app/src/main/res/drawable/ic_placeholder_folder.xml
Normal file
@@ -0,0 +1,17 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:name="vector"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:name="path"
|
||||
android:fillColor="?attr/colorSurfaceVariant"
|
||||
android:pathData="M 0 0 L 24 0 L 24 24 L 0 24 Z"
|
||||
android:strokeWidth="1" />
|
||||
<path
|
||||
android:name="path_1"
|
||||
android:fillColor="?attr/colorOnSurfaceVariant"
|
||||
android:pathData="M 10.834 7.335 L 7.335 7.335 C 6.694 7.335 6.175 7.86 6.175 8.502 L 6.169 15.498 C 6.169 16.14 6.694 16.665 7.335 16.665 L 16.665 16.665 C 17.306 16.665 17.831 16.14 17.831 15.498 L 17.831 9.668 C 17.831 9.026 17.306 8.502 16.665 8.502 L 12 8.502 Z"
|
||||
android:strokeWidth="1" />
|
||||
</vector>
|
||||
17
app/src/main/res/drawable/ic_placeholder_playlist.xml
Normal file
17
app/src/main/res/drawable/ic_placeholder_playlist.xml
Normal file
@@ -0,0 +1,17 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:name="vector"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:name="path"
|
||||
android:fillColor="?attr/colorSurfaceVariant"
|
||||
android:pathData="M 0 0 L 24 0 L 24 24 L 0 24 Z"
|
||||
android:strokeWidth="1" />
|
||||
<path
|
||||
android:name="path_1"
|
||||
android:fillColor="?attr/colorOnSurfaceVariant"
|
||||
android:pathData="M 13.593 8.061 L 5.946 8.061 L 5.946 9.329 L 13.593 9.329 Z M 13.593 10.598 L 5.946 10.598 L 5.946 11.866 L 13.593 11.866 Z M 5.946 14.403 L 11.044 14.403 L 11.044 13.134 L 5.946 13.134 Z M 14.867 8.061 L 14.867 13.249 C 14.669 13.179 14.453 13.134 14.23 13.134 C 13.172 13.134 12.318 13.984 12.318 15.037 C 12.318 16.09 13.172 16.939 14.23 16.939 C 15.288 16.939 16.141 16.09 16.141 15.037 L 16.141 9.329 L 18.053 9.329 L 18.053 8.061 Z"
|
||||
android:strokeWidth="1" />
|
||||
</vector>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user