103 Commits
3.4.9 ... 3.5.7

Author SHA1 Message Date
antonio
24747207b8 gradle: bump up code version 2023-09-17 19:15:58 +02:00
antonio
e762c91ff3 feat: added setting to request estimated duration of transcoded file 2023-09-17 19:12:45 +02:00
antonio
0fe4636fe1 feat: parameterized track sharing feature 2023-09-17 18:51:22 +02:00
antonio
8ab011781e fix: made expiration date TextInputEditText unselectable 2023-09-17 18:27:16 +02:00
antonio
7f820bd5a6 feat: implemented playlist sharing 2023-09-17 18:22:57 +02:00
antonio
26b8f3f65c feat: implemented album and song sharing 2023-09-17 16:46:21 +02:00
antonio
f172a00fb7 feat: implemented updating shared items through dialog 2023-09-17 16:43:09 +02:00
antonio
077d22167c feat: added bottomSheet for shared items on the home screen 2023-09-17 16:42:07 +02:00
antonio
ebd1582d1c feat: added shared items list on the homepage 2023-09-17 16:40:41 +02:00
antonio
5e486d4794 feat: implemented client, service, and repository for sharing APIs 2023-09-17 16:38:08 +02:00
antonio
537d0c6d8f gradle: bump up code version 2023-09-15 22:13:13 +02:00
antonio
4fc75f5c46 refactor: code cleanup 2023-09-15 22:02:31 +02:00
CappielloAntonio
baf1bc48b0 Merge pull request #82 from GallowsDove/notifications
feat: add notification groups
2023-09-15 21:50:08 +02:00
antonio
f26a123646 style: added icon for indexes 2023-09-15 21:42:24 +02:00
antonio
7160e3f4b9 style: modified placeholder icon background colors and added folder icon 2023-09-15 21:34:44 +02:00
antonio
24b3161a8e style: hide top-listened until server response is confirmed 2023-09-15 21:33:39 +02:00
GallowsDove
383fbd1c49 feat: add notification groups 2023-09-15 13:09:48 +02:00
antonio
c0f5abdfae Merge remote-tracking branch 'origin/main' 2023-09-13 17:27:49 +02:00
antonio
a50e50d797 fix: set no replay gain if array is null or empty 2023-09-13 17:27:43 +02:00
CappielloAntonio
1f9dac9e3a github: add crash issue template 2023-09-13 17:01:51 +02:00
CappielloAntonio
f57ac6d624 github: add feature request issue template 2023-09-13 16:56:09 +02:00
CappielloAntonio
0b96fe5605 github: add bug issue template 2023-09-13 16:51:05 +02:00
antonio
16561be854 fix: null checking 2023-09-13 16:19:50 +02:00
antonio
570ad1c984 gradle: dependencies update 2023-09-13 16:09:02 +02:00
antonio
736bcdb994 style: uniformed button size for "more buttons" to make them consistent and easier to click 2023-09-09 18:43:18 +02:00
antonio
e71472f498 style: pull request refactoring 2023-09-08 15:56:50 +02:00
CappielloAntonio
fb328f26b8 Merge pull request #70 from ivan-avalos/main
feat: improved item placeholders
2023-09-08 15:38:17 +02:00
antonio
c26aba8b2d style: pull request refactoring 2023-09-08 15:30:05 +02:00
antonio
a17de1de8d fix: null checking 2023-09-08 14:41:32 +02:00
antonio
7ea159cb10 Merge remote-tracking branch 'origin/main' 2023-09-08 14:20:15 +02:00
CappielloAntonio
8ed98bbedc Merge pull request #61 from GallowsDove/main
feat: add bottom sheet for grouped views in download tab
2023-09-08 14:17:49 +02:00
antonio
43a1b5b93a fix: null checking 2023-09-08 14:12:35 +02:00
antonio
977754f02c clean: code cleanup 2023-09-08 10:54:43 +02:00
antonio
10aae5fa15 test: implemented and temporarily set aside the implementation of a customLoadController 2023-09-08 10:52:21 +02:00
antonio
dee60e5e8f fix: fixed application logic of Replay Gain 2023-09-08 10:51:09 +02:00
Iván Ávalos
0c05b77849 feat: improved item placeholders 2023-09-07 19:48:03 -06:00
GallowsDove
223954e9ca Merge branch 'main' into main 2023-09-07 10:39:03 +02:00
antonio
fdc8c50d25 clean: code cleanup 2023-09-06 22:56:54 +02:00
antonio
aa7872d030 fix: fixed lag when opening AlbumBottomSheet caused by disappearing download removal button 2023-09-06 22:33:20 +02:00
antonio
9ed7744421 fix: updated download section navigation to match the system's correct stack-based navigation 2023-09-06 21:49:23 +02:00
antonio
c19db362d9 gradle: bump up code version 2023-09-06 18:31:04 +02:00
antonio
6a505eea4e feat: implemented a dialog box for displaying details of the currently playing track 2023-09-06 18:28:32 +02:00
antonio
99be2764d0 feat: added the ability to shuffle the playback queue 2023-09-06 15:03:28 +02:00
antonio
b656ad9e7f feat: increased visibility of gestures within the music player 2023-09-06 15:02:32 +02:00
GallowsDove
75e5756d7e fix: Fix performance issues with DownloadedBottomSheet with large number of grouped entries 2023-09-06 00:53:59 +02:00
antonio
13d8bbf877 feat: made tap on the track cover more prominent to discover quick action commands 2023-09-05 21:33:31 +02:00
GallowsDove
f43d7fb394 feat: Add bottom sheet for grouped views in Download tab 2023-09-05 18:54:48 +02:00
antonio
68527b2c91 style: adjusted padding of elements for improved consistency 2023-08-31 16:21:36 +02:00
antonio
347c074368 style: fixed visibility state of playback icons 2023-08-31 16:20:55 +02:00
antonio
2a74f51f8d fix: corrected folder navigation, now displaying correct subfolders 2023-08-31 16:20:05 +02:00
antonio
1a75369591 fix: null checking 2023-08-31 15:58:00 +02:00
antonio
8f6e775ca9 gradle: bump up code version 2023-08-30 17:53:44 +02:00
antonio
bad0fa6c23 fix: fixed a bug in the paginated song lists 2023-08-30 17:52:44 +02:00
antonio
d4caa6f209 build: dependencies update 2023-08-29 08:03:47 +02:00
antonio
723bdf9771 clean: code cleanup 2023-08-28 12:26:04 +02:00
antonio
49fbd85bb4 clean: code cleanup 2023-08-28 12:25:36 +02:00
CappielloAntonio
f040fbf0cf Merge pull request #50 from GallowsDove/main
feat: show albums instead of the artist in artist's top songs
2023-08-28 12:17:54 +02:00
antonio
efb2213ab7 fix: files without embedded cover art now display the image in the music player notification 2023-08-28 12:15:40 +02:00
GallowsDove
742ac6b17d feat: show albums instead of the artist in artist's top songs. 2023-08-28 11:28:59 +02:00
antonio
ae7761cb96 fix: null checking 2023-08-28 10:51:16 +02:00
antonio
c977982d64 gradle: bump up code version 2023-08-25 15:38:05 +02:00
antonio
28fc3dca36 fix: set initial visibility of the grid to "Gone" and make it appear once the elements are successfully loaded 2023-08-25 15:36:55 +02:00
antonio
f1cf65a371 fix: redefined proguard rules to make Retrofit work for everyone 2023-08-25 15:28:47 +02:00
antonio
beb1d29e8f fix: delayed grid display until server connectivity is confirmed 2023-08-25 15:27:39 +02:00
antonio
1eda7cef9e fix: fix a crash when clicking the dot menu of an empty playlist 2023-08-25 12:32:24 +02:00
antonio
1af92ad949 fix: now refreshing playlist view every time library is visited 2023-08-25 12:23:26 +02:00
antonio
3fc9b35fe4 fix: a callback on playlist editor dialog closing tells me when to refresh the playlist view 2023-08-25 12:22:43 +02:00
antonio
56b48dbd4d fix: fixed race condition issue in playlist update 2023-08-25 12:20:49 +02:00
antonio
1cb371dc5a gradle: dependencies update 2023-08-25 08:50:52 +02:00
antonio
499001a269 fix: podcast playback issue resolved 2023-08-24 10:04:12 +02:00
antonio
4c9e47379d style: code cleanup 2023-08-22 14:35:21 +02:00
CappielloAntonio
a0dbb5c81f clean: readme cleanup 2023-08-22 14:34:49 +02:00
CappielloAntonio
dab53c6bbf Merge pull request #42 from dnno/fix/null-pointer-genre-scrolling
fix: check if children exist before adding
2023-08-22 14:30:35 +02:00
antonio
c0a665c00a fix: fix click on mediaitem 2023-08-22 14:22:20 +02:00
Reinhard Prechtl
41b5c57240 Check if children exist before adding 2023-08-22 13:09:30 +02:00
antonio
1d65a79c20 Fix: manually collapse bottomSheet on device state change 2023-08-21 16:40:01 +02:00
antonio
efb6e72636 Merge remote-tracking branch 'origin/main' 2023-08-21 16:15:26 +02:00
antonio
af83ffd608 fix: manually encoded the username when creating the uri for streaming and fetching coverArt 2023-08-21 16:15:08 +02:00
antonio
0201077bc4 gradle: dependencies update 2023-08-21 15:49:26 +02:00
CappielloAntonio
91d91d3024 Merge pull request #39 from dnno/main
Add new localized strings for german language
2023-08-20 15:25:54 +02:00
Reinhard Prechtl
9784a2b6c5 Add new localized strings for german language 2023-08-20 14:24:55 +02:00
antonio
87a3301912 feat: implemented hard deletion of downloaded files upon storage change or explicit user request 2023-08-19 12:33:39 +02:00
antonio
295795edc9 clean: AndroidManifest cleaning 2023-08-19 11:57:05 +02:00
antonio
6120ab66ba style: implemented string conversion method to PascalCase 2023-08-17 14:09:56 +02:00
antonio
7bea180c58 feat: implemented language picker in app settings, dynamically populating the list of available languages programmatically. 2023-08-17 14:09:17 +02:00
antonio
a29cee488e build: started implementing language picker via appcompat instead of automatically generating language configurations to maintain compatibility with Android versions below 13 2023-08-17 14:07:56 +02:00
antonio
74b4b04693 feat: enabled automatic per-app language support 2023-08-16 23:54:47 +02:00
antonio
b8b9c80bdc fix: null checking 2023-08-16 23:48:14 +02:00
antonio
a50fc74117 build: update workflow 2023-08-16 23:31:33 +02:00
antonio
c1af438a3a build: update workflow 2023-08-16 23:27:55 +02:00
antonio
80b251cddc gradle: bump up gradle version 2023-08-16 22:53:02 +02:00
antonio
7d9a48818e gradle: installing jetbrains runtime 17.0.8 2023-08-16 22:47:54 +02:00
antonio
ca3da0839b Merge remote-tracking branch 'origin/main' 2023-08-16 22:42:37 +02:00
CappielloAntonio
cf463d8fa1 Update github_release.yml 2023-08-16 22:41:44 +02:00
antonio
635fdc4c5c gradle: started working on Gradle upgrade, for testing purposes 2023-08-16 22:33:52 +02:00
antonio
3e913931c4 gradle: bump up code version 2023-08-15 19:16:32 +02:00
antonio
46420da038 chore: uploaded Android Room migration file 2023-08-15 19:08:10 +02:00
antonio
d5b7619dd1 style: aligned surface colors with recommended Material You colors 2023-08-15 19:06:31 +02:00
antonio
89b39123da fix: updated status bar and music player background colors 2023-08-14 19:24:38 +02:00
antonio
5a43137984 fix: fixed issue with dynamic color not being correctly applied 2023-08-14 19:02:26 +02:00
CappielloAntonio
761b07450f Merge pull request #36 from ThePBone/fix-filter
fix: filter array of songs in setItems (DownloadHorizontalAdapter)
2023-08-14 18:10:49 +02:00
Tim Schneeberger
a0b67a06f4 refactor: put grouped into bundle instead of songs 2023-08-14 18:08:11 +02:00
Tim Schneeberger
59d7adb66d fix: filter array of songs in setItems() 2023-08-14 17:43:33 +02:00
157 changed files with 4337 additions and 571 deletions

33
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View 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
View 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.

View 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.

View File

@@ -6,19 +6,15 @@ on:
- '[0-9]+.[0-9]+.[0-9]+' - '[0-9]+.[0-9]+.[0-9]+'
jobs: jobs:
setup:
runs-on: ubuntu-latest
steps:
- name: Setup JDK 8
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: 8
build: build:
needs: setup
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Setup JDK 17
uses: actions/setup-java@v2
with:
distribution: 'zulu'
java-version: '17'
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Cache Gradle and wrapper - name: Cache Gradle and wrapper

2
.idea/gradle.xml generated
View File

@@ -7,7 +7,7 @@
<option name="testRunner" value="GRADLE" /> <option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" /> <option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" /> <option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="11" /> <option name="gradleJvm" value="17" />
<option name="modules"> <option name="modules">
<set> <set>
<option value="$PROJECT_DIR$" /> <option value="$PROJECT_DIR$" />

2
.idea/misc.xml generated
View File

@@ -191,7 +191,7 @@
</map> </map>
</option> </option>
</component> </component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="jbr-17" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" /> <output url="file://$PROJECT_DIR$/build/classes" />
</component> </component>
<component name="ProjectType"> <component name="ProjectType">

View File

@@ -33,12 +33,8 @@ Tempo does not rely on magic algorithms to decide what you should listen to. Ins
<img src="mockup/feat/6_screenshot.png" width=200> <img src="mockup/feat/6_screenshot.png" width=200>
<img src="mockup/feat/7_screenshot.png" width=200> <img src="mockup/feat/7_screenshot.png" width=200>
<img src="mockup/feat/8_screenshot.png" width=200> <img src="mockup/feat/8_screenshot.png" width=200>
<img src="mockup/feat/9_screenshot.png" width=200>
</p> </p>
## Disclaimer
Tempo is currently under active development and is in alpha state. This means that the app may contain stability issues, bugs, or incomplete features. While we strive to provide a smooth and reliable experience, please be aware that using Tempo in its current state may not be as stable as a fully released version. I appreciate your understanding and patience as I work towards improving the app.
## Sponsors ## Sponsors
Tempo is an open-source project developed and maintained solely by me. I would like to express my heartfelt thanks to all the users who have shown their love and support for Tempo. Your contributions and encouragement mean a lot to me, and they help drive the development and improvement of the app. Tempo is an open-source project developed and maintained solely by me. I would like to express my heartfelt thanks to all the users who have shown their love and support for Tempo. Your contributions and encouragement mean a lot to me, and they help drive the development and improvement of the app.

View File

@@ -3,8 +3,8 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlin-parcelize' apply plugin: 'kotlin-parcelize'
android { android {
compileSdkVersion 34 compileSdk 34
buildToolsVersion '33.0.0' buildToolsVersion "34.0.0"
defaultConfig { defaultConfig {
minSdkVersion 24 minSdkVersion 24
@@ -28,8 +28,8 @@ android {
tempo { tempo {
dimension "default" dimension "default"
applicationId 'com.cappielloantonio.tempo' applicationId 'com.cappielloantonio.tempo'
versionCode 15 versionCode 22
versionName '3.4.9' versionName '3.5.7'
} }
notquitemy { notquitemy {
@@ -72,27 +72,28 @@ dependencies {
implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.2.0' implementation 'androidx.coordinatorlayout:coordinatorlayout:1.2.0'
implementation 'androidx.preference:preference-ktx:1.2.1' implementation 'androidx.preference:preference-ktx:1.2.1'
implementation 'androidx.navigation:navigation-fragment-ktx:2.7.0' implementation 'androidx.navigation:navigation-fragment-ktx:2.7.2'
implementation 'androidx.navigation:navigation-ui-ktx:2.7.0' implementation 'androidx.navigation:navigation-ui-ktx:2.7.2'
implementation 'androidx.recyclerview:recyclerview:1.3.1' implementation 'androidx.recyclerview:recyclerview:1.3.1'
implementation 'androidx.room:room-runtime:2.5.2' implementation 'androidx.room:room-runtime:2.5.2'
implementation 'androidx.core:core-splashscreen:1.0.1' implementation 'androidx.core:core-splashscreen:1.0.1'
implementation "androidx.appcompat:appcompat:1.6.1"
// Android Material // Android Material
implementation 'com.google.android.material:material:1.9.0' implementation 'com.google.android.material:material:1.9.0'
// Glide // Glide
implementation 'com.github.bumptech.glide:glide:4.15.1' implementation 'com.github.bumptech.glide:glide:4.16.0'
implementation 'com.github.bumptech.glide:annotations:4.15.1' implementation 'com.github.bumptech.glide:annotations:4.16.0'
// Media3 // Media3
implementation 'androidx.media3:media3-session:1.1.0' implementation 'androidx.media3:media3-session:1.1.1'
implementation 'androidx.media3:media3-common:1.1.0' implementation 'androidx.media3:media3-common:1.1.1'
implementation 'androidx.media3:media3-exoplayer:1.1.0' implementation 'androidx.media3:media3-exoplayer:1.1.1'
implementation 'androidx.media3:media3-ui:1.1.0' implementation 'androidx.media3:media3-ui:1.1.1'
tempoImplementation 'androidx.media3:media3-cast:1.1.0' tempoImplementation 'androidx.media3:media3-cast:1.1.1'
annotationProcessor 'com.github.bumptech.glide:compiler:4.15.1' annotationProcessor 'com.github.bumptech.glide:compiler:4.16.0'
annotationProcessor 'androidx.room:room-compiler:2.5.2' annotationProcessor 'androidx.room:room-compiler:2.5.2'
// Retrofit // Retrofit

View File

@@ -21,4 +21,5 @@
#-renamesourcefileattribute SourceFile #-renamesourcefileattribute SourceFile
-keepattributes SourceFile, LineNumberTable -keepattributes SourceFile, LineNumberTable
-keep public class * extends java.lang.Exception -keep public class * extends java.lang.Exception
-keep class retrofit2.** { *; }

View File

@@ -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')"
]
}
}

View File

@@ -1,54 +1,71 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.WAKE_LOCK"/> <uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<application <application
android:name="App" android:name="App"
android:allowBackup="false"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:localeConfig="@xml/locale_config"
android:roundIcon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/AppTheme.SplashScreen" android:theme="@style/AppTheme.SplashScreen"
android:usesCleartextTraffic="true" android:usesCleartextTraffic="true">
android:allowBackup="false">
<meta-data <meta-data
android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME" android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
android:value="androidx.media3.cast.DefaultCastOptionsProvider"/> android:value="androidx.media3.cast.DefaultCastOptionsProvider" />
<meta-data
android:name="androidx.car.app.TintableAttributionIcon"
android:resource="@drawable/ic_graphic_eq" />
<activity <activity
android:name=".ui.activity.MainActivity" android:name=".ui.activity.MainActivity"
android:windowSoftInputMode="adjustPan|adjustResize" android:exported="true"
android:exported="true"> android:windowSoftInputMode="adjustPan|adjustResize">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<service <service
android:name=".service.MediaService" android:name=".service.MediaService"
android:foregroundServiceType="mediaPlayback" android:exported="true"
android:exported="true"> android:foregroundServiceType="mediaPlayback">
<intent-filter> <intent-filter>
<action android:name="androidx.media3.session.MediaLibraryService" /> <action android:name="androidx.media3.session.MediaLibraryService" />
<action android:name="androidx.media3.session.MediaBrowserService" /> <action android:name="androidx.media3.session.MediaBrowserService" />
</intent-filter> </intent-filter>
</service> </service>
<service android:name=".service.DownloaderService"
android:foregroundServiceType="dataSync" <service
android:exported="true"> android:name=".service.DownloaderService"
android:exported="true"
android:foregroundServiceType="dataSync">
<intent-filter> <intent-filter>
<action android:name="androidx.media3.exoplayer.downloadService.action.RESTART"/> <action android:name="androidx.media3.exoplayer.downloadService.action.RESTART" />
<category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.DEFAULT" />
</intent-filter> </intent-filter>
</service> </service>
<service
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
android:enabled="false"
android:exported="false">
<meta-data
android:name="autoStoreLocales"
android:value="true" />
</service>
</application> </application>
</manifest> </manifest>

View File

@@ -10,7 +10,6 @@ import com.cappielloantonio.tempo.helper.ThemeHelper;
import com.cappielloantonio.tempo.subsonic.Subsonic; import com.cappielloantonio.tempo.subsonic.Subsonic;
import com.cappielloantonio.tempo.subsonic.SubsonicPreferences; import com.cappielloantonio.tempo.subsonic.SubsonicPreferences;
import com.cappielloantonio.tempo.util.Preferences; import com.cappielloantonio.tempo.util.Preferences;
import com.google.android.material.color.DynamicColors;
public class App extends Application { public class App extends Application {
private static App instance; private static App instance;
@@ -22,7 +21,6 @@ public class App extends Application {
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
DynamicColors.applyToActivitiesIfAvailable(this);
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
String themePref = sharedPreferences.getString(Preferences.THEME, ThemeHelper.DEFAULT_MODE); String themePref = sharedPreferences.getString(Preferences.THEME, ThemeHelper.DEFAULT_MODE);
ThemeHelper.applyTheme(themePref); ThemeHelper.applyTheme(themePref);

View File

@@ -5,6 +5,9 @@ import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.util.Log; import android.util.Log;
import androidx.annotation.Nullable;
import androidx.appcompat.content.res.AppCompatResources;
import com.bumptech.glide.Glide; import com.bumptech.glide.Glide;
import com.bumptech.glide.RequestBuilder; import com.bumptech.glide.RequestBuilder;
import com.bumptech.glide.RequestManager; 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.request.RequestOptions;
import com.bumptech.glide.signature.ObjectKey; import com.bumptech.glide.signature.ObjectKey;
import com.cappielloantonio.tempo.App; import com.cappielloantonio.tempo.App;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.util.Preferences; import com.cappielloantonio.tempo.util.Preferences;
import com.cappielloantonio.tempo.util.Util;
import com.google.android.material.elevation.SurfaceColors; import com.google.android.material.elevation.SurfaceColors;
import java.util.Map; import java.util.Map;
@@ -27,16 +32,53 @@ public class CustomGlideRequest {
public static final DiskCacheStrategy DEFAULT_DISK_CACHE_STRATEGY = DiskCacheStrategy.ALL; 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() return new RequestOptions()
.placeholder(new ColorDrawable(SurfaceColors.SURFACE_5.getColor(context))) .placeholder(new ColorDrawable(SurfaceColors.SURFACE_5.getColor(context)))
.fallback(new ColorDrawable(SurfaceColors.SURFACE_5.getColor(context))) .fallback(getPlaceholder(context, type))
.error(new ColorDrawable(SurfaceColors.SURFACE_5.getColor(context))) .error(getPlaceholder(context, type))
.diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY) .diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY)
.signature(new ObjectKey(item != null ? item : 0)) .signature(new ObjectKey(item != null ? item : 0))
.transform(new CenterCrop(), new RoundedCorners(CustomGlideRequest.CORNER_RADIUS)); .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) { public static String createUrl(String item, int size) {
Map<String, String> params = App.getSubsonicClientInstance(false).getParams(); Map<String, String> params = App.getSubsonicClientInstance(false).getParams();
@@ -46,7 +88,7 @@ public class CustomGlideRequest {
uri.append("getCoverArt"); uri.append("getCoverArt");
if (params.containsKey("u") && params.get("u") != null) if (params.containsKey("u") && params.get("u") != null)
uri.append("?u=").append(params.get("u")); uri.append("?u=").append(Util.encode(params.get("u")));
if (params.containsKey("p") && params.get("p") != null) if (params.containsKey("p") && params.get("p") != null)
uri.append("&p=").append(params.get("p")); uri.append("&p=").append(params.get("p"));
if (params.containsKey("s") && params.get("s") != null) if (params.containsKey("s") && params.get("s") != null)
@@ -71,18 +113,18 @@ public class CustomGlideRequest {
private final RequestManager requestManager; private final RequestManager requestManager;
private Object item; private Object item;
private Builder(Context context, String item) { private Builder(Context context, String item, ResourceType type) {
this.requestManager = Glide.with(context); this.requestManager = Glide.with(context);
if (item != null && !Preferences.isDataSavingMode()) { if (item != null && !Preferences.isDataSavingMode()) {
this.item = createUrl(item, Preferences.getImageSize()); 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) { public static Builder from(Context context, String item, ResourceType type) {
return new Builder(context, item); return new Builder(context, item, type);
} }
public RequestBuilder<Drawable> build() { public RequestBuilder<Drawable> build() {

View File

@@ -29,4 +29,7 @@ public interface ClickCallback {
default void onMusicFolderClick(Bundle bundle) {} default void onMusicFolderClick(Bundle bundle) {}
default void onMusicDirectoryClick(Bundle bundle) {} default void onMusicDirectoryClick(Bundle bundle) {}
default void onMusicIndexClick(Bundle bundle) {} default void onMusicIndexClick(Bundle bundle) {}
default void onDownloadGroupLongClick(Bundle bundle) {}
default void onShareClick(Bundle bundle) {}
default void onShareLongClick(Bundle bundle) {}
} }

View File

@@ -0,0 +1,8 @@
package com.cappielloantonio.tempo.interfaces;
import androidx.annotation.Keep;
@Keep
public interface PlaylistCallback {
default void onDismiss() {}
}

View File

@@ -105,7 +105,9 @@ public class AlbumRepository {
List<Child> tracks = new ArrayList<>(); List<Child> tracks = new ArrayList<>();
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getAlbum() != null) { if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getAlbum() != null) {
tracks.addAll(response.body().getSubsonicResponse().getAlbum().getSongs()); if (response.body().getSubsonicResponse().getAlbum().getSongs() != null) {
tracks.addAll(response.body().getSubsonicResponse().getAlbum().getSongs());
}
} }
albumTracks.setValue(tracks); albumTracks.setValue(tracks);
@@ -247,7 +249,7 @@ public class AlbumRepository {
.enqueue(new Callback<ApiResponse>() { .enqueue(new Callback<ApiResponse>() {
@Override @Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) { public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().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()) { if (response.body().getSubsonicResponse().getAlbumList2().getAlbums().size() > 0 && !response.body().getSubsonicResponse().getAlbumList2().getAlbums().isEmpty()) {
callback.onLoadYear(response.body().getSubsonicResponse().getAlbumList2().getAlbums().get(0).getYear()); callback.onLoadYear(response.body().getSubsonicResponse().getAlbumList2().getAlbums().get(0).getYear());
} else { } else {

View File

@@ -63,8 +63,12 @@ public class ArtistRepository {
if (response.isSuccessful() && response.body() != null) { if (response.isSuccessful() && response.body() != null) {
List<ArtistID3> artists = new ArrayList<>(); List<ArtistID3> artists = new ArrayList<>();
for (IndexID3 index : response.body().getSubsonicResponse().getArtists().getIndices()) { if(response.body().getSubsonicResponse().getArtists() != null && response.body().getSubsonicResponse().getArtists().getIndices() != null) {
artists.addAll(index.getArtists()); for (IndexID3 index : response.body().getSubsonicResponse().getArtists().getIndices()) {
if(index != null && index.getArtists() != null) {
artists.addAll(index.getArtists());
}
}
} }
if (random) { if (random) {

View File

@@ -95,12 +95,29 @@ public class PlaylistRepository {
.enqueue(new Callback<ApiResponse>() { .enqueue(new Callback<ApiResponse>() {
@Override @Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) { public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
Log.d("PLAYLIST", response.toString());
} }
@Override @Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) { public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
Log.d("PLAYLIST", t.toString());
}
});
}
public void updatePlaylist(String playlistId, String name, ArrayList<String> songsId) {
App.getSubsonicClientInstance(false)
.getPlaylistClient()
.deletePlaylist(playlistId)
.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
createPlaylist(null, name, songsId);
}
@Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
} }
}); });
} }

View File

@@ -59,7 +59,7 @@ public class PodcastRepository {
@Override @Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) { public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
Log.d(TAG, "onFailure()");
} }
}); });

View File

@@ -1,7 +1,5 @@
package com.cappielloantonio.tempo.repository; package com.cappielloantonio.tempo.repository;
import android.util.Log;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
@@ -36,7 +34,9 @@ public class SearchingRepository {
.enqueue(new Callback<ApiResponse>() { .enqueue(new Callback<ApiResponse>() {
@Override @Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) { public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
result.setValue(response.body().getSubsonicResponse().getSearchResult2()); if (response.isSuccessful() && response.body() != null) {
result.setValue(response.body().getSubsonicResponse().getSearchResult2());
}
} }
@Override @Override
@@ -57,7 +57,9 @@ public class SearchingRepository {
.enqueue(new Callback<ApiResponse>() { .enqueue(new Callback<ApiResponse>() {
@Override @Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) { public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
result.setValue(response.body().getSubsonicResponse().getSearchResult3()); if (response.isSuccessful() && response.body() != null) {
result.setValue(response.body().getSubsonicResponse().getSearchResult3());
}
} }
@Override @Override
@@ -80,7 +82,7 @@ public class SearchingRepository {
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) { public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
List<String> newSuggestions = new ArrayList(); 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) { if (response.body().getSubsonicResponse().getSearchResult3().getArtists() != null) {
for (ArtistID3 artistID3 : response.body().getSubsonicResponse().getSearchResult3().getArtists()) { for (ArtistID3 artistID3 : response.body().getSubsonicResponse().getSearchResult3().getArtists()) {
newSuggestions.add(artistID3.getName()); newSuggestions.add(artistID3.getName());
@@ -102,8 +104,6 @@ public class SearchingRepository {
LinkedHashSet<String> hashSet = new LinkedHashSet<>(newSuggestions); LinkedHashSet<String> hashSet = new LinkedHashSet<>(newSuggestions);
ArrayList<String> suggestionsWithoutDuplicates = new ArrayList<>(hashSet); ArrayList<String> suggestionsWithoutDuplicates = new ArrayList<>(hashSet);
Log.d("suggestionsWithoutDuplicates", suggestionsWithoutDuplicates.toString());
suggestions.setValue(suggestionsWithoutDuplicates); suggestions.setValue(suggestionsWithoutDuplicates);
} }
} }

View File

@@ -0,0 +1,97 @@
package com.cappielloantonio.tempo.repository;
import androidx.annotation.NonNull;
import androidx.lifecycle.MutableLiveData;
import com.cappielloantonio.tempo.App;
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
import com.cappielloantonio.tempo.subsonic.models.Share;
import java.util.ArrayList;
import java.util.List;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class SharingRepository {
public MutableLiveData<List<Share>> getShares() {
MutableLiveData<List<Share>> shares = new MutableLiveData<>(new ArrayList<>());
App.getSubsonicClientInstance(false)
.getSharingClient()
.getShares()
.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getShares() != null && response.body().getSubsonicResponse().getShares().getShares() != null) {
shares.setValue(response.body().getSubsonicResponse().getShares().getShares());
}
}
@Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
}
});
return shares;
}
public MutableLiveData<Share> createShare(String id, String description, Long expires) {
MutableLiveData<Share> share = new MutableLiveData<>();
App.getSubsonicClientInstance(false)
.getSharingClient()
.createShare(id, description, expires)
.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getShares() != null && response.body().getSubsonicResponse().getShares().getShares() != null && response.body().getSubsonicResponse().getShares().getShares().get(0) != null) {
share.setValue(response.body().getSubsonicResponse().getShares().getShares().get(0));
}
}
@Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
}
});
return share;
}
public void updateShare(String id, String description, Long expires) {
App.getSubsonicClientInstance(false)
.getSharingClient()
.updateShare(id, description, expires)
.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
}
@Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
}
});
}
public void deleteShare(String id) {
App.getSubsonicClientInstance(false)
.getSharingClient()
.deleteShare(id)
.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
}
@Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
}
});
}
}

View File

@@ -97,7 +97,7 @@ public class SongRepository {
@Override @Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) { public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
Log.d(TAG, "onFailure: ");
} }
}); });
@@ -205,7 +205,7 @@ public class SongRepository {
@Override @Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) { public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
Log.d(TAG, "onFailure: ");
} }
}); });

View File

@@ -103,6 +103,7 @@ public class DownloaderManager {
public void removeAll() { public void removeAll() {
DownloadService.sendRemoveAllDownloads(context, DownloaderService.class, false); DownloadService.sendRemoveAllDownloads(context, DownloaderService.class, false);
deleteAllDatabase(); deleteAllDatabase();
DownloadUtil.eraseDownloadFolder(context);
} }
private void loadDownloads() { private void loadDownloads() {

View File

@@ -51,17 +51,40 @@ public class DownloaderService extends androidx.media3.exoplayer.offline.Downloa
} }
private static final class TerminalStateNotificationHelper implements DownloadManager.Listener { private static final class TerminalStateNotificationHelper implements DownloadManager.Listener {
private static final String TAG = "TerminalStateNotificatinHelper";
private final Context context; private final Context context;
private final DownloadNotificationHelper notificationHelper; private final DownloadNotificationHelper notificationHelper;
private final Notification successfulDownloadGroupNotification;
private final Notification failedDownloadGroupNotification;
private final int successfulDownloadGroupNotificationId;
private final int failedDownloadGroupNotificationId;
private int nextNotificationId; private int nextNotificationId;
public TerminalStateNotificationHelper(Context context, DownloadNotificationHelper notificationHelper, int firstNotificationId) { public TerminalStateNotificationHelper(Context context, DownloadNotificationHelper notificationHelper, int firstNotificationId) {
this.context = context.getApplicationContext(); this.context = context.getApplicationContext();
this.notificationHelper = notificationHelper; this.notificationHelper = notificationHelper;
nextNotificationId = firstNotificationId; 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 @Override
@@ -70,9 +93,13 @@ public class DownloaderService extends androidx.media3.exoplayer.offline.Downloa
if (download.state == Download.STATE_COMPLETED) { if (download.state == Download.STATE_COMPLETED) {
notification = notificationHelper.buildDownloadCompletedNotification(context, R.drawable.ic_check_circle, null, DownloaderManager.getDownloadNotificationMessage(download.request.id)); 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); DownloaderManager.updateDatabase(download.request.id);
} else if (download.state == Download.STATE_FAILED) { } else if (download.state == Download.STATE_FAILED) {
notification = notificationHelper.buildDownloadFailedNotification(context, R.drawable.ic_error, null, DownloaderManager.getDownloadNotificationMessage(download.request.id)); 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 { } else {
return; return;
} }

View File

@@ -204,6 +204,22 @@ public class MediaManager {
} }
} }
public static void shuffle(ListenableFuture<MediaBrowser> mediaBrowserListenableFuture, List<Child> media, int startIndex, int endIndex) {
if (mediaBrowserListenableFuture != null) {
mediaBrowserListenableFuture.addListener(() -> {
try {
if (mediaBrowserListenableFuture.isDone()) {
mediaBrowserListenableFuture.get().removeMediaItems(startIndex, endIndex + 1);
mediaBrowserListenableFuture.get().addMediaItems(MappingUtil.mapMediaItems(media).subList(startIndex, endIndex + 1));
swapDatabase(media);
}
} catch (ExecutionException | InterruptedException e) {
e.printStackTrace();
}
}, MoreExecutors.directExecutor());
}
}
public static void swap(ListenableFuture<MediaBrowser> mediaBrowserListenableFuture, List<Child> media, int from, int to) { public static void swap(ListenableFuture<MediaBrowser> mediaBrowserListenableFuture, List<Child> media, int from, int to) {
if (mediaBrowserListenableFuture != null) { if (mediaBrowserListenableFuture != null) {
mediaBrowserListenableFuture.addListener(() -> { mediaBrowserListenableFuture.addListener(() -> {

View File

@@ -10,6 +10,7 @@ import com.cappielloantonio.tempo.subsonic.api.mediaretrieval.MediaRetrievalClie
import com.cappielloantonio.tempo.subsonic.api.playlist.PlaylistClient; import com.cappielloantonio.tempo.subsonic.api.playlist.PlaylistClient;
import com.cappielloantonio.tempo.subsonic.api.podcast.PodcastClient; import com.cappielloantonio.tempo.subsonic.api.podcast.PodcastClient;
import com.cappielloantonio.tempo.subsonic.api.searching.SearchingClient; import com.cappielloantonio.tempo.subsonic.api.searching.SearchingClient;
import com.cappielloantonio.tempo.subsonic.api.sharing.SharingClient;
import com.cappielloantonio.tempo.subsonic.api.system.SystemClient; import com.cappielloantonio.tempo.subsonic.api.system.SystemClient;
import com.cappielloantonio.tempo.subsonic.base.Version; import com.cappielloantonio.tempo.subsonic.base.Version;
@@ -33,6 +34,7 @@ public class Subsonic {
private MediaLibraryScanningClient mediaLibraryScanningClient; private MediaLibraryScanningClient mediaLibraryScanningClient;
private BookmarksClient bookmarksClient; private BookmarksClient bookmarksClient;
private InternetRadioClient internetRadioClient; private InternetRadioClient internetRadioClient;
private SharingClient sharingClient;
public Subsonic(SubsonicPreferences preferences) { public Subsonic(SubsonicPreferences preferences) {
this.preferences = preferences; this.preferences = preferences;
@@ -119,6 +121,13 @@ public class Subsonic {
return internetRadioClient; return internetRadioClient;
} }
public SharingClient getSharingClient() {
if (sharingClient == null) {
sharingClient = new SharingClient(this);
}
return sharingClient;
}
public String getUrl() { public String getUrl() {
String url = preferences.getServerUrl() + "/rest/"; String url = preferences.getServerUrl() + "/rest/";
return url.replace("//rest", "/rest"); return url.replace("//rest", "/rest");

View File

@@ -0,0 +1,41 @@
package com.cappielloantonio.tempo.subsonic.api.sharing;
import android.util.Log;
import com.cappielloantonio.tempo.subsonic.RetrofitClient;
import com.cappielloantonio.tempo.subsonic.Subsonic;
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
import retrofit2.Call;
public class SharingClient {
private static final String TAG = "BrowsingClient";
private final Subsonic subsonic;
private final SharingService sharingService;
public SharingClient(Subsonic subsonic) {
this.subsonic = subsonic;
this.sharingService = new RetrofitClient(subsonic).getRetrofit().create(SharingService.class);
}
public Call<ApiResponse> getShares() {
Log.d(TAG, "getShares()");
return sharingService.getShares(subsonic.getParams());
}
public Call<ApiResponse> createShare(String id, String description, Long expires) {
Log.d(TAG, "createShare()");
return sharingService.createShare(subsonic.getParams(), id, description, expires);
}
public Call<ApiResponse> updateShare(String id, String description, Long expires) {
Log.d(TAG, "updateShare()");
return sharingService.updateShare(subsonic.getParams(), id, description, expires);
}
public Call<ApiResponse> deleteShare(String id) {
Log.d(TAG, "deleteShare()");
return sharingService.deleteShare(subsonic.getParams(), id);
}
}

View File

@@ -0,0 +1,24 @@
package com.cappielloantonio.tempo.subsonic.api.sharing;
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
import java.util.Map;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Query;
import retrofit2.http.QueryMap;
public interface SharingService {
@GET("getShares")
Call<ApiResponse> getShares(@QueryMap Map<String, String> params);
@GET("createShare")
Call<ApiResponse> createShare(@QueryMap Map<String, String> params, @Query("id") String id, @Query("description") String description, @Query("expires") Long expires);
@GET("updateShare")
Call<ApiResponse> updateShare(@QueryMap Map<String, String> params, @Query("id") String id, @Query("description") String description, @Query("expires") Long expires);
@GET("deleteShare")
Call<ApiResponse> deleteShare(@QueryMap Map<String, String> params, @Query("id") String id);
}

View File

@@ -7,5 +7,5 @@ import com.google.gson.annotations.SerializedName
@Keep @Keep
class ApiResponse { class ApiResponse {
@SerializedName("subsonic-response") @SerializedName("subsonic-response")
var subsonicResponse: SubsonicResponse? = null lateinit var subsonicResponse: SubsonicResponse;
} }

View File

@@ -1,10 +1,15 @@
package com.cappielloantonio.tempo.subsonic.models package com.cappielloantonio.tempo.subsonic.models
import android.os.Parcelable
import androidx.annotation.Keep import androidx.annotation.Keep
import com.google.gson.annotations.SerializedName
import kotlinx.parcelize.Parcelize
import java.util.* import java.util.*
@Keep @Keep
class Share { @Parcelize
class Share : Parcelable {
@SerializedName("entry")
var entries: List<Child>? = null var entries: List<Child>? = null
var id: String? = null var id: String? = null
var url: String? = null var url: String? = null

View File

@@ -1,8 +1,10 @@
package com.cappielloantonio.tempo.subsonic.models package com.cappielloantonio.tempo.subsonic.models
import androidx.annotation.Keep import androidx.annotation.Keep
import com.google.gson.annotations.SerializedName
@Keep @Keep
class Shares { class Shares {
@SerializedName("share")
var shares: List<Share>? = null var shares: List<Share>? = null
} }

View File

@@ -31,6 +31,7 @@ import com.cappielloantonio.tempo.util.Preferences;
import com.cappielloantonio.tempo.viewmodel.MainViewModel; import com.cappielloantonio.tempo.viewmodel.MainViewModel;
import com.google.android.material.bottomnavigation.BottomNavigationView; import com.google.android.material.bottomnavigation.BottomNavigationView;
import com.google.android.material.bottomsheet.BottomSheetBehavior; import com.google.android.material.bottomsheet.BottomSheetBehavior;
import com.google.android.material.color.DynamicColors;
import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.MoreExecutors;
import java.util.Objects; import java.util.Objects;
@@ -51,10 +52,10 @@ public class MainActivity extends BaseActivity {
ConnectivityStatusBroadcastReceiver connectivityStatusBroadcastReceiver; ConnectivityStatusBroadcastReceiver connectivityStatusBroadcastReceiver;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
SplashScreen.installSplashScreen(this); SplashScreen.installSplashScreen(this);
DynamicColors.applyToActivityIfAvailable(this);
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@@ -118,6 +119,8 @@ public class MainActivity extends BaseActivity {
fragmentManager.beginTransaction().replace(R.id.player_bottom_sheet, new PlayerBottomSheetFragment(), "PlayerBottomSheet").commit(); fragmentManager.beginTransaction().replace(R.id.player_bottom_sheet, new PlayerBottomSheetFragment(), "PlayerBottomSheet").commit();
setBottomSheetInPeek(mainViewModel.isQueueLoaded()); setBottomSheetInPeek(mainViewModel.isQueueLoaded());
collapseBottomSheet();
} }
public void setBottomSheetInPeek(Boolean isVisible) { public void setBottomSheetInPeek(Boolean isVisible) {

View File

@@ -98,6 +98,7 @@ public class BaseActivity extends AppCompatActivity {
} }
private void setNavigationBarColor() { private void setNavigationBarColor() {
getWindow().setNavigationBarColor(SurfaceColors.getColorForElevation(this, 10)); getWindow().setNavigationBarColor(SurfaceColors.getColorForElevation(this, 8));
getWindow().setStatusBarColor(SurfaceColors.getColorForElevation(this, 0));
} }
} }

View File

@@ -42,7 +42,7 @@ public class AlbumAdapter extends RecyclerView.Adapter<AlbumAdapter.ViewHolder>
holder.item.artistNameLabel.setText(MusicUtil.getReadableString(album.getArtist())); holder.item.artistNameLabel.setText(MusicUtil.getReadableString(album.getArtist()));
CustomGlideRequest.Builder CustomGlideRequest.Builder
.from(holder.itemView.getContext(), album.getCoverArtId()) .from(holder.itemView.getContext(), album.getCoverArtId(), CustomGlideRequest.ResourceType.Album)
.build() .build()
.into(holder.item.albumCoverImageView); .into(holder.item.albumCoverImageView);
} }

View File

@@ -42,7 +42,7 @@ public class AlbumArtistPageOrSimilarAdapter extends RecyclerView.Adapter<AlbumA
holder.item.artistNameLabel.setText(MusicUtil.getReadableString(album.getArtist())); holder.item.artistNameLabel.setText(MusicUtil.getReadableString(album.getArtist()));
CustomGlideRequest.Builder CustomGlideRequest.Builder
.from(holder.itemView.getContext(), album.getCoverArtId()) .from(holder.itemView.getContext(), album.getCoverArtId(), CustomGlideRequest.ResourceType.Album)
.build() .build()
.into(holder.item.artistPageAlbumCoverImageView); .into(holder.item.artistPageAlbumCoverImageView);
} }

View File

@@ -77,7 +77,7 @@ public class AlbumCatalogueAdapter extends RecyclerView.Adapter<AlbumCatalogueAd
holder.item.artistNameLabel.setText(MusicUtil.getReadableString(album.getArtist())); holder.item.artistNameLabel.setText(MusicUtil.getReadableString(album.getArtist()));
CustomGlideRequest.Builder CustomGlideRequest.Builder
.from(holder.itemView.getContext(), album.getCoverArtId()) .from(holder.itemView.getContext(), album.getCoverArtId(), CustomGlideRequest.ResourceType.Album)
.build() .build()
.into(holder.item.albumCatalogueCoverImageView); .into(holder.item.albumCatalogueCoverImageView);
} }

View File

@@ -44,7 +44,7 @@ public class AlbumHorizontalAdapter extends RecyclerView.Adapter<AlbumHorizontal
holder.item.albumArtistTextView.setText(MusicUtil.getReadableString(album.getArtist())); holder.item.albumArtistTextView.setText(MusicUtil.getReadableString(album.getArtist()));
CustomGlideRequest.Builder CustomGlideRequest.Builder
.from(holder.itemView.getContext(), album.getCoverArtId()) .from(holder.itemView.getContext(), album.getCoverArtId(), CustomGlideRequest.ResourceType.Album)
.build() .build()
.into(holder.item.albumCoverImageView); .into(holder.item.albumCoverImageView);
} }

View File

@@ -47,7 +47,7 @@ public class ArtistAdapter extends RecyclerView.Adapter<ArtistAdapter.ViewHolder
holder.item.artistNameLabel.setText(MusicUtil.getReadableString(artist.getName())); holder.item.artistNameLabel.setText(MusicUtil.getReadableString(artist.getName()));
CustomGlideRequest.Builder CustomGlideRequest.Builder
.from(holder.itemView.getContext(), artist.getCoverArtId()) .from(holder.itemView.getContext(), artist.getCoverArtId(), CustomGlideRequest.ResourceType.Artist)
.build() .build()
.into(holder.item.artistCoverImageView); .into(holder.item.artistCoverImageView);
} }

View File

@@ -77,7 +77,7 @@ public class ArtistCatalogueAdapter extends RecyclerView.Adapter<ArtistCatalogue
holder.item.artistNameLabel.setText(MusicUtil.getReadableString(artist.getName())); holder.item.artistNameLabel.setText(MusicUtil.getReadableString(artist.getName()));
CustomGlideRequest.Builder CustomGlideRequest.Builder
.from(holder.itemView.getContext(), artist.getCoverArtId()) .from(holder.itemView.getContext(), artist.getCoverArtId(), CustomGlideRequest.ResourceType.Artist)
.build() .build()
.into(holder.item.artistCatalogueCoverImageView); .into(holder.item.artistCatalogueCoverImageView);
} }

View File

@@ -48,7 +48,7 @@ public class ArtistHorizontalAdapter extends RecyclerView.Adapter<ArtistHorizont
} }
CustomGlideRequest.Builder CustomGlideRequest.Builder
.from(holder.itemView.getContext(), artist.getCoverArtId()) .from(holder.itemView.getContext(), artist.getCoverArtId(), CustomGlideRequest.ResourceType.Artist)
.build() .build()
.into(holder.item.artistCoverImageView); .into(holder.item.artistCoverImageView);
} }

View File

@@ -41,7 +41,7 @@ public class ArtistSimilarAdapter extends RecyclerView.Adapter<ArtistSimilarAdap
holder.item.artistNameLabel.setText(MusicUtil.getReadableString(artist.getName())); holder.item.artistNameLabel.setText(MusicUtil.getReadableString(artist.getName()));
CustomGlideRequest.Builder CustomGlideRequest.Builder
.from(holder.itemView.getContext(), artist.getCoverArtId()) .from(holder.itemView.getContext(), artist.getCoverArtId(), CustomGlideRequest.ResourceType.Artist)
.build() .build()
.into(holder.item.similarArtistCoverImageView); .into(holder.item.similarArtistCoverImageView);
} }

View File

@@ -43,7 +43,7 @@ public class DiscoverSongAdapter extends RecyclerView.Adapter<DiscoverSongAdapte
holder.item.albumDiscoverSongLabel.setText(MusicUtil.getReadableString(song.getAlbum())); holder.item.albumDiscoverSongLabel.setText(MusicUtil.getReadableString(song.getAlbum()));
CustomGlideRequest.Builder CustomGlideRequest.Builder
.from(holder.itemView.getContext(), song.getCoverArtId()) .from(holder.itemView.getContext(), song.getCoverArtId(), CustomGlideRequest.ResourceType.Song)
.build() .build()
.into(holder.item.discoverSongCoverImageView); .into(holder.item.discoverSongCoverImageView);
} }

View File

@@ -163,7 +163,7 @@ public class DownloadHorizontalAdapter extends RecyclerView.Adapter<DownloadHori
holder.item.downloadedItemPreTextView.setText(MusicUtil.getReadableString(song.getAlbum())); holder.item.downloadedItemPreTextView.setText(MusicUtil.getReadableString(song.getAlbum()));
CustomGlideRequest.Builder CustomGlideRequest.Builder
.from(holder.itemView.getContext(), song.getCoverArtId()) .from(holder.itemView.getContext(), song.getCoverArtId(), CustomGlideRequest.ResourceType.Song)
.build() .build()
.into(holder.item.itemCoverImageView); .into(holder.item.itemCoverImageView);
@@ -186,12 +186,12 @@ public class DownloadHorizontalAdapter extends RecyclerView.Adapter<DownloadHori
holder.item.downloadedItemPreTextView.setText(MusicUtil.getReadableString(song.getArtist())); holder.item.downloadedItemPreTextView.setText(MusicUtil.getReadableString(song.getArtist()));
CustomGlideRequest.Builder CustomGlideRequest.Builder
.from(holder.itemView.getContext(), song.getCoverArtId()) .from(holder.itemView.getContext(), song.getCoverArtId(), CustomGlideRequest.ResourceType.Song)
.build() .build()
.into(holder.item.itemCoverImageView); .into(holder.item.itemCoverImageView);
holder.item.itemCoverImageView.setVisibility(View.VISIBLE); holder.item.itemCoverImageView.setVisibility(View.VISIBLE);
holder.item.downloadedItemMoreButton.setVisibility(View.GONE); holder.item.downloadedItemMoreButton.setVisibility(View.VISIBLE);
holder.item.divider.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())) { if (position > 0 && grouped.get(position - 1) != null && !Objects.equals(grouped.get(position - 1).getArtist(), grouped.get(position).getArtist())) {
@@ -208,12 +208,12 @@ public class DownloadHorizontalAdapter extends RecyclerView.Adapter<DownloadHori
holder.item.downloadedItemSubtitleTextView.setText(holder.itemView.getContext().getString(R.string.download_item_single_subtitle_formatter, countSong(Constants.DOWNLOAD_TYPE_ARTIST, song.getArtistId(), songs))); 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 CustomGlideRequest.Builder
.from(holder.itemView.getContext(), song.getCoverArtId()) .from(holder.itemView.getContext(), song.getCoverArtId(), CustomGlideRequest.ResourceType.Song)
.build() .build()
.into(holder.item.itemCoverImageView); .into(holder.item.itemCoverImageView);
holder.item.itemCoverImageView.setVisibility(View.VISIBLE); holder.item.itemCoverImageView.setVisibility(View.VISIBLE);
holder.item.downloadedItemMoreButton.setVisibility(View.GONE); holder.item.downloadedItemMoreButton.setVisibility(View.VISIBLE);
holder.item.divider.setVisibility(View.GONE); holder.item.divider.setVisibility(View.GONE);
} }
@@ -224,7 +224,7 @@ public class DownloadHorizontalAdapter extends RecyclerView.Adapter<DownloadHori
holder.item.downloadedItemSubtitleTextView.setText(holder.itemView.getContext().getString(R.string.download_item_single_subtitle_formatter, countSong(Constants.DOWNLOAD_TYPE_GENRE, song.getGenre(), songs))); holder.item.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.itemCoverImageView.setVisibility(View.GONE);
holder.item.downloadedItemMoreButton.setVisibility(View.GONE); holder.item.downloadedItemMoreButton.setVisibility(View.VISIBLE);
holder.item.divider.setVisibility(View.GONE); holder.item.divider.setVisibility(View.GONE);
} }
@@ -235,7 +235,7 @@ public class DownloadHorizontalAdapter extends RecyclerView.Adapter<DownloadHori
holder.item.downloadedItemSubtitleTextView.setText(holder.itemView.getContext().getString(R.string.download_item_single_subtitle_formatter, countSong(Constants.DOWNLOAD_TYPE_YEAR, song.getYear().toString(), songs))); holder.item.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.itemCoverImageView.setVisibility(View.GONE);
holder.item.downloadedItemMoreButton.setVisibility(View.GONE); holder.item.downloadedItemMoreButton.setVisibility(View.VISIBLE);
holder.item.divider.setVisibility(View.GONE); holder.item.divider.setVisibility(View.GONE);
} }
@@ -261,7 +261,7 @@ public class DownloadHorizontalAdapter extends RecyclerView.Adapter<DownloadHori
switch (view) { switch (view) {
case Constants.DOWNLOAD_TYPE_TRACK: case Constants.DOWNLOAD_TYPE_TRACK:
bundle.putParcelableArrayList(Constants.TRACKS_OBJECT, new ArrayList<>(songs)); bundle.putParcelableArrayList(Constants.TRACKS_OBJECT, new ArrayList<>(grouped));
bundle.putInt(Constants.ITEM_POSITION, getBindingAdapterPosition()); bundle.putInt(Constants.ITEM_POSITION, getBindingAdapterPosition());
click.onMediaClick(bundle); click.onMediaClick(bundle);
break; break;
@@ -285,24 +285,36 @@ public class DownloadHorizontalAdapter extends RecyclerView.Adapter<DownloadHori
} }
private boolean onLongClick() { private boolean onLongClick() {
ArrayList<Child> filteredSongs = new ArrayList<>();
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
switch (view) { switch (view) {
case Constants.DOWNLOAD_TYPE_TRACK: case Constants.DOWNLOAD_TYPE_TRACK:
bundle.putParcelable(Constants.TRACK_OBJECT, grouped.get(getBindingAdapterPosition())); filteredSongs.add(grouped.get(getBindingAdapterPosition()));
click.onMediaLongClick(bundle); break;
return true;
case Constants.DOWNLOAD_TYPE_ALBUM: case Constants.DOWNLOAD_TYPE_ALBUM:
bundle.putString(Constants.DOWNLOAD_TYPE_ALBUM, grouped.get(getBindingAdapterPosition()).getAlbumId()); filteredSongs.addAll(filterSong(Constants.DOWNLOAD_TYPE_ALBUM, grouped.get(getBindingAdapterPosition()).getAlbumId(), songs));
click.onAlbumLongClick(bundle); break;
return true;
case Constants.DOWNLOAD_TYPE_ARTIST: case Constants.DOWNLOAD_TYPE_ARTIST:
bundle.putString(Constants.DOWNLOAD_TYPE_ARTIST, grouped.get(getBindingAdapterPosition()).getArtistId()); filteredSongs.addAll(filterSong(Constants.DOWNLOAD_TYPE_ARTIST, grouped.get(getBindingAdapterPosition()).getArtistId(), songs));
click.onArtistLongClick(bundle); break;
return true; case Constants.DOWNLOAD_TYPE_GENRE:
filteredSongs.addAll(filterSong(Constants.DOWNLOAD_TYPE_GENRE, grouped.get(getBindingAdapterPosition()).getGenre(), songs));
break;
case Constants.DOWNLOAD_TYPE_YEAR:
filteredSongs.addAll(filterSong(Constants.DOWNLOAD_TYPE_YEAR, grouped.get(getBindingAdapterPosition()).getYear().toString(), songs));
break;
} }
return false; if (filteredSongs.isEmpty()) return false;
bundle.putParcelableArrayList(Constants.DOWNLOAD_GROUP, new ArrayList<>(filteredSongs));
bundle.putString(Constants.DOWNLOAD_GROUP_TITLE, item.downloadedItemTitleTextView.getText().toString());
bundle.putString(Constants.DOWNLOAD_GROUP_SUBTITLE, item.downloadedItemSubtitleTextView.getText().toString());
click.onDownloadGroupLongClick(bundle);
return true;
} }
} }
} }

View File

@@ -39,7 +39,7 @@ public class GridTrackAdapter extends RecyclerView.Adapter<GridTrackAdapter.View
Chronology item = items.get(position); Chronology item = items.get(position);
CustomGlideRequest.Builder CustomGlideRequest.Builder
.from(holder.itemView.getContext(), item.getCoverArtId()) .from(holder.itemView.getContext(), item.getCoverArtId(), CustomGlideRequest.ResourceType.Song)
.build() .build()
.into(holder.item.trackCoverImageView); .into(holder.item.trackCoverImageView);
} }

View File

@@ -43,7 +43,7 @@ public class InternetRadioStationAdapter extends RecyclerView.Adapter<InternetRa
holder.item.internetRadioStationSubtitleTextView.setText(internetRadioStation.getStreamUrl()); holder.item.internetRadioStationSubtitleTextView.setText(internetRadioStation.getStreamUrl());
CustomGlideRequest.Builder CustomGlideRequest.Builder
.from(holder.itemView.getContext(), internetRadioStation.getStreamUrl()) .from(holder.itemView.getContext(), internetRadioStation.getStreamUrl(), CustomGlideRequest.ResourceType.Radio)
.build() .build()
.into(holder.item.internetRadioStationCoverImageView); .into(holder.item.internetRadioStationCoverImageView);
} }

View File

@@ -43,13 +43,17 @@ public class MusicDirectoryAdapter extends RecyclerView.Adapter<MusicDirectoryAd
holder.item.musicDirectoryTitleTextView.setText(child.getTitle()); holder.item.musicDirectoryTitleTextView.setText(child.getTitle());
CustomGlideRequest.ResourceType type = child.isDir()
? CustomGlideRequest.ResourceType.Directory
: CustomGlideRequest.ResourceType.Song;
CustomGlideRequest.Builder CustomGlideRequest.Builder
.from(holder.itemView.getContext(), child.getCoverArtId()) .from(holder.itemView.getContext(), child.getCoverArtId(), type)
.build() .build()
.into(holder.item.musicDirectoryCoverImageView); .into(holder.item.musicDirectoryCoverImageView);
holder.item.musicDirectoryMoreButton.setVisibility(child.isDir() ? View.VISIBLE : View.GONE); holder.item.musicDirectoryMoreButton.setVisibility(child.isDir() ? View.VISIBLE : View.INVISIBLE);
holder.item.musicDirectoryPlayButton.setVisibility(child.isDir() ? View.GONE : View.VISIBLE); holder.item.musicDirectoryPlayButton.setVisibility(child.isDir() ? View.INVISIBLE : View.VISIBLE);
} }
@Override @Override

View File

@@ -42,7 +42,7 @@ public class MusicFolderAdapter extends RecyclerView.Adapter<MusicFolderAdapter.
holder.item.musicFolderTitleTextView.setText(musicFolder.getName()); holder.item.musicFolderTitleTextView.setText(musicFolder.getName());
CustomGlideRequest.Builder CustomGlideRequest.Builder
.from(holder.itemView.getContext(), musicFolder.getName()) .from(holder.itemView.getContext(), musicFolder.getName(), CustomGlideRequest.ResourceType.Folder)
.build() .build()
.into(holder.item.musicFolderCoverImageView); .into(holder.item.musicFolderCoverImageView);
} }

View File

@@ -9,6 +9,7 @@ import androidx.media3.common.util.UnstableApi;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import com.cappielloantonio.tempo.databinding.ItemLibraryMusicIndexBinding; import com.cappielloantonio.tempo.databinding.ItemLibraryMusicIndexBinding;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.helper.recyclerview.FastScrollbar; import com.cappielloantonio.tempo.helper.recyclerview.FastScrollbar;
import com.cappielloantonio.tempo.interfaces.ClickCallback; import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.subsonic.models.Artist; import com.cappielloantonio.tempo.subsonic.models.Artist;
@@ -42,10 +43,10 @@ public class MusicIndexAdapter extends RecyclerView.Adapter<MusicIndexAdapter.Vi
holder.item.musicIndexTitleTextView.setText(artist.getName()); holder.item.musicIndexTitleTextView.setText(artist.getName());
/* CustomGlideRequest.Builder CustomGlideRequest.Builder
.from(holder.itemView.getContext(), artist.getName()) .from(holder.itemView.getContext(), artist.getName(), CustomGlideRequest.ResourceType.Directory)
.build() .build()
.into(holder.item.musicIndexCoverImageView); */ .into(holder.item.musicIndexCoverImageView);
} }
@Override @Override
@@ -60,7 +61,7 @@ public class MusicIndexAdapter extends RecyclerView.Adapter<MusicIndexAdapter.Vi
@Override @Override
public String getTextToShowInBubble(int pos) { 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 { public class ViewHolder extends RecyclerView.ViewHolder {

View File

@@ -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))); holder.item.queueSongSubtitleTextView.setText(holder.itemView.getContext().getString(R.string.song_subtitle_formatter, MusicUtil.getReadableString(song.getArtist()), MusicUtil.getReadableDurationString(song.getDuration(), false)));
CustomGlideRequest.Builder CustomGlideRequest.Builder
.from(holder.itemView.getContext(), song.getCoverArtId()) .from(holder.itemView.getContext(), song.getCoverArtId(), CustomGlideRequest.ResourceType.Song)
.build() .build()
.into(holder.item.queueSongCoverImageView); .into(holder.item.queueSongCoverImageView);

View File

@@ -37,7 +37,7 @@ public class PlaylistDialogSongHorizontalAdapter extends RecyclerView.Adapter<Pl
holder.item.playlistDialogSongDurationTextView.setText(MusicUtil.getReadableDurationString(song.getDuration(), false)); holder.item.playlistDialogSongDurationTextView.setText(MusicUtil.getReadableDurationString(song.getDuration(), false));
CustomGlideRequest.Builder CustomGlideRequest.Builder
.from(holder.itemView.getContext(), song.getCoverArtId()) .from(holder.itemView.getContext(), song.getCoverArtId(), CustomGlideRequest.ResourceType.Song)
.build() .build()
.into(holder.item.playlistDialogSongCoverImageView); .into(holder.item.playlistDialogSongCoverImageView);
} }

View File

@@ -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))); holder.item.playlistSubtitleTextView.setText(holder.itemView.getContext().getString(R.string.playlist_counted_tracks, playlist.getSongCount(), MusicUtil.getReadableDurationString(playlist.getDuration(), false)));
CustomGlideRequest.Builder CustomGlideRequest.Builder
.from(holder.itemView.getContext(), playlist.getCoverArtId()) .from(holder.itemView.getContext(), playlist.getCoverArtId(), CustomGlideRequest.ResourceType.Playlist)
.build() .build()
.into(holder.item.playlistCoverImageView); .into(holder.item.playlistCoverImageView);
} }

View File

@@ -75,7 +75,7 @@ public class PodcastChannelCatalogueAdapter extends RecyclerView.Adapter<Podcast
holder.item.podcastChannelTitleLabel.setText(MusicUtil.getReadableString(podcastChannel.getTitle())); holder.item.podcastChannelTitleLabel.setText(MusicUtil.getReadableString(podcastChannel.getTitle()));
CustomGlideRequest.Builder CustomGlideRequest.Builder
.from(holder.itemView.getContext(), podcastChannel.getCoverArtId()) .from(holder.itemView.getContext(), podcastChannel.getCoverArtId(), CustomGlideRequest.ResourceType.Podcast)
.build() .build()
.into(holder.item.podcastChannelCatalogueCoverImageView); .into(holder.item.podcastChannelCatalogueCoverImageView);
} }

View File

@@ -42,7 +42,7 @@ public class PodcastChannelHorizontalAdapter extends RecyclerView.Adapter<Podcas
holder.item.podcastChannelDescriptionTextView.setText(MusicUtil.getReadableString(podcastChannel.getDescription())); holder.item.podcastChannelDescriptionTextView.setText(MusicUtil.getReadableString(podcastChannel.getDescription()));
CustomGlideRequest.Builder CustomGlideRequest.Builder
.from(holder.itemView.getContext(), podcastChannel.getOriginalImageUrl()) .from(holder.itemView.getContext(), podcastChannel.getOriginalImageUrl(), CustomGlideRequest.ResourceType.Podcast)
.build() .build()
.into(holder.item.podcastChannelCoverImageView); .into(holder.item.podcastChannelCoverImageView);
} }

View File

@@ -51,7 +51,7 @@ public class PodcastEpisodeAdapter extends RecyclerView.Adapter<PodcastEpisodeAd
holder.item.podcastDescriptionText.setText(MusicUtil.getReadableString(podcastEpisode.getDescription())); holder.item.podcastDescriptionText.setText(MusicUtil.getReadableString(podcastEpisode.getDescription()));
CustomGlideRequest.Builder CustomGlideRequest.Builder
.from(holder.itemView.getContext(), podcastEpisode.getCoverArtId()) .from(holder.itemView.getContext(), podcastEpisode.getCoverArtId(), CustomGlideRequest.ResourceType.Podcast)
.build() .build()
.into(holder.item.podcastCoverImageView); .into(holder.item.podcastCoverImageView);

View File

@@ -0,0 +1,99 @@
package com.cappielloantonio.tempo.ui.adapter;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.ItemHorizontalShareBinding;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.subsonic.models.Share;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.util.UIUtil;
import java.util.Collections;
import java.util.List;
public class ShareHorizontalAdapter extends RecyclerView.Adapter<ShareHorizontalAdapter.ViewHolder> {
private final ClickCallback click;
private List<Share> shares;
public ShareHorizontalAdapter(ClickCallback click) {
this.click = click;
this.shares = Collections.emptyList();
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ItemHorizontalShareBinding view = ItemHorizontalShareBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Share share = shares.get(position);
holder.item.shareTitleTextView.setText(MusicUtil.getReadableString(share.getDescription()));
holder.item.shareSubtitleTextView.setText(holder.itemView.getContext().getString(R.string.share_subtitle_item, UIUtil.getReadableDate(share.getExpires())));
if (share.getEntries() != null && !share.getEntries().isEmpty()) CustomGlideRequest.Builder
.from(holder.itemView.getContext(), share.getEntries().get(0).getCoverArtId(), CustomGlideRequest.ResourceType.Album)
.build()
.into(holder.item.shareCoverImageView);
}
@Override
public int getItemCount() {
return shares.size();
}
public void setItems(List<Share> shares) {
this.shares = shares;
notifyDataSetChanged();
}
public Share getItem(int id) {
return shares.get(id);
}
public class ViewHolder extends RecyclerView.ViewHolder {
ItemHorizontalShareBinding item;
ViewHolder(ItemHorizontalShareBinding item) {
super(item.getRoot());
this.item = item;
item.shareTitleTextView.setSelected(true);
item.shareSubtitleTextView.setSelected(true);
itemView.setOnClickListener(v -> onClick());
itemView.setOnLongClickListener(v -> onLongClick());
item.shareButton.setOnClickListener(v -> onLongClick());
}
private void onClick() {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.SHARE_OBJECT, shares.get(getBindingAdapterPosition()));
click.onShareClick(bundle);
}
private boolean onLongClick() {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.SHARE_OBJECT, shares.get(getBindingAdapterPosition()));
click.onShareLongClick(bundle);
return true;
}
}
}

View File

@@ -41,7 +41,7 @@ public class SimilarTrackAdapter extends RecyclerView.Adapter<SimilarTrackAdapte
holder.item.titleTrackLabel.setText(MusicUtil.getReadableString(song.getTitle())); holder.item.titleTrackLabel.setText(MusicUtil.getReadableString(song.getTitle()));
CustomGlideRequest.Builder CustomGlideRequest.Builder
.from(holder.itemView.getContext(), song.getCoverArtId()) .from(holder.itemView.getContext(), song.getCoverArtId(), CustomGlideRequest.ResourceType.Song)
.build() .build()
.into(holder.item.trackCoverImageView); .into(holder.item.trackCoverImageView);
} }

View File

@@ -25,13 +25,15 @@ import java.util.List;
@UnstableApi @UnstableApi
public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAdapter.ViewHolder> { public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAdapter.ViewHolder> {
private final ClickCallback click; private final ClickCallback click;
private final boolean isCoverVisible; private final boolean showCoverArt;
private final boolean showAlbum;
private List<Child> songs; private List<Child> songs;
public SongHorizontalAdapter(ClickCallback click, boolean isCoverVisible) { public SongHorizontalAdapter(ClickCallback click, boolean showCoverArt, boolean showAlbum) {
this.click = click; this.click = click;
this.isCoverVisible = isCoverVisible; this.showCoverArt = showCoverArt;
this.showAlbum = showAlbum;
this.songs = Collections.emptyList(); this.songs = Collections.emptyList();
} }
@@ -47,7 +49,7 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAd
Child song = songs.get(position); Child song = songs.get(position);
holder.item.searchResultSongTitleTextView.setText(MusicUtil.getReadableString(song.getTitle())); holder.item.searchResultSongTitleTextView.setText(MusicUtil.getReadableString(song.getTitle()));
holder.item.searchResultSongSubtitleTextView.setText(holder.itemView.getContext().getString(R.string.song_subtitle_formatter, MusicUtil.getReadableString(song.getArtist()), MusicUtil.getReadableDurationString(song.getDuration() != null ? song.getDuration() : 0, false))); holder.item.searchResultSongSubtitleTextView.setText(holder.itemView.getContext().getString(R.string.song_subtitle_formatter, MusicUtil.getReadableString(this.showAlbum ? song.getAlbum() : song.getArtist()), MusicUtil.getReadableDurationString(song.getDuration() != null ? song.getDuration() : 0, false)));
holder.item.trackNumberTextView.setText(MusicUtil.getReadableTrackNumber(holder.itemView.getContext(), song.getTrack())); holder.item.trackNumberTextView.setText(MusicUtil.getReadableTrackNumber(holder.itemView.getContext(), song.getTrack()));
if (DownloadUtil.getDownloadTracker(holder.itemView.getContext()).isDownloaded(song.getId())) { if (DownloadUtil.getDownloadTracker(holder.itemView.getContext()).isDownloaded(song.getId())) {
@@ -56,16 +58,15 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAd
holder.item.searchResultDowanloadIndicatorImageView.setVisibility(View.GONE); holder.item.searchResultDowanloadIndicatorImageView.setVisibility(View.GONE);
} }
if (isCoverVisible) CustomGlideRequest.Builder if (showCoverArt) CustomGlideRequest.Builder
.from(holder.itemView.getContext(), song.getCoverArtId()) .from(holder.itemView.getContext(), song.getCoverArtId(), CustomGlideRequest.ResourceType.Song)
.build() .build()
.into(holder.item.songCoverImageView); .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 (!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())) {
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())) {
holder.item.differentDiskDivider.setVisibility(View.VISIBLE); holder.item.differentDiskDivider.setVisibility(View.VISIBLE);
} }
} }
@@ -114,7 +115,7 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAd
public void onClick() { public void onClick() {
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
bundle.putParcelableArrayList(Constants.TRACKS_OBJECT, new ArrayList<>(MusicUtil.limitPlayableMedia(songs, getBindingAdapterPosition()))); bundle.putParcelableArrayList(Constants.TRACKS_OBJECT, new ArrayList<>(MusicUtil.limitPlayableMedia(songs, getBindingAdapterPosition())));
bundle.putInt(Constants.ITEM_POSITION, MusicUtil.getPlayableMediaPosition(getBindingAdapterPosition())); bundle.putInt(Constants.ITEM_POSITION, MusicUtil.getPlayableMediaPosition(songs, getBindingAdapterPosition()));
click.onMediaClick(bundle); click.onMediaClick(bundle);
} }

View File

@@ -67,7 +67,7 @@ public class PlaylistChooserDialog extends DialogFragment implements ClickCallba
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
bundle.putParcelable(Constants.TRACK_OBJECT, playlistChooserViewModel.getSongToAdd()); bundle.putParcelable(Constants.TRACK_OBJECT, playlistChooserViewModel.getSongToAdd());
PlaylistEditorDialog dialog = new PlaylistEditorDialog(); PlaylistEditorDialog dialog = new PlaylistEditorDialog(null);
dialog.setArguments(bundle); dialog.setArguments(bundle);
dialog.show(requireActivity().getSupportFragmentManager(), null); dialog.show(requireActivity().getSupportFragmentManager(), null);

View File

@@ -2,8 +2,12 @@ package com.cappielloantonio.tempo.ui.dialog;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.Dialog; import android.app.Dialog;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.View;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.fragment.app.DialogFragment; import androidx.fragment.app.DialogFragment;
@@ -14,9 +18,11 @@ import androidx.recyclerview.widget.RecyclerView;
import com.cappielloantonio.tempo.R; import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.DialogPlaylistEditorBinding; import com.cappielloantonio.tempo.databinding.DialogPlaylistEditorBinding;
import com.cappielloantonio.tempo.interfaces.PlaylistCallback;
import com.cappielloantonio.tempo.ui.adapter.PlaylistDialogSongHorizontalAdapter; import com.cappielloantonio.tempo.ui.adapter.PlaylistDialogSongHorizontalAdapter;
import com.cappielloantonio.tempo.util.Constants; import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil; import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.util.Preferences;
import com.cappielloantonio.tempo.viewmodel.PlaylistEditorViewModel; import com.cappielloantonio.tempo.viewmodel.PlaylistEditorViewModel;
import java.util.Collections; import java.util.Collections;
@@ -25,10 +31,15 @@ import java.util.Objects;
public class PlaylistEditorDialog extends DialogFragment { public class PlaylistEditorDialog extends DialogFragment {
private DialogPlaylistEditorBinding bind; private DialogPlaylistEditorBinding bind;
private PlaylistEditorViewModel playlistEditorViewModel; private PlaylistEditorViewModel playlistEditorViewModel;
private PlaylistCallback playlistCallback;
private String playlistName; private String playlistName;
private PlaylistDialogSongHorizontalAdapter playlistDialogSongHorizontalAdapter; private PlaylistDialogSongHorizontalAdapter playlistDialogSongHorizontalAdapter;
public PlaylistEditorDialog(PlaylistCallback playlistCallback) {
this.playlistCallback = playlistCallback;
}
@NonNull @NonNull
@Override @Override
public Dialog onCreateDialog(Bundle savedInstanceState) { public Dialog onCreateDialog(Bundle savedInstanceState) {
@@ -85,14 +96,24 @@ public class PlaylistEditorDialog extends DialogFragment {
playlistEditorViewModel.updatePlaylist(playlistName); playlistEditorViewModel.updatePlaylist(playlistName);
} }
Objects.requireNonNull(getDialog()).dismiss(); dialogDismiss();
} }
}); });
((AlertDialog) Objects.requireNonNull(getDialog())).getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener(v -> { ((AlertDialog) Objects.requireNonNull(getDialog())).getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener(v -> {
playlistEditorViewModel.deletePlaylist(); playlistEditorViewModel.deletePlaylist();
Objects.requireNonNull(getDialog()).dismiss(); dialogDismiss();
}); });
bind.playlistShareButton.setOnClickListener(view -> {
playlistEditorViewModel.sharePlaylist().observe(requireActivity(), sharedPlaylist -> {
ClipboardManager clipboardManager = (ClipboardManager) requireActivity().getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clipData = ClipData.newPlainText(getString(R.string.app_name), sharedPlaylist.getUrl());
clipboardManager.setPrimaryClip(clipData);
});
});
bind.playlistShareButton.setVisibility(Preferences.isSharingEnabled() ? View.VISIBLE : View.GONE);
} }
private void initSongsView() { private void initSongsView() {
@@ -102,7 +123,9 @@ public class PlaylistEditorDialog extends DialogFragment {
playlistDialogSongHorizontalAdapter = new PlaylistDialogSongHorizontalAdapter(); playlistDialogSongHorizontalAdapter = new PlaylistDialogSongHorizontalAdapter();
bind.playlistSongRecyclerView.setAdapter(playlistDialogSongHorizontalAdapter); bind.playlistSongRecyclerView.setAdapter(playlistDialogSongHorizontalAdapter);
playlistEditorViewModel.getPlaylistSongLiveList().observe(requireActivity(), songs -> playlistDialogSongHorizontalAdapter.setItems(songs)); playlistEditorViewModel.getPlaylistSongLiveList().observe(requireActivity(), songs -> {
if (songs != null) playlistDialogSongHorizontalAdapter.setItems(songs);
});
new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.LEFT) { new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.LEFT) {
int originalPosition = -1; int originalPosition = -1;
@@ -157,4 +180,9 @@ public class PlaylistEditorDialog extends DialogFragment {
return true; return true;
} }
private void dialogDismiss() {
Objects.requireNonNull(getDialog()).dismiss();
playlistCallback.onDismiss();
}
} }

View File

@@ -0,0 +1,137 @@
package com.cappielloantonio.tempo.ui.dialog;
import android.app.AlertDialog;
import android.app.Dialog;
import android.os.Bundle;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.fragment.app.DialogFragment;
import androidx.lifecycle.ViewModelProvider;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.DialogShareUpdateBinding;
import com.cappielloantonio.tempo.util.UIUtil;
import com.cappielloantonio.tempo.viewmodel.HomeViewModel;
import com.cappielloantonio.tempo.viewmodel.ShareBottomSheetViewModel;
import com.google.android.material.datepicker.CalendarConstraints;
import com.google.android.material.datepicker.DateValidatorPointForward;
import com.google.android.material.datepicker.MaterialDatePicker;
import com.google.android.material.datepicker.MaterialPickerOnPositiveButtonClickListener;
import java.util.Calendar;
import java.util.Date;
import java.util.Objects;
public class ShareUpdateDialog extends DialogFragment {
private static final String TAG = "ShareUpdateDialog";
private DialogShareUpdateBinding bind;
private HomeViewModel homeViewModel;
private ShareBottomSheetViewModel shareBottomSheetViewModel;
private MaterialDatePicker<Long> datePicker;
private String descriptionTextView;
private String expirationTextView;
private long expiration;
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
homeViewModel = new ViewModelProvider(requireActivity()).get(HomeViewModel.class);
shareBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(ShareBottomSheetViewModel.class);
bind = DialogShareUpdateBinding.inflate(getLayoutInflater());
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setView(bind.getRoot())
.setTitle(R.string.share_update_dialog_title)
.setPositiveButton(R.string.share_update_dialog_positive_button, (dialog, id) -> {
})
.setNegativeButton(R.string.share_update_dialog_negative_button, (dialog, id) -> dialog.cancel());
return builder.create();
}
@Override
public void onStart() {
super.onStart();
setShareInfo();
setShareCalendar();
setButtonAction();
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void setShareInfo() {
if (shareBottomSheetViewModel.getShare() != null) {
bind.shareDescriptionTextView.setText(shareBottomSheetViewModel.getShare().getDescription());
// bind.shareExpirationTextView.setText(shareBottomSheetViewModel.getShare().getExpires());
}
}
private void setShareCalendar() {
expiration = shareBottomSheetViewModel.getShare().getExpires().getTime();
bind.shareExpirationTextView.setText(UIUtil.getReadableDate(new Date(expiration)));
bind.shareExpirationTextView.setFocusable(false);
bind.shareExpirationTextView.setOnLongClickListener(null);
bind.shareExpirationTextView.setOnClickListener(view -> {
CalendarConstraints constraints = new CalendarConstraints.Builder()
.setValidator(DateValidatorPointForward.now())
.build();
datePicker = MaterialDatePicker.Builder.datePicker()
.setCalendarConstraints(constraints)
.setSelection(expiration)
.build();
datePicker.addOnPositiveButtonClickListener(selection -> {
expiration = selection;
bind.shareExpirationTextView.setText(UIUtil.getReadableDate(new Date(selection)));
});
datePicker.show(requireActivity().getSupportFragmentManager(), null);
});
}
private void setButtonAction() {
((AlertDialog) Objects.requireNonNull(getDialog())).getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> {
if (validateInput()) {
updateShare();
Objects.requireNonNull(getDialog()).dismiss();
}
});
}
private boolean validateInput() {
descriptionTextView = Objects.requireNonNull(bind.shareDescriptionTextView.getText()).toString().trim();
expirationTextView = Objects.requireNonNull(bind.shareExpirationTextView.getText()).toString().trim();
if (TextUtils.isEmpty(descriptionTextView)) {
bind.shareDescriptionTextView.setError(getString(R.string.error_required));
return false;
}
if (TextUtils.isEmpty(expirationTextView)) {
bind.shareExpirationTextView.setError(getString(R.string.error_required));
return false;
}
return true;
}
private void updateShare() {
shareBottomSheetViewModel.updateShare(descriptionTextView, expiration);
homeViewModel.refreshShares(requireActivity());
}
}

View File

@@ -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);
}
}
}

View File

@@ -172,7 +172,7 @@ public class AlbumPageFragment extends Fragment implements ClickCallback {
private void initBackCover() { private void initBackCover() {
CustomGlideRequest.Builder CustomGlideRequest.Builder
.from(requireContext(), albumPageViewModel.getAlbum().getCoverArtId()) .from(requireContext(), albumPageViewModel.getAlbum().getCoverArtId(), CustomGlideRequest.ResourceType.Album)
.build() .build()
.into(bind.albumCoverImageView); .into(bind.albumCoverImageView);
} }
@@ -181,7 +181,7 @@ public class AlbumPageFragment extends Fragment implements ClickCallback {
bind.songRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext())); bind.songRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
bind.songRecyclerView.setHasFixedSize(true); bind.songRecyclerView.setHasFixedSize(true);
songHorizontalAdapter = new SongHorizontalAdapter(this, false); songHorizontalAdapter = new SongHorizontalAdapter(this, false, false);
bind.songRecyclerView.setAdapter(songHorizontalAdapter); bind.songRecyclerView.setAdapter(songHorizontalAdapter);
albumPageViewModel.getAlbumSongLiveList().observe(getViewLifecycleOwner(), songs -> songHorizontalAdapter.setItems(songs)); albumPageViewModel.getAlbumSongLiveList().observe(getViewLifecycleOwner(), songs -> songHorizontalAdapter.setItems(songs));

View File

@@ -119,7 +119,7 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
bind.bioMoreTextViewClickable.setVisibility(artistInfo.getLastFmUrl() != null ? View.VISIBLE : View.GONE); bind.bioMoreTextViewClickable.setVisibility(artistInfo.getLastFmUrl() != null ? View.VISIBLE : View.GONE);
if (getContext() != null && bind != null) CustomGlideRequest.Builder if (getContext() != null && bind != null) CustomGlideRequest.Builder
.from(requireContext(), artistPageViewModel.getArtist().getId()) .from(requireContext(), artistPageViewModel.getArtist().getId(), CustomGlideRequest.ResourceType.Artist)
.build() .build()
.into(bind.artistBackdropImageView); .into(bind.artistBackdropImageView);
@@ -164,7 +164,7 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
private void initTopSongsView() { private void initTopSongsView() {
bind.mostStreamedSongRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext())); bind.mostStreamedSongRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
songHorizontalAdapter = new SongHorizontalAdapter(this, true); songHorizontalAdapter = new SongHorizontalAdapter(this, true, true);
bind.mostStreamedSongRecyclerView.setAdapter(songHorizontalAdapter); bind.mostStreamedSongRecyclerView.setAdapter(songHorizontalAdapter);
artistPageViewModel.getArtistTopSongList().observe(getViewLifecycleOwner(), songs -> { artistPageViewModel.getArtistTopSongList().observe(getViewLifecycleOwner(), songs -> {
if (songs == null) { if (songs == null) {

View File

@@ -7,6 +7,7 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.PopupMenu; import android.widget.PopupMenu;
import androidx.activity.OnBackPressedCallback;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
@@ -37,6 +38,8 @@ import java.util.Objects;
@UnstableApi @UnstableApi
public class DownloadFragment extends Fragment implements ClickCallback { public class DownloadFragment extends Fragment implements ClickCallback {
private static final String TAG = "DownloadFragment";
private FragmentDownloadBinding bind; private FragmentDownloadBinding bind;
private MainActivity activity; private MainActivity activity;
private DownloadViewModel downloadViewModel; private DownloadViewModel downloadViewModel;
@@ -160,6 +163,23 @@ public class DownloadFragment extends Fragment implements ClickCallback {
} }
bind.downloadedGoBackImageView.setVisibility(stack.size() > 1 ? View.VISIBLE : View.GONE); 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();
}
}); });
} }
@@ -234,4 +254,9 @@ public class DownloadFragment extends Fragment implements ClickCallback {
public void onMediaLongClick(Bundle bundle) { public void onMediaLongClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.songBottomSheetDialog, bundle); Navigation.findNavController(requireView()).navigate(R.id.songBottomSheetDialog, bundle);
} }
@Override
public void onDownloadGroupLongClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.downloadBottomSheetDialog, bundle);
}
} }

View File

@@ -1,11 +1,13 @@
package com.cappielloantonio.tempo.ui.fragment; package com.cappielloantonio.tempo.ui.fragment;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@@ -34,6 +36,7 @@ import com.cappielloantonio.tempo.service.DownloaderManager;
import com.cappielloantonio.tempo.service.MediaManager; import com.cappielloantonio.tempo.service.MediaManager;
import com.cappielloantonio.tempo.service.MediaService; import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.subsonic.models.Child; import com.cappielloantonio.tempo.subsonic.models.Child;
import com.cappielloantonio.tempo.subsonic.models.Share;
import com.cappielloantonio.tempo.ui.activity.MainActivity; import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.AlbumAdapter; import com.cappielloantonio.tempo.ui.adapter.AlbumAdapter;
import com.cappielloantonio.tempo.ui.adapter.AlbumHorizontalAdapter; import com.cappielloantonio.tempo.ui.adapter.AlbumHorizontalAdapter;
@@ -41,6 +44,7 @@ import com.cappielloantonio.tempo.ui.adapter.ArtistAdapter;
import com.cappielloantonio.tempo.ui.adapter.ArtistHorizontalAdapter; import com.cappielloantonio.tempo.ui.adapter.ArtistHorizontalAdapter;
import com.cappielloantonio.tempo.ui.adapter.DiscoverSongAdapter; import com.cappielloantonio.tempo.ui.adapter.DiscoverSongAdapter;
import com.cappielloantonio.tempo.ui.adapter.GridTrackAdapter; import com.cappielloantonio.tempo.ui.adapter.GridTrackAdapter;
import com.cappielloantonio.tempo.ui.adapter.ShareHorizontalAdapter;
import com.cappielloantonio.tempo.ui.adapter.SimilarTrackAdapter; import com.cappielloantonio.tempo.ui.adapter.SimilarTrackAdapter;
import com.cappielloantonio.tempo.ui.adapter.SongHorizontalAdapter; import com.cappielloantonio.tempo.ui.adapter.SongHorizontalAdapter;
import com.cappielloantonio.tempo.ui.adapter.YearAdapter; import com.cappielloantonio.tempo.ui.adapter.YearAdapter;
@@ -77,6 +81,7 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
private AlbumHorizontalAdapter newReleasesAlbumAdapter; private AlbumHorizontalAdapter newReleasesAlbumAdapter;
private YearAdapter yearAdapter; private YearAdapter yearAdapter;
private GridTrackAdapter gridTrackAdapter; private GridTrackAdapter gridTrackAdapter;
private ShareHorizontalAdapter shareHorizontalAdapter;
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture; private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
@@ -112,6 +117,7 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
initYearSongView(); initYearSongView();
initRecentAddedAlbumView(); initRecentAddedAlbumView();
initGridView(); initGridView();
initSharesView();
} }
@Override @Override
@@ -121,6 +127,12 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
initializeMediaBrowser(); initializeMediaBrowser();
} }
@Override
public void onResume() {
super.onResume();
refreshSharesView();
}
@Override @Override
public void onStop() { public void onStop() {
releaseMediaBrowser(); releaseMediaBrowser();
@@ -228,6 +240,11 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
homeViewModel.refreshMostRecentlyAddedAlbums(getViewLifecycleOwner()); homeViewModel.refreshMostRecentlyAddedAlbums(getViewLifecycleOwner());
return true; return true;
}); });
bind.sharesTextViewRefreshable.setOnLongClickListener(v -> {
homeViewModel.refreshShares(getViewLifecycleOwner());
return true;
});
} }
private void initSyncStarredView() { private void initSyncStarredView() {
@@ -290,11 +307,14 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
bind.discoverSongViewPager.setOffscreenPageLimit(1); bind.discoverSongViewPager.setOffscreenPageLimit(1);
homeViewModel.getDiscoverSongSample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), songs -> { homeViewModel.getDiscoverSongSample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), songs -> {
if (songs == null) { 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); if (bind != null) bind.homeDiscoverSector.setVisibility(View.GONE);
} else { } else {
if (bind != null) bind.homeDiscoveryPlaceholder.placeholder.setVisibility(View.GONE); if (bind != null)
if (bind != null) bind.homeDiscoverSector.setVisibility(!songs.isEmpty() ? View.VISIBLE : View.GONE); bind.homeDiscoveryPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.homeDiscoverSector.setVisibility(!songs.isEmpty() ? View.VISIBLE : View.GONE);
discoverSongAdapter.setItems(songs); discoverSongAdapter.setItems(songs);
} }
@@ -311,11 +331,14 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
bind.similarTracksRecyclerView.setAdapter(similarMusicAdapter); bind.similarTracksRecyclerView.setAdapter(similarMusicAdapter);
homeViewModel.getStarredTracksSample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), songs -> { homeViewModel.getStarredTracksSample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), songs -> {
if (songs == null) { 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); if (bind != null) bind.homeSimilarTracksSector.setVisibility(View.GONE);
} else { } else {
if (bind != null) bind.homeSimilarTracksPlaceholder.placeholder.setVisibility(View.GONE); if (bind != null)
if (bind != null) bind.homeSimilarTracksSector.setVisibility(!songs.isEmpty() ? View.VISIBLE : View.GONE); bind.homeSimilarTracksPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.homeSimilarTracksSector.setVisibility(!songs.isEmpty() ? View.VISIBLE : View.GONE);
similarMusicAdapter.setItems(songs); similarMusicAdapter.setItems(songs);
} }
@@ -333,11 +356,14 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
bind.bestOfArtistRecyclerView.setAdapter(bestOfArtistAdapter); bind.bestOfArtistRecyclerView.setAdapter(bestOfArtistAdapter);
homeViewModel.getBestOfArtists(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), artists -> { homeViewModel.getBestOfArtists(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), artists -> {
if (artists == null) { 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); if (bind != null) bind.homeBestOfArtistSector.setVisibility(View.GONE);
} else { } else {
if (bind != null) bind.homeBestOfArtistPlaceholder.placeholder.setVisibility(View.GONE); if (bind != null)
if (bind != null) bind.homeBestOfArtistSector.setVisibility(!artists.isEmpty() ? View.VISIBLE : View.GONE); bind.homeBestOfArtistPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.homeBestOfArtistSector.setVisibility(!artists.isEmpty() ? View.VISIBLE : View.GONE);
bestOfArtistAdapter.setItems(artists); bestOfArtistAdapter.setItems(artists);
} }
@@ -355,12 +381,16 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
bind.radioArtistRecyclerView.setAdapter(radioArtistAdapter); bind.radioArtistRecyclerView.setAdapter(radioArtistAdapter);
homeViewModel.getStarredArtistsSample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), artists -> { homeViewModel.getStarredArtistsSample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), artists -> {
if (artists == null) { 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); if (bind != null) bind.homeRadioArtistSector.setVisibility(View.GONE);
} else { } else {
if (bind != null) bind.homeRadioArtistPlaceholder.placeholder.setVisibility(View.GONE); if (bind != null)
if (bind != null) bind.homeRadioArtistSector.setVisibility(!artists.isEmpty() ? View.VISIBLE : View.GONE); bind.homeRadioArtistPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null) bind.afterRadioArtistDivider.setVisibility(!artists.isEmpty() ? View.VISIBLE : 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); radioArtistAdapter.setItems(artists);
} }
@@ -378,14 +408,18 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
gridTrackAdapter = new GridTrackAdapter(this); gridTrackAdapter = new GridTrackAdapter(this);
bind.gridTracksRecyclerView.setAdapter(gridTrackAdapter); bind.gridTracksRecyclerView.setAdapter(gridTrackAdapter);
homeViewModel.getGridSongSample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), chronologies -> { homeViewModel.getDiscoverSongSample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), music -> {
if (chronologies == null || chronologies.size() == 0) { if (music != null) {
if (bind != null) bind.homeGridTracksSector.setVisibility(View.GONE); homeViewModel.getGridSongSample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), chronologies -> {
if (bind != null) bind.afterGridDivider.setVisibility(View.GONE); if (chronologies == null || chronologies.size() == 0) {
} else { if (bind != null) bind.homeGridTracksSector.setVisibility(View.GONE);
if (bind != null) bind.homeGridTracksSector.setVisibility(View.VISIBLE); if (bind != null) bind.afterGridDivider.setVisibility(View.GONE);
if (bind != null) bind.afterGridDivider.setVisibility(View.VISIBLE); } else {
gridTrackAdapter.setItems(chronologies); if (bind != null) bind.homeGridTracksSector.setVisibility(View.VISIBLE);
if (bind != null) bind.afterGridDivider.setVisibility(View.VISIBLE);
gridTrackAdapter.setItems(chronologies);
}
});
} }
}); });
} }
@@ -393,16 +427,20 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
private void initStarredTracksView() { private void initStarredTracksView() {
bind.starredTracksRecyclerView.setHasFixedSize(true); bind.starredTracksRecyclerView.setHasFixedSize(true);
starredSongAdapter = new SongHorizontalAdapter(this, true); starredSongAdapter = new SongHorizontalAdapter(this, true, false);
bind.starredTracksRecyclerView.setAdapter(starredSongAdapter); bind.starredTracksRecyclerView.setAdapter(starredSongAdapter);
homeViewModel.getStarredTracks(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), songs -> { homeViewModel.getStarredTracks(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), songs -> {
if (songs == null) { if (songs == null) {
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); if (bind != null) bind.starredTracksSector.setVisibility(View.GONE);
} else { } else {
if (bind != null) bind.starredTracksPlaceholder.placeholder.setVisibility(View.GONE); if (bind != null)
if (bind != null) bind.starredTracksSector.setVisibility(!songs.isEmpty() ? View.VISIBLE : View.GONE); bind.starredTracksPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null) bind.starredTracksRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), UIUtil.getSpanCount(songs.size(), 5), GridLayoutManager.HORIZONTAL, false)); 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); starredSongAdapter.setItems(songs);
} }
@@ -428,12 +466,16 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
bind.starredAlbumsRecyclerView.setAdapter(starredAlbumAdapter); bind.starredAlbumsRecyclerView.setAdapter(starredAlbumAdapter);
homeViewModel.getStarredAlbums(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), albums -> { homeViewModel.getStarredAlbums(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), albums -> {
if (albums == null) { 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); if (bind != null) bind.starredAlbumsSector.setVisibility(View.GONE);
} else { } else {
if (bind != null) bind.starredAlbumsPlaceholder.placeholder.setVisibility(View.GONE); if (bind != null)
if (bind != null) bind.starredAlbumsSector.setVisibility(!albums.isEmpty() ? View.VISIBLE : View.GONE); bind.starredAlbumsPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null) bind.starredAlbumsRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), UIUtil.getSpanCount(albums.size(), 5), GridLayoutManager.HORIZONTAL, false)); 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); starredAlbumAdapter.setItems(albums);
} }
@@ -459,13 +501,18 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
bind.starredArtistsRecyclerView.setAdapter(starredArtistAdapter); bind.starredArtistsRecyclerView.setAdapter(starredArtistAdapter);
homeViewModel.getStarredArtists(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), artists -> { homeViewModel.getStarredArtists(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), artists -> {
if (artists == null) { 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); if (bind != null) bind.starredArtistsSector.setVisibility(View.GONE);
} else { } else {
if (bind != null) bind.starredArtistsPlaceholder.placeholder.setVisibility(View.GONE); if (bind != null)
if (bind != null) bind.starredArtistsSector.setVisibility(!artists.isEmpty() ? View.VISIBLE : View.GONE); bind.starredArtistsPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null) bind.afterFavoritesDivider.setVisibility(!artists.isEmpty() ? View.VISIBLE : View.GONE); if (bind != null)
if (bind != null) bind.starredArtistsRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), UIUtil.getSpanCount(artists.size(), 5), GridLayoutManager.HORIZONTAL, false)); 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); starredArtistAdapter.setItems(artists);
} }
@@ -491,12 +538,16 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
bind.newReleasesRecyclerView.setAdapter(newReleasesAlbumAdapter); bind.newReleasesRecyclerView.setAdapter(newReleasesAlbumAdapter);
homeViewModel.getRecentlyReleasedAlbums(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), albums -> { homeViewModel.getRecentlyReleasedAlbums(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), albums -> {
if (albums == null) { 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); if (bind != null) bind.homeNewReleasesSector.setVisibility(View.GONE);
} else { } else {
if (bind != null) bind.homeNewReleasesPlaceholder.placeholder.setVisibility(View.GONE); if (bind != null)
if (bind != null) bind.homeNewReleasesSector.setVisibility(!albums.isEmpty() ? View.VISIBLE : View.GONE); bind.homeNewReleasesPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null) bind.newReleasesRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), UIUtil.getSpanCount(albums.size(), 5), GridLayoutManager.HORIZONTAL, false)); 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); newReleasesAlbumAdapter.setItems(albums);
} }
@@ -523,11 +574,14 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
bind.yearsRecyclerView.setAdapter(yearAdapter); bind.yearsRecyclerView.setAdapter(yearAdapter);
homeViewModel.getYearList(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), years -> { homeViewModel.getYearList(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), years -> {
if (years == null) { 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); if (bind != null) bind.homeFlashbackSector.setVisibility(View.GONE);
} else { } else {
if (bind != null) bind.homeFlashbackPlaceholder.placeholder.setVisibility(View.GONE); if (bind != null)
if (bind != null) bind.homeFlashbackSector.setVisibility(!years.isEmpty() ? View.VISIBLE : View.GONE); bind.homeFlashbackPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.homeFlashbackSector.setVisibility(!years.isEmpty() ? View.VISIBLE : View.GONE);
yearAdapter.setItems(years); yearAdapter.setItems(years);
} }
@@ -545,11 +599,14 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
bind.mostPlayedAlbumsRecyclerView.setAdapter(mostPlayedAlbumAdapter); bind.mostPlayedAlbumsRecyclerView.setAdapter(mostPlayedAlbumAdapter);
homeViewModel.getMostPlayedAlbums(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), albums -> { homeViewModel.getMostPlayedAlbums(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), albums -> {
if (albums == null) { 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); if (bind != null) bind.homeMostPlayedAlbumsSector.setVisibility(View.GONE);
} else { } else {
if (bind != null) bind.homeMostPlayedAlbumsPlaceholder.placeholder.setVisibility(View.GONE); if (bind != null)
if (bind != null) bind.homeMostPlayedAlbumsSector.setVisibility(!albums.isEmpty() ? View.VISIBLE : View.GONE); bind.homeMostPlayedAlbumsPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.homeMostPlayedAlbumsSector.setVisibility(!albums.isEmpty() ? View.VISIBLE : View.GONE);
// if (albums.size() < 5) reorder(); // if (albums.size() < 5) reorder();
mostPlayedAlbumAdapter.setItems(albums); mostPlayedAlbumAdapter.setItems(albums);
@@ -568,11 +625,14 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
bind.recentlyPlayedAlbumsRecyclerView.setAdapter(recentlyPlayedAlbumAdapter); bind.recentlyPlayedAlbumsRecyclerView.setAdapter(recentlyPlayedAlbumAdapter);
homeViewModel.getRecentlyPlayedAlbumList(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), albums -> { homeViewModel.getRecentlyPlayedAlbumList(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), albums -> {
if (albums == null) { 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); if (bind != null) bind.homeRecentlyPlayedAlbumsSector.setVisibility(View.GONE);
} else { } else {
if (bind != null) bind.homeRecentlyPlayedAlbumsPlaceholder.placeholder.setVisibility(View.GONE); if (bind != null)
if (bind != null) bind.homeRecentlyPlayedAlbumsSector.setVisibility(!albums.isEmpty() ? View.VISIBLE : View.GONE); bind.homeRecentlyPlayedAlbumsPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.homeRecentlyPlayedAlbumsSector.setVisibility(!albums.isEmpty() ? View.VISIBLE : View.GONE);
recentlyPlayedAlbumAdapter.setItems(albums); recentlyPlayedAlbumAdapter.setItems(albums);
} }
@@ -590,11 +650,14 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
bind.recentlyAddedAlbumsRecyclerView.setAdapter(recentlyAddedAlbumAdapter); bind.recentlyAddedAlbumsRecyclerView.setAdapter(recentlyAddedAlbumAdapter);
homeViewModel.getMostRecentlyAddedAlbums(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), albums -> { homeViewModel.getMostRecentlyAddedAlbums(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), albums -> {
if (albums == null) { 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); if (bind != null) bind.homeRecentlyAddedAlbumsSector.setVisibility(View.GONE);
} else { } else {
if (bind != null) bind.homeRecentlyAddedAlbumsPlaceholder.placeholder.setVisibility(View.GONE); if (bind != null)
if (bind != null) bind.homeRecentlyAddedAlbumsSector.setVisibility(!albums.isEmpty() ? View.VISIBLE : View.GONE); bind.homeRecentlyAddedAlbumsPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.homeRecentlyAddedAlbumsSector.setVisibility(!albums.isEmpty() ? View.VISIBLE : View.GONE);
recentlyAddedAlbumAdapter.setItems(albums); recentlyAddedAlbumAdapter.setItems(albums);
} }
@@ -604,6 +667,50 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
recentAddedAlbumSnapHelper.attachToRecyclerView(bind.recentlyAddedAlbumsRecyclerView); recentAddedAlbumSnapHelper.attachToRecyclerView(bind.recentlyAddedAlbumsRecyclerView);
} }
private void initSharesView() {
bind.sharesRecyclerView.setHasFixedSize(true);
shareHorizontalAdapter = new ShareHorizontalAdapter(this);
bind.sharesRecyclerView.setAdapter(shareHorizontalAdapter);
if (Preferences.isSharingEnabled()) {
homeViewModel.getShares(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), shares -> {
if (shares == null) {
if (bind != null)
bind.sharesPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.sharesSector.setVisibility(View.GONE);
} else {
if (bind != null) bind.sharesPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.sharesSector.setVisibility(!shares.isEmpty() ? View.VISIBLE : View.GONE);
if (bind != null)
bind.sharesRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), UIUtil.getSpanCount(shares.size(), 10), GridLayoutManager.HORIZONTAL, false));
shareHorizontalAdapter.setItems(shares);
}
});
}
SnapHelper starredTrackSnapHelper = new PagerSnapHelper();
starredTrackSnapHelper.attachToRecyclerView(bind.sharesRecyclerView);
bind.sharesRecyclerView.addItemDecoration(
new DotsIndicatorDecoration(
getResources().getDimensionPixelSize(R.dimen.radius),
getResources().getDimensionPixelSize(R.dimen.radius) * 4,
getResources().getDimensionPixelSize(R.dimen.dots_height),
requireContext().getResources().getColor(R.color.titleTextColor, null),
requireContext().getResources().getColor(R.color.titleTextColor, null))
);
}
private void refreshSharesView() {
final Handler handler = new Handler();
final Runnable runnable = () -> {
if (Preferences.isSharingEnabled()) homeViewModel.refreshShares(getViewLifecycleOwner());
};
handler.postDelayed(runnable, 100);
}
private void setSlideViewOffset(ViewPager2 viewPager, float pageOffset, float pageMargin) { private void setSlideViewOffset(ViewPager2 viewPager, float pageOffset, float pageMargin) {
viewPager.setPageTransformer((page, position) -> { viewPager.setPageTransformer((page, position) -> {
float myOffset = position * -(2 * pageOffset + pageMargin); float myOffset = position * -(2 * pageOffset + pageMargin);
@@ -619,25 +726,6 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
}); });
} }
public void reorder() {
if (bind != null) {
// bind.homeLinearLayoutContainer.removeAllViews();
// bind.homeLinearLayoutContainer.addView(bind.homeDiscoverSector);
// bind.homeLinearLayoutContainer.addView(bind.homeSimilarTracksSector);
// bind.homeLinearLayoutContainer.addView(bind.homeRadioArtistSector);
// bind.homeLinearLayoutContainer.addView(bind.homeGridTracksSector);
// bind.homeLinearLayoutContainer.addView(bind.starredTracksSector);
// bind.homeLinearLayoutContainer.addView(bind.starredAlbumsSector);
// bind.homeLinearLayoutContainer.addView(bind.starredArtistsSector);
// bind.homeLinearLayoutContainer.addView(bind.homeRecentlyAddedAlbumsSector);
// bind.homeLinearLayoutContainer.addView(bind.homeFlashbackSector);
// bind.homeLinearLayoutContainer.addView(bind.homeMostPlayedAlbumsSector);
// bind.homeLinearLayoutContainer.addView(bind.homeRecentlyPlayedAlbumsSector);
}
}
private void initializeMediaBrowser() { private void initializeMediaBrowser() {
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync(); mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
} }
@@ -722,4 +810,16 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
public void onYearClick(Bundle bundle) { public void onYearClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.songListPageFragment, bundle); Navigation.findNavController(requireView()).navigate(R.id.songListPageFragment, bundle);
} }
@Override
public void onShareClick(Bundle bundle) {
Share share = bundle.getParcelable(Constants.SHARE_OBJECT);
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(share.getUrl())).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
@Override
public void onShareLongClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.shareBottomSheetDialog, bundle);
}
} }

View File

@@ -85,13 +85,15 @@ public class IndexFragment extends Fragment implements ClickCallback {
} }
private void initDirectoryListView() { private void initDirectoryListView() {
MusicFolder musicFolder = getArguments().getParcelable(Constants.MUSIC_FOLDER_OBJECT);
bind.indexRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext())); bind.indexRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
bind.indexRecyclerView.setHasFixedSize(true); bind.indexRecyclerView.setHasFixedSize(true);
musicIndexAdapter = new MusicIndexAdapter(this); musicIndexAdapter = new MusicIndexAdapter(this);
bind.indexRecyclerView.setAdapter(musicIndexAdapter); bind.indexRecyclerView.setAdapter(musicIndexAdapter);
indexViewModel.getIndexes().observe(getViewLifecycleOwner(), indexes -> { indexViewModel.getIndexes(musicFolder != null ? musicFolder.getId() : null).observe(getViewLifecycleOwner(), indexes -> {
if (indexes != null) { if (indexes != null) {
musicIndexAdapter.setItems(IndexUtil.getArtist(indexes)); musicIndexAdapter.setItems(IndexUtil.getArtist(indexes));
} }

View File

@@ -1,6 +1,7 @@
package com.cappielloantonio.tempo.ui.fragment; package com.cappielloantonio.tempo.ui.fragment;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@@ -18,6 +19,7 @@ import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentLibraryBinding; import com.cappielloantonio.tempo.databinding.FragmentLibraryBinding;
import com.cappielloantonio.tempo.helper.recyclerview.CustomLinearSnapHelper; import com.cappielloantonio.tempo.helper.recyclerview.CustomLinearSnapHelper;
import com.cappielloantonio.tempo.interfaces.ClickCallback; import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.interfaces.PlaylistCallback;
import com.cappielloantonio.tempo.ui.activity.MainActivity; import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.AlbumAdapter; import com.cappielloantonio.tempo.ui.adapter.AlbumAdapter;
import com.cappielloantonio.tempo.ui.adapter.ArtistAdapter; import com.cappielloantonio.tempo.ui.adapter.ArtistAdapter;
@@ -71,7 +73,7 @@ public class LibraryFragment extends Fragment implements ClickCallback {
initAlbumView(); initAlbumView();
initArtistView(); initArtistView();
initGenreView(); initGenreView();
initPlaylistSlideView(); initPlaylistView();
} }
@Override @Override
@@ -80,6 +82,12 @@ public class LibraryFragment extends Fragment implements ClickCallback {
activity.setBottomNavigationBarVisibility(true); activity.setBottomNavigationBarVisibility(true);
} }
@Override
public void onResume() {
super.onResume();
refreshPlaylistView();
}
@Override @Override
public void onDestroyView() { public void onDestroyView() {
super.onDestroyView(); super.onDestroyView();
@@ -222,7 +230,7 @@ public class LibraryFragment extends Fragment implements ClickCallback {
genreSnapHelper.attachToRecyclerView(bind.genreRecyclerView); genreSnapHelper.attachToRecyclerView(bind.genreRecyclerView);
} }
private void initPlaylistSlideView() { private void initPlaylistView() {
bind.playlistRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext())); bind.playlistRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
bind.playlistRecyclerView.setHasFixedSize(true); bind.playlistRecyclerView.setHasFixedSize(true);
@@ -244,6 +252,12 @@ public class LibraryFragment extends Fragment implements ClickCallback {
}); });
} }
private void refreshPlaylistView() {
final Handler handler = new Handler();
final Runnable runnable = () -> libraryViewModel.refreshPlaylistSample(getViewLifecycleOwner());
handler.postDelayed(runnable, 100);
}
@Override @Override
public void onAlbumClick(Bundle bundle) { public void onAlbumClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.albumPageFragment, bundle); Navigation.findNavController(requireView()).navigate(R.id.albumPageFragment, bundle);
@@ -276,7 +290,13 @@ public class LibraryFragment extends Fragment implements ClickCallback {
@Override @Override
public void onPlaylistLongClick(Bundle bundle) { public void onPlaylistLongClick(Bundle bundle) {
PlaylistEditorDialog dialog = new PlaylistEditorDialog(); PlaylistEditorDialog dialog = new PlaylistEditorDialog(new PlaylistCallback() {
@Override
public void onDismiss() {
refreshPlaylistView();
}
});
dialog.setArguments(bundle); dialog.setArguments(bundle);
dialog.show(activity.getSupportFragmentManager(), null); dialog.show(activity.getSupportFragmentManager(), null);
} }

View File

@@ -86,7 +86,7 @@ public class PlayerBottomSheetFragment extends Fragment {
} }
private void customizeBottomSheetBackground() { private void customizeBottomSheetBackground() {
bind.playerHeaderLayout.getRoot().setBackgroundColor(SurfaceColors.getColorForElevation(requireContext(), 2)); bind.playerHeaderLayout.getRoot().setBackgroundColor(SurfaceColors.getColorForElevation(requireContext(), 8));
} }
private void customizeBottomSheetAction() { private void customizeBottomSheetAction() {
@@ -161,7 +161,7 @@ public class PlayerBottomSheetFragment extends Fragment {
bind.playerHeaderLayout.playerHeaderMediaArtistLabel.setText(MusicUtil.getReadableString(mediaMetadata.extras.getString("artist"))); bind.playerHeaderLayout.playerHeaderMediaArtistLabel.setText(MusicUtil.getReadableString(mediaMetadata.extras.getString("artist")));
CustomGlideRequest.Builder CustomGlideRequest.Builder
.from(requireContext(), mediaMetadata.extras.getString("coverArtId")) .from(requireContext(), mediaMetadata.extras.getString("coverArtId"), CustomGlideRequest.ResourceType.Song)
.build() .build()
.into(bind.playerHeaderLayout.playerHeaderMediaCoverImage); .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) { private void defineProgressBarHandler(MediaBrowser mediaBrowser) {
progressBarHandler = new Handler(); progressBarHandler = new Handler();
progressBarRunnable = () -> { progressBarRunnable = () -> {

View File

@@ -6,12 +6,12 @@ import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Button; import android.widget.Button;
import android.widget.ImageView; import android.widget.ImageButton;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast;
import android.widget.ToggleButton; import android.widget.ToggleButton;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.MediaMetadata; import androidx.media3.common.MediaMetadata;
@@ -29,12 +29,14 @@ import com.cappielloantonio.tempo.databinding.InnerFragmentPlayerControllerBindi
import com.cappielloantonio.tempo.service.MediaService; import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.ui.activity.MainActivity; import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.dialog.RatingDialog; import com.cappielloantonio.tempo.ui.dialog.RatingDialog;
import com.cappielloantonio.tempo.ui.dialog.TrackInfoDialog;
import com.cappielloantonio.tempo.ui.fragment.pager.PlayerControllerHorizontalPager; import com.cappielloantonio.tempo.ui.fragment.pager.PlayerControllerHorizontalPager;
import com.cappielloantonio.tempo.util.Constants; import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil; import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.util.Preferences; import com.cappielloantonio.tempo.util.Preferences;
import com.cappielloantonio.tempo.viewmodel.PlayerBottomSheetViewModel; import com.cappielloantonio.tempo.viewmodel.PlayerBottomSheetViewModel;
import com.google.android.material.chip.Chip; import com.google.android.material.chip.Chip;
import com.google.android.material.elevation.SurfaceColors;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.MoreExecutors;
@@ -51,10 +53,8 @@ public class PlayerControllerFragment extends Fragment {
private ToggleButton skipSilenceToggleButton; private ToggleButton skipSilenceToggleButton;
private Chip playerMediaExtension; private Chip playerMediaExtension;
private TextView playerMediaBitrate; private TextView playerMediaBitrate;
private ImageView playerMediaTranscodingIcon; private ConstraintLayout playerQuickActionView;
private ImageView playerMediaTranscodingPriorityIcon; private ImageButton playerTrackInfo;
private Chip playerMediaTranscodedExtension;
private TextView playerMediaTranscodedBitrate;
private MainActivity activity; private MainActivity activity;
private PlayerBottomSheetViewModel playerBottomSheetViewModel; private PlayerBottomSheetViewModel playerBottomSheetViewModel;
@@ -70,10 +70,10 @@ public class PlayerControllerFragment extends Fragment {
playerBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(PlayerBottomSheetViewModel.class); playerBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(PlayerBottomSheetViewModel.class);
init(); init();
initQuickActionView();
initCoverLyricsSlideView(); initCoverLyricsSlideView();
initMediaListenable(); initMediaListenable();
initArtistLabelButton(); initArtistLabelButton();
initTranscodingInfo();
return view; return view;
} }
@@ -106,10 +106,19 @@ public class PlayerControllerFragment extends Fragment {
skipSilenceToggleButton = bind.getRoot().findViewById(R.id.player_skip_silence_toggle_button); skipSilenceToggleButton = bind.getRoot().findViewById(R.id.player_skip_silence_toggle_button);
playerMediaExtension = bind.getRoot().findViewById(R.id.player_media_extension); playerMediaExtension = bind.getRoot().findViewById(R.id.player_media_extension);
playerMediaBitrate = bind.getRoot().findViewById(R.id.player_media_bitrate); playerMediaBitrate = bind.getRoot().findViewById(R.id.player_media_bitrate);
playerMediaTranscodingIcon = bind.getRoot().findViewById(R.id.player_media_transcoding_audio); playerQuickActionView = bind.getRoot().findViewById(R.id.player_quick_action_view);
playerMediaTranscodingPriorityIcon = bind.getRoot().findViewById(R.id.player_media_server_transcode_priority); playerTrackInfo = bind.getRoot().findViewById(R.id.player_info_track);
playerMediaTranscodedExtension = bind.getRoot().findViewById(R.id.player_media_transcoded_extension); }
playerMediaTranscodedBitrate = bind.getRoot().findViewById(R.id.player_media_transcoded_bitrate);
private void initQuickActionView() {
playerQuickActionView.setBackgroundColor(SurfaceColors.getColorForElevation(requireContext(), 8));
playerQuickActionView.setOnClickListener(view -> {
PlayerBottomSheetFragment playerBottomSheetFragment = (PlayerBottomSheetFragment) requireActivity().getSupportFragmentManager().findFragmentByTag("PlayerBottomSheet");
if (playerBottomSheetFragment != null) {
playerBottomSheetFragment.goToQueuePage();
}
});
} }
private void initializeBrowser() { private void initializeBrowser() {
@@ -160,9 +169,7 @@ public class PlayerControllerFragment extends Fragment {
private void setMediaInfo(MediaMetadata mediaMetadata) { private void setMediaInfo(MediaMetadata mediaMetadata) {
if (mediaMetadata.extras != null) { if (mediaMetadata.extras != null) {
String extension = mediaMetadata.extras.getString("suffix", "Unknown format"); String extension = mediaMetadata.extras.getString("suffix", "Unknown format");
String bitrate = mediaMetadata.extras.getInt("bitrate", 0) != 0 String bitrate = mediaMetadata.extras.getInt("bitrate", 0) != 0 ? mediaMetadata.extras.getInt("bitrate", 0) + "kbps" : "Original";
? mediaMetadata.extras.getInt("bitrate", 0) + "kbps"
: "Original";
playerMediaExtension.setText(extension); playerMediaExtension.setText(extension);
@@ -174,38 +181,10 @@ public class PlayerControllerFragment extends Fragment {
} }
} }
String transcodingExtension = MusicUtil.getTranscodingFormatPreference(); playerTrackInfo.setOnClickListener(view -> {
String transcodingBitrate = Integer.parseInt(MusicUtil.getBitratePreference()) != 0 TrackInfoDialog dialog = new TrackInfoDialog(mediaMetadata);
? Integer.parseInt(MusicUtil.getBitratePreference()) + "kbps" dialog.show(activity.getSupportFragmentManager(), null);
: "Original"; });
if (transcodingExtension.equals("raw") && transcodingBitrate.equals("Original")) {
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);
}
} }
private void setMediaControllerUI(MediaBrowser mediaBrowser) { 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) { private void initPlaybackSpeedButton(MediaBrowser mediaBrowser) {
playbackSpeedButton.setOnClickListener(view -> { playbackSpeedButton.setOnClickListener(view -> {
float currentSpeed = Preferences.getPlaybackSpeed(); float currentSpeed = Preferences.getPlaybackSpeed();

View File

@@ -2,6 +2,7 @@ package com.cappielloantonio.tempo.ui.fragment;
import android.content.ComponentName; import android.content.ComponentName;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler;
import android.transition.Fade; import android.transition.Fade;
import android.transition.Transition; import android.transition.Transition;
import android.transition.TransitionManager; import android.transition.TransitionManager;
@@ -39,6 +40,8 @@ public class PlayerCoverFragment extends Fragment {
private InnerFragmentPlayerCoverBinding bind; private InnerFragmentPlayerCoverBinding bind;
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture; private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
private final Handler handler = new Handler();
@Override @Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
bind = InnerFragmentPlayerCoverBinding.inflate(inflater, container, false); bind = InnerFragmentPlayerCoverBinding.inflate(inflater, container, false);
@@ -72,9 +75,19 @@ public class PlayerCoverFragment extends Fragment {
bind = null; bind = null;
} }
private void initTapButtonHideTransition() {
bind.nowPlayingTapButton.setVisibility(View.VISIBLE);
handler.removeCallbacksAndMessages(null);
final Runnable runnable = () -> bind.nowPlayingTapButton.setVisibility(View.GONE);
handler.postDelayed(runnable, 10000);
}
private void initOverlay() { private void initOverlay() {
bind.nowPlayingSongCoverImageView.setOnClickListener(view -> toggleOverlayVisibility(true)); bind.nowPlayingSongCoverImageView.setOnClickListener(view -> toggleOverlayVisibility(true));
bind.nowPlayingSongCoverButtonGroup.setOnClickListener(view -> toggleOverlayVisibility(false)); bind.nowPlayingSongCoverButtonGroup.setOnClickListener(view -> toggleOverlayVisibility(false));
bind.nowPlayingTapButton.setOnClickListener(view -> toggleOverlayVisibility(true));
} }
private void toggleOverlayVisibility(boolean isVisible) { private void toggleOverlayVisibility(boolean isVisible) {
@@ -84,9 +97,12 @@ public class PlayerCoverFragment extends Fragment {
TransitionManager.beginDelayedTransition(bind.getRoot(), transition); TransitionManager.beginDelayedTransition(bind.getRoot(), transition);
bind.nowPlayingSongCoverButtonGroup.setVisibility(isVisible ? View.VISIBLE : View.GONE); bind.nowPlayingSongCoverButtonGroup.setVisibility(isVisible ? View.VISIBLE : View.GONE);
bind.nowPlayingTapButton.setVisibility(isVisible ? View.GONE : View.VISIBLE);
bind.innerButtonBottomRight.setVisibility(Preferences.isSyncronizationEnabled() ? View.VISIBLE : View.GONE); bind.innerButtonBottomRight.setVisibility(Preferences.isSyncronizationEnabled() ? View.VISIBLE : View.GONE);
bind.innerButtonBottomRightAlternative.setVisibility(Preferences.isSyncronizationEnabled() ? View.GONE : View.VISIBLE); bind.innerButtonBottomRightAlternative.setVisibility(Preferences.isSyncronizationEnabled() ? View.GONE : View.VISIBLE);
if (!isVisible) initTapButtonHideTransition();
} }
private void initInnerButton() { private void initInnerButton() {
@@ -166,7 +182,7 @@ public class PlayerCoverFragment extends Fragment {
private void setCover(MediaMetadata mediaMetadata) { private void setCover(MediaMetadata mediaMetadata) {
CustomGlideRequest.Builder 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() .build()
.into(bind.nowPlayingSongCoverImageView); .into(bind.nowPlayingSongCoverImageView);
} }

View File

@@ -25,12 +25,16 @@ import com.cappielloantonio.tempo.ui.adapter.PlayerSongQueueAdapter;
import com.cappielloantonio.tempo.util.Constants; import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.viewmodel.PlayerBottomSheetViewModel; import com.cappielloantonio.tempo.viewmodel.PlayerBottomSheetViewModel;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@UnstableApi @UnstableApi
public class PlayerQueueFragment extends Fragment implements ClickCallback { public class PlayerQueueFragment extends Fragment implements ClickCallback {
private static final String TAG = "PlayerQueueFragment";
private InnerFragmentPlayerQueueBinding bind; private InnerFragmentPlayerQueueBinding bind;
private PlayerBottomSheetViewModel playerBottomSheetViewModel; private PlayerBottomSheetViewModel playerBottomSheetViewModel;
@@ -54,6 +58,7 @@ public class PlayerQueueFragment extends Fragment implements ClickCallback {
public void onStart() { public void onStart() {
super.onStart(); super.onStart();
initializeBrowser(); initializeBrowser();
bindMediaController();
} }
@Override @Override
@@ -83,6 +88,17 @@ public class PlayerQueueFragment extends Fragment implements ClickCallback {
MediaBrowser.releaseFuture(mediaBrowserListenableFuture); MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
} }
private void bindMediaController() {
mediaBrowserListenableFuture.addListener(() -> {
try {
MediaBrowser mediaBrowser = mediaBrowserListenableFuture.get();
initShuffleButton(mediaBrowser);
} catch (Exception exception) {
exception.printStackTrace();
}
}, MoreExecutors.directExecutor());
}
private void setMediaBrowserListenableFuture() { private void setMediaBrowserListenableFuture() {
playerSongQueueAdapter.setMediaBrowserListenableFuture(mediaBrowserListenableFuture); playerSongQueueAdapter.setMediaBrowserListenableFuture(mediaBrowserListenableFuture);
} }
@@ -151,6 +167,36 @@ public class PlayerQueueFragment extends Fragment implements ClickCallback {
}).attachToRecyclerView(bind.playerQueueRecyclerView); }).attachToRecyclerView(bind.playerQueueRecyclerView);
} }
private void initShuffleButton(MediaBrowser mediaBrowser) {
bind.playerShuffleQueueFab.setOnClickListener(view -> {
int startPosition = mediaBrowser.getCurrentMediaItemIndex() + 1;
int endPosition = playerSongQueueAdapter.getItems().size() - 1;
if (startPosition < endPosition) {
ArrayList<Integer> pool = new ArrayList<>();
for (int i = startPosition; i <= endPosition; i++) {
pool.add(i);
}
while (pool.size() >= 2) {
int fromPosition = (int) (Math.random() * (pool.size()));
int positionA = pool.get(fromPosition);
pool.remove(fromPosition);
int toPosition = (int) (Math.random() * (pool.size()));
int positionB = pool.get(toPosition);
pool.remove(toPosition);
Collections.swap(playerSongQueueAdapter.getItems(), positionA, positionB);
bind.playerQueueRecyclerView.getAdapter().notifyItemMoved(positionA, positionB);
}
MediaManager.shuffle(mediaBrowserListenableFuture, playerSongQueueAdapter.getItems(), startPosition, endPosition);
}
});
}
private void updateNowPlayingItem() { private void updateNowPlayingItem() {
playerSongQueueAdapter.notifyDataSetChanged(); playerSongQueueAdapter.notifyDataSetChanged();
} }

View File

@@ -178,7 +178,7 @@ public class PlaylistCatalogueFragment extends Fragment implements ClickCallback
@Override @Override
public void onPlaylistLongClick(Bundle bundle) { public void onPlaylistLongClick(Bundle bundle) {
PlaylistEditorDialog dialog = new PlaylistEditorDialog(); PlaylistEditorDialog dialog = new PlaylistEditorDialog(null);
dialog.setArguments(bundle); dialog.setArguments(bundle);
dialog.show(activity.getSupportFragmentManager(), null); dialog.show(activity.getSupportFragmentManager(), null);
hideKeyboard(requireView()); hideKeyboard(requireView());

View File

@@ -167,28 +167,28 @@ public class PlaylistPageFragment extends Fragment implements ClickCallback {
// Pic top-left // Pic top-left
CustomGlideRequest.Builder 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() .build()
.transform(new GranularRoundedCorners(CustomGlideRequest.CORNER_RADIUS, 0, 0, 0)) .transform(new GranularRoundedCorners(CustomGlideRequest.CORNER_RADIUS, 0, 0, 0))
.into(bind.playlistCoverImageViewTopLeft); .into(bind.playlistCoverImageViewTopLeft);
// Pic top-right // Pic top-right
CustomGlideRequest.Builder 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() .build()
.transform(new GranularRoundedCorners(0, CustomGlideRequest.CORNER_RADIUS, 0, 0)) .transform(new GranularRoundedCorners(0, CustomGlideRequest.CORNER_RADIUS, 0, 0))
.into(bind.playlistCoverImageViewTopRight); .into(bind.playlistCoverImageViewTopRight);
// Pic bottom-left // Pic bottom-left
CustomGlideRequest.Builder 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() .build()
.transform(new GranularRoundedCorners(0, 0, 0, CustomGlideRequest.CORNER_RADIUS)) .transform(new GranularRoundedCorners(0, 0, 0, CustomGlideRequest.CORNER_RADIUS))
.into(bind.playlistCoverImageViewBottomLeft); .into(bind.playlistCoverImageViewBottomLeft);
// Pic bottom-right // Pic bottom-right
CustomGlideRequest.Builder 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() .build()
.transform(new GranularRoundedCorners(0, 0, CustomGlideRequest.CORNER_RADIUS, 0)) .transform(new GranularRoundedCorners(0, 0, CustomGlideRequest.CORNER_RADIUS, 0))
.into(bind.playlistCoverImageViewBottomRight); .into(bind.playlistCoverImageViewBottomRight);
@@ -200,7 +200,7 @@ public class PlaylistPageFragment extends Fragment implements ClickCallback {
bind.songRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext())); bind.songRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
bind.songRecyclerView.setHasFixedSize(true); bind.songRecyclerView.setHasFixedSize(true);
songHorizontalAdapter = new SongHorizontalAdapter(this, true); songHorizontalAdapter = new SongHorizontalAdapter(this, true, false);
bind.songRecyclerView.setAdapter(songHorizontalAdapter); bind.songRecyclerView.setAdapter(songHorizontalAdapter);
playlistPageViewModel.getPlaylistSongLiveList().observe(getViewLifecycleOwner(), songs -> songHorizontalAdapter.setItems(songs)); playlistPageViewModel.getPlaylistSongLiveList().observe(getViewLifecycleOwner(), songs -> songHorizontalAdapter.setItems(songs));

View File

@@ -112,7 +112,7 @@ public class SearchFragment extends Fragment implements ClickCallback {
bind.searchResultTracksRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext())); bind.searchResultTracksRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
bind.searchResultTracksRecyclerView.setHasFixedSize(true); bind.searchResultTracksRecyclerView.setHasFixedSize(true);
songHorizontalAdapter = new SongHorizontalAdapter(this, true); songHorizontalAdapter = new SongHorizontalAdapter(this, true, false);
bind.searchResultTracksRecyclerView.setAdapter(songHorizontalAdapter); bind.searchResultTracksRecyclerView.setAdapter(songHorizontalAdapter);
} }
@@ -242,7 +242,6 @@ public class SearchFragment extends Fragment implements ClickCallback {
} }
private boolean isQueryValid(String query) { private boolean isQueryValid(String query) {
Log.d(TAG, "isQueryValid()");
return !query.equals("") && query.trim().length() > 2; return !query.equals("") && query.trim().length() > 2;
} }

View File

@@ -12,6 +12,8 @@ import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.OptIn; import androidx.annotation.OptIn;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.core.os.LocaleListCompat;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.preference.ListPreference; import androidx.preference.ListPreference;
@@ -28,8 +30,12 @@ import com.cappielloantonio.tempo.ui.dialog.DeleteDownloadStorageDialog;
import com.cappielloantonio.tempo.ui.dialog.DownloadStorageDialog; import com.cappielloantonio.tempo.ui.dialog.DownloadStorageDialog;
import com.cappielloantonio.tempo.ui.dialog.StarredSyncDialog; import com.cappielloantonio.tempo.ui.dialog.StarredSyncDialog;
import com.cappielloantonio.tempo.util.Preferences; import com.cappielloantonio.tempo.util.Preferences;
import com.cappielloantonio.tempo.util.UIUtil;
import com.cappielloantonio.tempo.viewmodel.SettingViewModel; import com.cappielloantonio.tempo.viewmodel.SettingViewModel;
import java.util.Locale;
import java.util.Map;
@OptIn(markerClass = UnstableApi.class) @OptIn(markerClass = UnstableApi.class)
public class SettingsFragment extends PreferenceFragmentCompat { public class SettingsFragment extends PreferenceFragmentCompat {
private static final String TAG = "SettingsFragment"; private static final String TAG = "SettingsFragment";
@@ -75,63 +81,16 @@ public class SettingsFragment extends PreferenceFragmentCompat {
super.onResume(); super.onResume();
checkEqualizer(); checkEqualizer();
checkStorage(); checkStorage();
findPreference("version").setSummary(BuildConfig.VERSION_NAME); setAppLanguage();
setVersion();
findPreference("logout").setOnPreferenceClickListener(preference -> { actionLogout();
activity.quit(); actionScan();
return true; actionSyncStarredTracks();
}); actionChangeDownloadStorage();
actionDeleteDownloadStorage();
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;
});
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;
});
findPreference("delete_download_storage").setOnPreferenceClickListener(preference -> {
DeleteDownloadStorageDialog dialog = new DeleteDownloadStorageDialog();
dialog.show(activity.getSupportFragmentManager(), null);
return true;
});
} }
@Override @Override
@@ -187,6 +146,94 @@ public class SettingsFragment extends PreferenceFragmentCompat {
} }
} }
private void setAppLanguage() {
ListPreference localePref = (ListPreference) findPreference("language");
Map<String, String> locales = UIUtil.getLangPreferenceDropdownEntries(requireContext());
CharSequence[] entries = locales.keySet().toArray(new CharSequence[locales.size()]);
CharSequence[] entryValues = locales.values().toArray(new CharSequence[locales.size()]);
localePref.setEntries(entries);
localePref.setEntryValues(entryValues);
localePref.setDefaultValue(entryValues[0]);
localePref.setSummary(Locale.forLanguageTag(localePref.getValue()).getDisplayLanguage());
localePref.setOnPreferenceChangeListener((preference, newValue) -> {
LocaleListCompat appLocale = LocaleListCompat.forLanguageTags((String) newValue);
AppCompatDelegate.setApplicationLocales(appLocale);
return true;
});
}
private void setVersion() {
findPreference("version").setSummary(BuildConfig.VERSION_NAME);
}
private void actionLogout() {
findPreference("logout").setOnPreferenceClickListener(preference -> {
activity.quit();
return true;
});
}
private void actionScan() {
findPreference("scan_library").setOnPreferenceClickListener(preference -> {
settingViewModel.launchScan(new ScanCallback() {
@Override
public void onError(Exception exception) {
findPreference("scan_library").setSummary(exception.getMessage());
}
@Override
public void onSuccess(boolean isScanning, long count) {
getScanStatus();
}
});
return true;
});
}
private void actionSyncStarredTracks() {
findPreference("sync_starred_tracks_for_offline_use").setOnPreferenceChangeListener((preference, newValue) -> {
if (newValue instanceof Boolean) {
if ((Boolean) newValue) {
StarredSyncDialog dialog = new StarredSyncDialog();
dialog.show(activity.getSupportFragmentManager(), null);
}
}
return true;
});
}
private void actionChangeDownloadStorage() {
findPreference("download_storage").setOnPreferenceClickListener(preference -> {
DownloadStorageDialog dialog = new DownloadStorageDialog(new DialogClickCallback() {
@Override
public void onPositiveClick() {
findPreference("download_storage").setSummary(R.string.download_storage_external_dialog_positive_button);
}
@Override
public void onNegativeClick() {
findPreference("download_storage").setSummary(R.string.download_storage_internal_dialog_negative_button);
}
});
dialog.show(activity.getSupportFragmentManager(), null);
return true;
});
}
private void actionDeleteDownloadStorage() {
findPreference("delete_download_storage").setOnPreferenceClickListener(preference -> {
DeleteDownloadStorageDialog dialog = new DeleteDownloadStorageDialog();
dialog.show(activity.getSupportFragmentManager(), null);
return true;
});
}
private void getScanStatus() { private void getScanStatus() {
settingViewModel.getScanStatus(new ScanCallback() { settingViewModel.getScanStatus(new ScanCallback() {
@Override @Override

View File

@@ -166,7 +166,7 @@ public class SongListPageFragment extends Fragment implements ClickCallback {
bind.songListRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext())); bind.songListRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
bind.songListRecyclerView.setHasFixedSize(true); bind.songListRecyclerView.setHasFixedSize(true);
songHorizontalAdapter = new SongHorizontalAdapter(this, true); songHorizontalAdapter = new SongHorizontalAdapter(this, true, false);
bind.songListRecyclerView.setAdapter(songHorizontalAdapter); bind.songListRecyclerView.setAdapter(songHorizontalAdapter);
songListPageViewModel.getSongList().observe(getViewLifecycleOwner(), songs -> { songListPageViewModel.getSongList().observe(getViewLifecycleOwner(), songs -> {
isLoading = false; isLoading = false;

View File

@@ -1,6 +1,9 @@
package com.cappielloantonio.tempo.ui.fragment.bottomsheetdialog; package com.cappielloantonio.tempo.ui.fragment.bottomsheetdialog;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
@@ -32,7 +35,9 @@ import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.DownloadUtil; import com.cappielloantonio.tempo.util.DownloadUtil;
import com.cappielloantonio.tempo.util.MappingUtil; import com.cappielloantonio.tempo.util.MappingUtil;
import com.cappielloantonio.tempo.util.MusicUtil; import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.util.Preferences;
import com.cappielloantonio.tempo.viewmodel.AlbumBottomSheetViewModel; import com.cappielloantonio.tempo.viewmodel.AlbumBottomSheetViewModel;
import com.cappielloantonio.tempo.viewmodel.HomeViewModel;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment; import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
@@ -43,6 +48,7 @@ import java.util.stream.Collectors;
@UnstableApi @UnstableApi
public class AlbumBottomSheetDialog extends BottomSheetDialogFragment implements View.OnClickListener { public class AlbumBottomSheetDialog extends BottomSheetDialogFragment implements View.OnClickListener {
private HomeViewModel homeViewModel;
private AlbumBottomSheetViewModel albumBottomSheetViewModel; private AlbumBottomSheetViewModel albumBottomSheetViewModel;
private AlbumID3 album; private AlbumID3 album;
@@ -55,6 +61,7 @@ public class AlbumBottomSheetDialog extends BottomSheetDialogFragment implements
album = this.requireArguments().getParcelable(Constants.ALBUM_OBJECT); album = this.requireArguments().getParcelable(Constants.ALBUM_OBJECT);
homeViewModel = new ViewModelProvider(requireActivity()).get(HomeViewModel.class);
albumBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(AlbumBottomSheetViewModel.class); albumBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(AlbumBottomSheetViewModel.class);
albumBottomSheetViewModel.setAlbum(album); albumBottomSheetViewModel.setAlbum(album);
@@ -79,7 +86,7 @@ public class AlbumBottomSheetDialog extends BottomSheetDialogFragment implements
private void init(View view) { private void init(View view) {
ImageView coverAlbum = view.findViewById(R.id.album_cover_image_view); ImageView coverAlbum = view.findViewById(R.id.album_cover_image_view);
CustomGlideRequest.Builder CustomGlideRequest.Builder
.from(requireContext(), albumBottomSheetViewModel.getAlbum().getCoverArtId()) .from(requireContext(), albumBottomSheetViewModel.getAlbum().getCoverArtId(), CustomGlideRequest.ResourceType.Album)
.build() .build()
.into(coverAlbum); .into(coverAlbum);
@@ -147,8 +154,6 @@ public class AlbumBottomSheetDialog extends BottomSheetDialogFragment implements
})); }));
TextView downloadAll = view.findViewById(R.id.download_all_text_view); 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 -> { albumBottomSheetViewModel.getAlbumTracks().observe(getViewLifecycleOwner(), songs -> {
List<MediaItem> mediaItems = MappingUtil.mapDownloads(songs); List<MediaItem> mediaItems = MappingUtil.mapDownloads(songs);
List<Download> downloads = songs.stream().map(Download::new).collect(Collectors.toList()); List<Download> downloads = songs.stream().map(Download::new).collect(Collectors.toList());
@@ -157,17 +162,21 @@ public class AlbumBottomSheetDialog extends BottomSheetDialogFragment implements
DownloadUtil.getDownloadTracker(requireContext()).download(mediaItems, downloads); DownloadUtil.getDownloadTracker(requireContext()).download(mediaItems, downloads);
dismissBottomSheet(); 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); TextView goToArtist = view.findViewById(R.id.go_to_artist_text_view);
goToArtist.setOnClickListener(v -> albumBottomSheetViewModel.getArtist().observe(getViewLifecycleOwner(), artist -> { goToArtist.setOnClickListener(v -> albumBottomSheetViewModel.getArtist().observe(getViewLifecycleOwner(), artist -> {
if (artist != null) { if (artist != null) {
@@ -180,6 +189,19 @@ public class AlbumBottomSheetDialog extends BottomSheetDialogFragment implements
dismissBottomSheet(); dismissBottomSheet();
})); }));
TextView share = view.findViewById(R.id.share_text_view);
share.setOnClickListener(v -> albumBottomSheetViewModel.shareAlbum().observe(getViewLifecycleOwner(), sharedAlbum -> {
if (sharedAlbum != null) {
ClipboardManager clipboardManager = (ClipboardManager) requireActivity().getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clipData = ClipData.newPlainText(getString(R.string.app_name), sharedAlbum.getUrl());
clipboardManager.setPrimaryClip(clipData);
refreshShares();
dismissBottomSheet();
}
}));
share.setVisibility(Preferences.isSharingEnabled() ? View.VISIBLE : View.GONE);
} }
@Override @Override
@@ -191,6 +213,16 @@ public class AlbumBottomSheetDialog extends BottomSheetDialogFragment implements
dismiss(); 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() { private void initializeMediaBrowser() {
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync(); mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
} }
@@ -198,4 +230,8 @@ public class AlbumBottomSheetDialog extends BottomSheetDialogFragment implements
private void releaseMediaBrowser() { private void releaseMediaBrowser() {
MediaBrowser.releaseFuture(mediaBrowserListenableFuture); MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
} }
private void refreshShares() {
homeViewModel.refreshShares(requireActivity());
}
} }

View File

@@ -70,7 +70,7 @@ public class ArtistBottomSheetDialog extends BottomSheetDialogFragment implement
private void init(View view) { private void init(View view) {
ImageView coverArtist = view.findViewById(R.id.artist_cover_image_view); ImageView coverArtist = view.findViewById(R.id.artist_cover_image_view);
CustomGlideRequest.Builder CustomGlideRequest.Builder
.from(requireContext(), artistBottomSheetViewModel.getArtist().getCoverArtId()) .from(requireContext(), artistBottomSheetViewModel.getArtist().getCoverArtId(), CustomGlideRequest.ResourceType.Artist)
.build() .build()
.into(coverArtist); .into(coverArtist);

View File

@@ -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);
}
}

View File

@@ -63,7 +63,7 @@ public class PodcastChannelBottomSheetDialog extends BottomSheetDialogFragment i
ImageView coverPodcast = view.findViewById(R.id.podcast_cover_image_view); ImageView coverPodcast = view.findViewById(R.id.podcast_cover_image_view);
CustomGlideRequest.Builder CustomGlideRequest.Builder
.from(requireContext(), podcastChannelBottomSheetViewModel.getPodcastChannel().getCoverArtId()) .from(requireContext(), podcastChannelBottomSheetViewModel.getPodcastChannel().getCoverArtId(), CustomGlideRequest.ResourceType.Podcast)
.build() .build()
.into(coverPodcast); .into(coverPodcast);

View File

@@ -65,7 +65,7 @@ public class PodcastEpisodeBottomSheetDialog extends BottomSheetDialogFragment i
ImageView coverPodcast = view.findViewById(R.id.podcast_cover_image_view); ImageView coverPodcast = view.findViewById(R.id.podcast_cover_image_view);
CustomGlideRequest.Builder CustomGlideRequest.Builder
.from(requireContext(), podcastEpisodeBottomSheetViewModel.getPodcastEpisode().getCoverArtId()) .from(requireContext(), podcastEpisodeBottomSheetViewModel.getPodcastEpisode().getCoverArtId(), CustomGlideRequest.ResourceType.Podcast)
.build() .build()
.into(coverPodcast); .into(coverPodcast);

View File

@@ -0,0 +1,110 @@
package com.cappielloantonio.tempo.ui.fragment.bottomsheetdialog;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
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.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.subsonic.models.Share;
import com.cappielloantonio.tempo.ui.dialog.ShareUpdateDialog;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.UIUtil;
import com.cappielloantonio.tempo.viewmodel.HomeViewModel;
import com.cappielloantonio.tempo.viewmodel.ShareBottomSheetViewModel;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
@UnstableApi
public class ShareBottomSheetDialog extends BottomSheetDialogFragment implements View.OnClickListener {
private HomeViewModel homeViewModel;
private ShareBottomSheetViewModel shareBottomSheetViewModel;
private Share share;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.bottom_sheet_share_dialog, container, false);
share = this.requireArguments().getParcelable(Constants.SHARE_OBJECT);
homeViewModel = new ViewModelProvider(requireActivity()).get(HomeViewModel.class);
shareBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(ShareBottomSheetViewModel.class);
shareBottomSheetViewModel.setShare(share);
init(view);
return view;
}
private void init(View view) {
ImageView shareCover = view.findViewById(R.id.share_cover_image_view);
CustomGlideRequest.Builder
.from(requireContext(), shareBottomSheetViewModel.getShare().getEntries().get(0).getCoverArtId(), CustomGlideRequest.ResourceType.Unknown)
.build()
.into(shareCover);
TextView shareTitle = view.findViewById(R.id.share_title_text_view);
shareTitle.setText(shareBottomSheetViewModel.getShare().getDescription());
shareTitle.setSelected(true);
TextView shareSubtitle = view.findViewById(R.id.share_subtitle_text_view);
shareSubtitle.setText(requireContext().getString(R.string.share_subtitle_item, UIUtil.getReadableDate(share.getExpires())));
shareSubtitle.setSelected(true);
TextView copyLink = view.findViewById(R.id.copy_link_text_view);
copyLink.setOnClickListener(v -> {
ClipboardManager clipboardManager = (ClipboardManager) requireActivity().getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clipData = ClipData.newPlainText(getString(R.string.app_name), shareBottomSheetViewModel.getShare().getUrl());
clipboardManager.setPrimaryClip(clipData);
dismissBottomSheet();
});
TextView updateShare = view.findViewById(R.id.update_share_preferences_text_view);
updateShare.setOnClickListener(v -> {
// refreshShares();
showUpdateShareDialog();
dismissBottomSheet();
});
TextView deleteShare = view.findViewById(R.id.delete_share_text_view);
deleteShare.setOnClickListener(v -> {
deleteShare();
refreshShares();
dismissBottomSheet();
});
}
@Override
public void onClick(View v) {
dismissBottomSheet();
}
private void dismissBottomSheet() {
dismiss();
}
private void showUpdateShareDialog() {
ShareUpdateDialog dialog = new ShareUpdateDialog();
dialog.show(requireActivity().getSupportFragmentManager(), null);
}
private void refreshShares() {
homeViewModel.refreshShares(getParentFragment());
}
private void deleteShare() {
shareBottomSheetViewModel.deleteShare();
}
}

View File

@@ -1,6 +1,9 @@
package com.cappielloantonio.tempo.ui.fragment.bottomsheetdialog; package com.cappielloantonio.tempo.ui.fragment.bottomsheetdialog;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
@@ -30,14 +33,15 @@ import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.DownloadUtil; import com.cappielloantonio.tempo.util.DownloadUtil;
import com.cappielloantonio.tempo.util.MappingUtil; import com.cappielloantonio.tempo.util.MappingUtil;
import com.cappielloantonio.tempo.util.MusicUtil; import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.util.Preferences;
import com.cappielloantonio.tempo.viewmodel.HomeViewModel;
import com.cappielloantonio.tempo.viewmodel.SongBottomSheetViewModel; import com.cappielloantonio.tempo.viewmodel.SongBottomSheetViewModel;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment; import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
@UnstableApi @UnstableApi
public class SongBottomSheetDialog extends BottomSheetDialogFragment implements View.OnClickListener { public class SongBottomSheetDialog extends BottomSheetDialogFragment implements View.OnClickListener {
private static final String TAG = "SongBottomSheetDialog"; private HomeViewModel homeViewModel;
private SongBottomSheetViewModel songBottomSheetViewModel; private SongBottomSheetViewModel songBottomSheetViewModel;
private Child song; private Child song;
@@ -50,6 +54,7 @@ public class SongBottomSheetDialog extends BottomSheetDialogFragment implements
song = requireArguments().getParcelable(Constants.TRACK_OBJECT); song = requireArguments().getParcelable(Constants.TRACK_OBJECT);
homeViewModel = new ViewModelProvider(requireActivity()).get(HomeViewModel.class);
songBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(SongBottomSheetViewModel.class); songBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(SongBottomSheetViewModel.class);
songBottomSheetViewModel.setSong(song); songBottomSheetViewModel.setSong(song);
@@ -74,7 +79,7 @@ public class SongBottomSheetDialog extends BottomSheetDialogFragment implements
private void init(View view) { private void init(View view) {
ImageView coverSong = view.findViewById(R.id.song_cover_image_view); ImageView coverSong = view.findViewById(R.id.song_cover_image_view);
CustomGlideRequest.Builder CustomGlideRequest.Builder
.from(requireContext(), songBottomSheetViewModel.getSong().getCoverArtId()) .from(requireContext(), songBottomSheetViewModel.getSong().getCoverArtId(), CustomGlideRequest.ResourceType.Song)
.build() .build()
.into(coverSong); .into(coverSong);
@@ -202,6 +207,19 @@ public class SongBottomSheetDialog extends BottomSheetDialogFragment implements
dismissBottomSheet(); dismissBottomSheet();
})); }));
TextView share = view.findViewById(R.id.share_text_view);
share.setOnClickListener(v -> songBottomSheetViewModel.shareTrack().observe(getViewLifecycleOwner(), sharedTrack -> {
if (sharedTrack != null) {
ClipboardManager clipboardManager = (ClipboardManager) requireActivity().getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clipData = ClipData.newPlainText(getString(R.string.app_name), sharedTrack.getUrl());
clipboardManager.setPrimaryClip(clipData);
refreshShares();
dismissBottomSheet();
}
}));
share.setVisibility(Preferences.isSharingEnabled() ? View.VISIBLE : View.GONE);
} }
@Override @Override
@@ -229,4 +247,8 @@ public class SongBottomSheetDialog extends BottomSheetDialogFragment implements
private void releaseMediaBrowser() { private void releaseMediaBrowser() {
MediaBrowser.releaseFuture(mediaBrowserListenableFuture); MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
} }
private void refreshShares() {
homeViewModel.refreshShares(requireActivity());
}
} }

View File

@@ -82,6 +82,12 @@ object Constants {
const val DOWNLOAD_TYPE_GENRE = "download_type_genre" const val DOWNLOAD_TYPE_GENRE = "download_type_genre"
const val DOWNLOAD_TYPE_YEAR = "download_type_year" 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 SHARE_OBJECT = "share_object"
const val PLAYABLE_MEDIA_LIMIT = 100 const val PLAYABLE_MEDIA_LIMIT = 100
const val PRE_PLAYABLE_MEDIA = 15 const val PRE_PLAYABLE_MEDIA = 15
} }

View File

@@ -1,7 +1,9 @@
package com.cappielloantonio.tempo.util; package com.cappielloantonio.tempo.util;
import android.app.Notification;
import android.content.Context; import android.content.Context;
import androidx.core.app.NotificationCompat;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.database.DatabaseProvider; import androidx.media3.database.DatabaseProvider;
import androidx.media3.database.StandaloneDatabaseProvider; import androidx.media3.database.StandaloneDatabaseProvider;
@@ -23,12 +25,15 @@ import java.io.File;
import java.net.CookieHandler; import java.net.CookieHandler;
import java.net.CookieManager; import java.net.CookieManager;
import java.net.CookiePolicy; import java.net.CookiePolicy;
import java.util.ArrayList;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
@UnstableApi @UnstableApi
public final class DownloadUtil { public final class DownloadUtil {
public static final String DOWNLOAD_NOTIFICATION_CHANNEL_ID = "download_channel"; 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"; private static final String DOWNLOAD_CONTENT_DIRECTORY = "downloads";
@@ -153,4 +158,41 @@ public final class DownloadUtil {
.setCacheWriteDataSinkFactory(null) .setCacheWriteDataSinkFactory(null)
.setFlags(CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR); .setFlags(CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR);
} }
public static synchronized void eraseDownloadFolder(Context context) {
File directory = getDownloadDirectory(context);
ArrayList<File> files = listFiles(directory, new ArrayList<>());
for (File file : files) {
file.delete();
}
}
private static synchronized ArrayList<File> listFiles(File directory, ArrayList<File> files) {
if (directory.isDirectory()) {
File[] list = directory.listFiles();
if (list != null) {
for (File file : list) {
if (file.isFile() && file.getName().toLowerCase().endsWith(".exo")) {
files.add(file);
} else if (file.isDirectory()) {
listFiles(file, files);
}
}
}
}
return files;
}
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();
}
} }

View File

@@ -10,6 +10,7 @@ import androidx.media3.common.MimeTypes;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import com.cappielloantonio.tempo.App; import com.cappielloantonio.tempo.App;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.model.Download; import com.cappielloantonio.tempo.model.Download;
import com.cappielloantonio.tempo.repository.DownloadRepository; import com.cappielloantonio.tempo.repository.DownloadRepository;
import com.cappielloantonio.tempo.subsonic.models.Child; import com.cappielloantonio.tempo.subsonic.models.Child;
@@ -33,6 +34,7 @@ public class MappingUtil {
public static MediaItem mapMediaItem(Child media) { public static MediaItem mapMediaItem(Child media) {
Uri uri = getUri(media); Uri uri = getUri(media);
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(media.getCoverArtId(), Preferences.getImageSize()));
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
bundle.putString("id", media.getId()); bundle.putString("id", media.getId());
@@ -78,6 +80,7 @@ public class MappingUtil {
.setReleaseYear(media.getYear() != null ? media.getYear() : 0) .setReleaseYear(media.getYear() != null ? media.getYear() : 0)
.setAlbumTitle(MusicUtil.getReadableString(media.getAlbum())) .setAlbumTitle(MusicUtil.getReadableString(media.getAlbum()))
.setArtist(MusicUtil.getReadableString(media.getArtist())) .setArtist(MusicUtil.getReadableString(media.getArtist()))
.setArtworkUri(artworkUri)
.setExtras(bundle) .setExtras(bundle)
.build() .build()
) )
@@ -157,6 +160,7 @@ public class MappingUtil {
public static MediaItem mapMediaItem(PodcastEpisode podcastEpisode) { public static MediaItem mapMediaItem(PodcastEpisode podcastEpisode) {
Uri uri = getUri(podcastEpisode); Uri uri = getUri(podcastEpisode);
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(podcastEpisode.getCoverArtId(), Preferences.getImageSize()));
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
bundle.putString("id", podcastEpisode.getId()); bundle.putString("id", podcastEpisode.getId());
@@ -192,7 +196,7 @@ public class MappingUtil {
bundle.putInt("originalHeight", podcastEpisode.getOriginalHeight() != null ? podcastEpisode.getOriginalHeight() : 0); bundle.putInt("originalHeight", podcastEpisode.getOriginalHeight() != null ? podcastEpisode.getOriginalHeight() : 0);
bundle.putString("uri", uri.toString()); bundle.putString("uri", uri.toString());
return new MediaItem.Builder() MediaItem item = new MediaItem.Builder()
.setMediaId(podcastEpisode.getId()) .setMediaId(podcastEpisode.getId())
.setMediaMetadata( .setMediaMetadata(
new MediaMetadata.Builder() new MediaMetadata.Builder()
@@ -202,6 +206,7 @@ public class MappingUtil {
.setReleaseYear(podcastEpisode.getYear() != null ? podcastEpisode.getYear() : 0) .setReleaseYear(podcastEpisode.getYear() != null ? podcastEpisode.getYear() : 0)
.setAlbumTitle(MusicUtil.getReadableString(podcastEpisode.getAlbum())) .setAlbumTitle(MusicUtil.getReadableString(podcastEpisode.getAlbum()))
.setArtist(MusicUtil.getReadableString(podcastEpisode.getArtist())) .setArtist(MusicUtil.getReadableString(podcastEpisode.getArtist()))
.setArtworkUri(artworkUri)
.setExtras(bundle) .setExtras(bundle)
.build() .build()
) )
@@ -211,9 +216,17 @@ public class MappingUtil {
.setExtras(bundle) .setExtras(bundle)
.build() .build()
) )
/* .setClippingConfiguration(
new MediaItem.ClippingConfiguration.Builder()
.setStartPositionMs(0)
.setEndPositionMs(podcastEpisode.getDuration() * 1000)
.build()
) */
.setMimeType(MimeTypes.BASE_TYPE_AUDIO) .setMimeType(MimeTypes.BASE_TYPE_AUDIO)
.setUri(uri) .setUri(uri)
.build(); .build();
return item;
} }
private static Uri getUri(Child media) { private static Uri getUri(Child media) {
@@ -223,9 +236,9 @@ public class MappingUtil {
} }
private static Uri getUri(PodcastEpisode podcastEpisode) { private static Uri getUri(PodcastEpisode podcastEpisode) {
return DownloadUtil.getDownloadTracker(App.getContext()).isDownloaded(podcastEpisode.getId()) return DownloadUtil.getDownloadTracker(App.getContext()).isDownloaded(podcastEpisode.getStreamId())
? getDownloadUri(podcastEpisode.getId()) ? getDownloadUri(podcastEpisode.getStreamId())
: MusicUtil.getStreamUri(podcastEpisode.getId()); : MusicUtil.getStreamUri(podcastEpisode.getStreamId());
} }
private static Uri getDownloadUri(String id) { private static Uri getDownloadUri(String id) {

View File

@@ -14,6 +14,8 @@ import com.cappielloantonio.tempo.model.Download;
import com.cappielloantonio.tempo.repository.DownloadRepository; import com.cappielloantonio.tempo.repository.DownloadRepository;
import com.cappielloantonio.tempo.subsonic.models.Child; import com.cappielloantonio.tempo.subsonic.models.Child;
import java.text.CharacterIterator;
import java.text.StringCharacterIterator;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
@@ -32,7 +34,7 @@ public class MusicUtil {
uri.append("stream"); uri.append("stream");
if (params.containsKey("u") && params.get("u") != null) if (params.containsKey("u") && params.get("u") != null)
uri.append("?u=").append(params.get("u")); uri.append("?u=").append(Util.encode(params.get("u")));
if (params.containsKey("p") && params.get("p") != null) if (params.containsKey("p") && params.get("p") != null)
uri.append("&p=").append(params.get("p")); uri.append("&p=").append(params.get("p"));
if (params.containsKey("s") && params.get("s") != null) if (params.containsKey("s") && params.get("s") != null)
@@ -48,6 +50,8 @@ public class MusicUtil {
uri.append("&maxBitRate=").append(getBitratePreference()); uri.append("&maxBitRate=").append(getBitratePreference());
if (!Preferences.isServerPrioritized()) if (!Preferences.isServerPrioritized())
uri.append("&format=").append(getTranscodingFormatPreference()); uri.append("&format=").append(getTranscodingFormatPreference());
if (Preferences.askForEstimateContentLength())
uri.append("&estimateContentLength=true");
uri.append("&id=").append(id); uri.append("&id=").append(id);
@@ -68,7 +72,7 @@ public class MusicUtil {
uri.append("download"); uri.append("download");
if (params.containsKey("u") && params.get("u") != null) if (params.containsKey("u") && params.get("u") != null)
uri.append("?u=").append(params.get("u")); uri.append("?u=").append(Util.encode(params.get("u")));
if (params.containsKey("p") && params.get("p") != null) if (params.containsKey("p") && params.get("p") != null)
uri.append("&p=").append(params.get("p")); uri.append("&p=").append(params.get("p"));
if (params.containsKey("s") && params.get("s") != null) if (params.containsKey("s") && params.get("s") != null)
@@ -99,7 +103,7 @@ public class MusicUtil {
uri.append("stream"); uri.append("stream");
if (params.containsKey("u") && params.get("u") != null) if (params.containsKey("u") && params.get("u") != null)
uri.append("?u=").append(params.get("u")); uri.append("?u=").append(Util.encode(params.get("u")));
if (params.containsKey("p") && params.get("p") != null) if (params.containsKey("p") && params.get("p") != null)
uri.append("&p=").append(params.get("p")); uri.append("&p=").append(params.get("p"));
if (params.containsKey("s") && params.get("s") != null) if (params.containsKey("s") && params.get("s") != null)
@@ -211,6 +215,27 @@ public class MusicUtil {
return readableStrings; return readableStrings;
} }
public static String getReadableByteCount(long bytes) {
long absB = bytes == Long.MIN_VALUE ? Long.MAX_VALUE : Math.abs(bytes);
if (absB < 1024) {
return bytes + " B";
}
long value = absB;
CharacterIterator ci = new StringCharacterIterator("KMGTPE");
for (int i = 40; i >= 0 && absB > 0xfffccccccccccccL >> i; i -= 10) {
value >>= 10;
ci.next();
}
value *= Long.signum(bytes);
return String.format("%.1f %ciB", value / 1024.0, ci.current());
}
public static String passwordHexEncoding(String plainPassword) { public static String passwordHexEncoding(String plainPassword) {
return "enc:" + plainPassword.chars().mapToObj(Integer::toHexString).collect(Collectors.joining()); return "enc:" + plainPassword.chars().mapToObj(Integer::toHexString).collect(Collectors.joining());
} }
@@ -271,8 +296,12 @@ public class MusicUtil {
return toLimit; return toLimit;
} }
public static int getPlayableMediaPosition(int initialPosition) { public static int getPlayableMediaPosition(List<Child> toLimit, int position) {
return Math.min(initialPosition, Constants.PRE_PLAYABLE_MEDIA); if (!toLimit.isEmpty() && toLimit.size() > Constants.PLAYABLE_MEDIA_LIMIT) {
return Math.min(position, Constants.PRE_PLAYABLE_MEDIA);
}
return position;
} }
private static ConnectivityManager getConnectivityManager() { private static ConnectivityManager getConnectivityManager() {

View File

@@ -39,6 +39,8 @@ object Preferences {
private const val AUDIO_TRANSCODE_DOWNLOAD_PRIORITY = "audio_transcode_download_priority" private const val AUDIO_TRANSCODE_DOWNLOAD_PRIORITY = "audio_transcode_download_priority"
private const val MAX_BITRATE_DOWNLOAD = "max_bitrate_download" private const val MAX_BITRATE_DOWNLOAD = "max_bitrate_download"
private const val AUDIO_TRANSCODE_FORMAT_DOWNLOAD = "audio_transcode_format_download" private const val AUDIO_TRANSCODE_FORMAT_DOWNLOAD = "audio_transcode_format_download"
private const val SHARE = "share"
private const val ESTIMATE_CONTENT_LENGTH = "estimate_content_length"
@JvmStatic @JvmStatic
fun getServer(): String? { fun getServer(): String? {
@@ -313,4 +315,14 @@ object Preferences {
fun getAudioTranscodeFormatTranscodedDownload(): String { fun getAudioTranscodeFormatTranscodedDownload(): String {
return App.getInstance().preferences.getString(AUDIO_TRANSCODE_FORMAT_DOWNLOAD, "raw")!! return App.getInstance().preferences.getString(AUDIO_TRANSCODE_FORMAT_DOWNLOAD, "raw")!!
} }
@JvmStatic
fun isSharingEnabled(): Boolean {
return App.getInstance().preferences.getBoolean(SHARE, false)
}
@JvmStatic
fun askForEstimateContentLength(): Boolean {
return App.getInstance().preferences.getBoolean(ESTIMATE_CONTENT_LENGTH, false)
}
} }

View File

@@ -1,6 +1,7 @@
package com.cappielloantonio.tempo.util; package com.cappielloantonio.tempo.util;
import androidx.annotation.OptIn; import androidx.annotation.OptIn;
import androidx.media3.common.MediaItem;
import androidx.media3.common.Metadata; import androidx.media3.common.Metadata;
import androidx.media3.common.Tracks; import androidx.media3.common.Tracks;
import androidx.media3.common.util.UnstableApi; import androidx.media3.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; return gains;
} }
@@ -105,13 +109,67 @@ public class ReplayGainUtil {
} }
private static void applyReplayGain(ExoPlayer player, List<ReplayGain> gains) { private static void applyReplayGain(ExoPlayer player, List<ReplayGain> gains) {
if (Objects.equals(Preferences.getReplayGainMode(), "disabled") || gains.size() == 0) { if (Objects.equals(Preferences.getReplayGainMode(), "disabled") || gains == null || gains.isEmpty()) {
setReplayGain(player, 0f); setNoReplayGain(player);
} else if (Objects.equals(Preferences.getReplayGainMode(), "track")) { return;
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(), "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) { private static void setReplayGain(ExoPlayer player, float gain) {

View File

@@ -5,8 +5,23 @@ import android.content.res.TypedArray;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.graphics.drawable.InsetDrawable; import android.graphics.drawable.InsetDrawable;
import androidx.core.os.LocaleListCompat;
import androidx.recyclerview.widget.DividerItemDecoration; import androidx.recyclerview.widget.DividerItemDecoration;
import com.cappielloantonio.tempo.R;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
public class UIUtil { public class UIUtil {
public static int getSpanCount(int itemCount, int maxSpan) { public static int getSpanCount(int itemCount, int maxSpan) {
int itemSize = itemCount == 0 ? 1 : itemCount; int itemSize = itemCount == 0 ? 1 : itemCount;
@@ -31,4 +46,49 @@ public class UIUtil {
return itemDecoration; return itemDecoration;
} }
private static LocaleListCompat getLocalesFromResources(Context context) {
final List<String> tagsList = new ArrayList<>();
XmlPullParser xpp = context.getResources().getXml(R.xml.locale_config);
try {
while (xpp.getEventType() != XmlPullParser.END_DOCUMENT) {
String tagName = xpp.getName();
if (xpp.getEventType() == XmlPullParser.START_TAG) {
if ("locale".equals(tagName) && xpp.getAttributeCount() > 0 && xpp.getAttributeName(0).equals("name")) {
tagsList.add(xpp.getAttributeValue(0));
}
}
xpp.next();
}
} catch (XmlPullParserException | IOException e) {
e.printStackTrace();
}
return LocaleListCompat.forLanguageTags(String.join(",", tagsList));
}
public static Map<String, String> getLangPreferenceDropdownEntries(Context context) {
LocaleListCompat localeList = getLocalesFromResources(context);
Map<String, String> map = new HashMap<>();
for (int i = 0; i < localeList.size(); i++) {
Locale locale = localeList.get(i);
if (locale != null) {
map.put(Util.toPascalCase(locale.getDisplayName()), locale.toLanguageTag());
}
}
return map;
}
public static String getReadableDate(Date date) {
SimpleDateFormat formatter = new SimpleDateFormat("dd MMM, yyyy", Locale.getDefault());
return formatter.format(date);
}
} }

View File

@@ -1,5 +1,8 @@
package com.cappielloantonio.tempo.util; 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.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function; import java.util.function.Function;
@@ -14,4 +17,48 @@ public class Util {
return null; 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;
}
}
} }

View File

@@ -11,9 +11,11 @@ import com.cappielloantonio.tempo.interfaces.StarCallback;
import com.cappielloantonio.tempo.repository.AlbumRepository; import com.cappielloantonio.tempo.repository.AlbumRepository;
import com.cappielloantonio.tempo.repository.ArtistRepository; import com.cappielloantonio.tempo.repository.ArtistRepository;
import com.cappielloantonio.tempo.repository.FavoriteRepository; import com.cappielloantonio.tempo.repository.FavoriteRepository;
import com.cappielloantonio.tempo.repository.SharingRepository;
import com.cappielloantonio.tempo.subsonic.models.AlbumID3; import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
import com.cappielloantonio.tempo.subsonic.models.ArtistID3; import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
import com.cappielloantonio.tempo.subsonic.models.Child; import com.cappielloantonio.tempo.subsonic.models.Child;
import com.cappielloantonio.tempo.subsonic.models.Share;
import com.cappielloantonio.tempo.util.NetworkUtil; import com.cappielloantonio.tempo.util.NetworkUtil;
import java.util.Date; import java.util.Date;
@@ -23,6 +25,7 @@ public class AlbumBottomSheetViewModel extends AndroidViewModel {
private final AlbumRepository albumRepository; private final AlbumRepository albumRepository;
private final ArtistRepository artistRepository; private final ArtistRepository artistRepository;
private final FavoriteRepository favoriteRepository; private final FavoriteRepository favoriteRepository;
private final SharingRepository sharingRepository;
private AlbumID3 album; private AlbumID3 album;
@@ -32,6 +35,7 @@ public class AlbumBottomSheetViewModel extends AndroidViewModel {
albumRepository = new AlbumRepository(); albumRepository = new AlbumRepository();
artistRepository = new ArtistRepository(); artistRepository = new ArtistRepository();
favoriteRepository = new FavoriteRepository(); favoriteRepository = new FavoriteRepository();
sharingRepository = new SharingRepository();
} }
public AlbumID3 getAlbum() { public AlbumID3 getAlbum() {
@@ -66,6 +70,10 @@ public class AlbumBottomSheetViewModel extends AndroidViewModel {
} }
} }
public MutableLiveData<Share> shareAlbum() {
return sharingRepository.createShare(album.getId(), album.getName(), null);
}
private void removeFavoriteOffline() { private void removeFavoriteOffline() {
favoriteRepository.starLater(null, album.getId(), null, false); favoriteRepository.starLater(null, album.getId(), null, false);
album.setStarred(null); album.setStarred(null);

View File

@@ -1,6 +1,7 @@
package com.cappielloantonio.tempo.viewmodel; package com.cappielloantonio.tempo.viewmodel;
import android.app.Application; import android.app.Application;
import android.util.Log;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel; import androidx.lifecycle.AndroidViewModel;
@@ -11,7 +12,6 @@ import androidx.lifecycle.MutableLiveData;
import com.cappielloantonio.tempo.model.DownloadStack; import com.cappielloantonio.tempo.model.DownloadStack;
import com.cappielloantonio.tempo.repository.DownloadRepository; import com.cappielloantonio.tempo.repository.DownloadRepository;
import com.cappielloantonio.tempo.subsonic.models.Child; import com.cappielloantonio.tempo.subsonic.models.Child;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.Preferences; import com.cappielloantonio.tempo.util.Preferences;
import java.util.ArrayList; import java.util.ArrayList;

View File

@@ -15,10 +15,12 @@ import com.cappielloantonio.tempo.repository.AlbumRepository;
import com.cappielloantonio.tempo.repository.ArtistRepository; import com.cappielloantonio.tempo.repository.ArtistRepository;
import com.cappielloantonio.tempo.repository.ChronologyRepository; import com.cappielloantonio.tempo.repository.ChronologyRepository;
import com.cappielloantonio.tempo.repository.FavoriteRepository; import com.cappielloantonio.tempo.repository.FavoriteRepository;
import com.cappielloantonio.tempo.repository.SharingRepository;
import com.cappielloantonio.tempo.repository.SongRepository; import com.cappielloantonio.tempo.repository.SongRepository;
import com.cappielloantonio.tempo.subsonic.models.AlbumID3; import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
import com.cappielloantonio.tempo.subsonic.models.ArtistID3; import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
import com.cappielloantonio.tempo.subsonic.models.Child; import com.cappielloantonio.tempo.subsonic.models.Child;
import com.cappielloantonio.tempo.subsonic.models.Share;
import com.cappielloantonio.tempo.util.Preferences; import com.cappielloantonio.tempo.util.Preferences;
import java.util.ArrayList; import java.util.ArrayList;
@@ -36,6 +38,7 @@ public class HomeViewModel extends AndroidViewModel {
private final ArtistRepository artistRepository; private final ArtistRepository artistRepository;
private final ChronologyRepository chronologyRepository; private final ChronologyRepository chronologyRepository;
private final FavoriteRepository favoriteRepository; private final FavoriteRepository favoriteRepository;
private final SharingRepository sharingRepository;
private final MutableLiveData<List<Child>> dicoverSongSample = new MutableLiveData<>(null); private final MutableLiveData<List<Child>> dicoverSongSample = new MutableLiveData<>(null);
private final MutableLiveData<List<AlbumID3>> newReleasedAlbum = new MutableLiveData<>(null); private final MutableLiveData<List<AlbumID3>> newReleasedAlbum = new MutableLiveData<>(null);
@@ -54,6 +57,8 @@ public class HomeViewModel extends AndroidViewModel {
private final MutableLiveData<List<Child>> mediaInstantMix = new MutableLiveData<>(null); private final MutableLiveData<List<Child>> mediaInstantMix = new MutableLiveData<>(null);
private final MutableLiveData<List<Child>> artistInstantMix = new MutableLiveData<>(null); private final MutableLiveData<List<Child>> artistInstantMix = new MutableLiveData<>(null);
private final MutableLiveData<List<Child>> artistBestOf = new MutableLiveData<>(null); private final MutableLiveData<List<Child>> artistBestOf = new MutableLiveData<>(null);
private final MutableLiveData<List<Share>> shares = new MutableLiveData<>(null);
public HomeViewModel(@NonNull Application application) { public HomeViewModel(@NonNull Application application) {
super(application); super(application);
@@ -63,6 +68,7 @@ public class HomeViewModel extends AndroidViewModel {
artistRepository = new ArtistRepository(); artistRepository = new ArtistRepository();
chronologyRepository = new ChronologyRepository(); chronologyRepository = new ChronologyRepository();
favoriteRepository = new FavoriteRepository(); favoriteRepository = new FavoriteRepository();
sharingRepository = new SharingRepository();
setOfflineFavorite(); setOfflineFavorite();
} }
@@ -204,6 +210,14 @@ public class HomeViewModel extends AndroidViewModel {
return artistBestOf; return artistBestOf;
} }
public LiveData<List<Share>> getShares(LifecycleOwner owner) {
if (shares.getValue() == null) {
sharingRepository.getShares().observe(owner, shares::postValue);
}
return shares;
}
public LiveData<List<Child>> getAllStarredTracks() { public LiveData<List<Child>> getAllStarredTracks() {
return songRepository.getStarredSongs(false, -1); return songRepository.getStarredSongs(false, -1);
} }
@@ -248,6 +262,10 @@ public class HomeViewModel extends AndroidViewModel {
albumRepository.getAlbums("recent", 20, null, null).observe(owner, recentlyPlayedAlbumSample::postValue); albumRepository.getAlbums("recent", 20, null, null).observe(owner, recentlyPlayedAlbumSample::postValue);
} }
public void refreshShares(LifecycleOwner owner) {
sharingRepository.getShares().observe(owner, this.shares::postValue);
}
public void setOfflineFavorite() { public void setOfflineFavorite() {
ArrayList<Favorite> favorites = getFavorites(); ArrayList<Favorite> favorites = getFavorites();
ArrayList<Favorite> favoritesToSave = getFavoritesToSave(favorites); ArrayList<Favorite> favoritesToSave = getFavoritesToSave(favorites);

View File

@@ -23,8 +23,8 @@ public class IndexViewModel extends AndroidViewModel {
directoryRepository = new DirectoryRepository(); directoryRepository = new DirectoryRepository();
} }
public MutableLiveData<Indexes> getIndexes() { public MutableLiveData<Indexes> getIndexes(String musicFolderId) {
return directoryRepository.getIndexes(null, null); return directoryRepository.getIndexes(musicFolderId, null);
} }
public String getMusicFolderName() { public String getMusicFolderName() {

View File

@@ -8,8 +8,10 @@ import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
import com.cappielloantonio.tempo.repository.PlaylistRepository; import com.cappielloantonio.tempo.repository.PlaylistRepository;
import com.cappielloantonio.tempo.repository.SharingRepository;
import com.cappielloantonio.tempo.subsonic.models.Child; import com.cappielloantonio.tempo.subsonic.models.Child;
import com.cappielloantonio.tempo.subsonic.models.Playlist; import com.cappielloantonio.tempo.subsonic.models.Playlist;
import com.cappielloantonio.tempo.subsonic.models.Share;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@@ -20,6 +22,7 @@ public class PlaylistEditorViewModel extends AndroidViewModel {
private static final String TAG = "PlaylistEditorViewModel"; private static final String TAG = "PlaylistEditorViewModel";
private final PlaylistRepository playlistRepository; private final PlaylistRepository playlistRepository;
private final SharingRepository sharingRepository;
private Child toAdd; private Child toAdd;
private Playlist toEdit; private Playlist toEdit;
@@ -30,6 +33,7 @@ public class PlaylistEditorViewModel extends AndroidViewModel {
super(application); super(application);
playlistRepository = new PlaylistRepository(); playlistRepository = new PlaylistRepository();
sharingRepository = new SharingRepository();
} }
public void createPlaylist(String name) { public void createPlaylist(String name) {
@@ -37,8 +41,7 @@ public class PlaylistEditorViewModel extends AndroidViewModel {
} }
public void updatePlaylist(String name) { public void updatePlaylist(String name) {
playlistRepository.deletePlaylist(toEdit.getId()); playlistRepository.updatePlaylist(toEdit.getId(), name, getPlaylistSongIds());
playlistRepository.createPlaylist(toEdit.getId(), name, getPlaylistSongIds());
} }
public void deletePlaylist() { public void deletePlaylist() {
@@ -93,4 +96,8 @@ public class PlaylistEditorViewModel extends AndroidViewModel {
return ids; return ids;
} }
public MutableLiveData<Share> sharePlaylist() {
return sharingRepository.createShare(toEdit.getId(), toEdit.getName(), null);
}
} }

View File

@@ -0,0 +1,37 @@
package com.cappielloantonio.tempo.viewmodel;
import android.app.Application;
import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import com.cappielloantonio.tempo.repository.SharingRepository;
import com.cappielloantonio.tempo.subsonic.models.Share;
public class ShareBottomSheetViewModel extends AndroidViewModel {
private final SharingRepository sharingRepository;
private Share share;
public ShareBottomSheetViewModel(@NonNull Application application) {
super(application);
sharingRepository = new SharingRepository();
}
public Share getShare() {
return share;
}
public void setShare(Share share) {
this.share = share;
}
public void updateShare(String description, long expires) {
sharingRepository.updateShare(share.getId(), description, expires);
}
public void deleteShare() {
sharingRepository.deleteShare(share.getId());
}
}

View File

@@ -15,10 +15,12 @@ import com.cappielloantonio.tempo.model.Download;
import com.cappielloantonio.tempo.repository.AlbumRepository; import com.cappielloantonio.tempo.repository.AlbumRepository;
import com.cappielloantonio.tempo.repository.ArtistRepository; import com.cappielloantonio.tempo.repository.ArtistRepository;
import com.cappielloantonio.tempo.repository.FavoriteRepository; import com.cappielloantonio.tempo.repository.FavoriteRepository;
import com.cappielloantonio.tempo.repository.SharingRepository;
import com.cappielloantonio.tempo.repository.SongRepository; import com.cappielloantonio.tempo.repository.SongRepository;
import com.cappielloantonio.tempo.subsonic.models.AlbumID3; import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
import com.cappielloantonio.tempo.subsonic.models.ArtistID3; import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
import com.cappielloantonio.tempo.subsonic.models.Child; import com.cappielloantonio.tempo.subsonic.models.Child;
import com.cappielloantonio.tempo.subsonic.models.Share;
import com.cappielloantonio.tempo.util.DownloadUtil; import com.cappielloantonio.tempo.util.DownloadUtil;
import com.cappielloantonio.tempo.util.MappingUtil; import com.cappielloantonio.tempo.util.MappingUtil;
import com.cappielloantonio.tempo.util.NetworkUtil; import com.cappielloantonio.tempo.util.NetworkUtil;
@@ -34,6 +36,7 @@ public class SongBottomSheetViewModel extends AndroidViewModel {
private final AlbumRepository albumRepository; private final AlbumRepository albumRepository;
private final ArtistRepository artistRepository; private final ArtistRepository artistRepository;
private final FavoriteRepository favoriteRepository; private final FavoriteRepository favoriteRepository;
private final SharingRepository sharingRepository;
private Child song; private Child song;
@@ -46,6 +49,7 @@ public class SongBottomSheetViewModel extends AndroidViewModel {
albumRepository = new AlbumRepository(); albumRepository = new AlbumRepository();
artistRepository = new ArtistRepository(); artistRepository = new ArtistRepository();
favoriteRepository = new FavoriteRepository(); favoriteRepository = new FavoriteRepository();
sharingRepository = new SharingRepository();
} }
public Child getSong() { public Child getSong() {
@@ -128,4 +132,8 @@ public class SongBottomSheetViewModel extends AndroidViewModel {
return instantMix; return instantMix;
} }
public MutableLiveData<Share> shareTrack() {
return sharingRepository.createShare(song.getId(), song.getTitle(), null);
}
} }

View File

@@ -71,11 +71,17 @@ public class SongListPageViewModel extends AndroidViewModel {
public void getSongsByPage(LifecycleOwner owner) { public void getSongsByPage(LifecycleOwner owner) {
switch (title) { switch (title) {
case Constants.MEDIA_BY_GENRE: case Constants.MEDIA_BY_GENRE:
int page = (songList.getValue() != null ? songList.getValue().size() : 0) / 100; int songCount = songList.getValue() != null ? songList.getValue().size() : 0;
if (songCount > 0 && songCount % 100 != 0) return;
int page = songCount / 100;
songRepository.getSongsByGenre(genre.getGenre(), page).observe(owner, children -> { songRepository.getSongsByGenre(genre.getGenre(), page).observe(owner, children -> {
List<Child> currentMedia = songList.getValue(); if (children != null && !children.isEmpty()) {
currentMedia.addAll(children); List<Child> currentMedia = songList.getValue();
songList.setValue(currentMedia); currentMedia.addAll(children);
songList.setValue(currentMedia);
}
}); });
break; break;
case Constants.MEDIA_BY_ARTIST: case Constants.MEDIA_BY_ARTIST:

View 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>

Some files were not shown because too many files have changed in this diff Show More