173 Commits
dev ... 3.5.6

Author SHA1 Message Date
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
antonio
0c2f0d23cd build: bump up code version 2023-08-14 11:46:33 +02:00
CappielloAntonio
dc201e6c8f Merge pull request #35 from ThePBone/fix-npe
fix: handle null values for genres, artist ids, and album ids in DownloadHorizontalAdapter
2023-08-14 00:44:24 +02:00
Tim Schneeberger
9bf3399371 fix: handle null values for genres, artists, and albums in DownloadHorizontalAdapter 2023-08-14 00:30:44 +02:00
antonio
28565b691a feat: always display the download button for tracks as users can now re-download the same track multiple times, either by relying on the original download endpoint or setting their preferred codec and bitrate 2023-08-13 23:20:01 +02:00
antonio
7c6faf66c1 feat: implemented logic for track download with codec and bitrate definition 2023-08-13 23:18:17 +02:00
antonio
b160274859 feat: added options in settings to define codec and bitrate for downloaded tracks 2023-08-13 23:17:24 +02:00
antonio
3a1ced65d5 feat: added options in settings to define codec and bitrate for downloaded tracks 2023-08-13 23:17:05 +02:00
antonio
afeb72803d fix: added file URI to Download object for improved download indexing 2023-08-13 23:15:25 +02:00
antonio
600c28c2a3 fix: added file URI to Download object for improved download indexing 2023-08-13 20:27:38 +02:00
antonio
ec799fff96 feat: included download URI in the download class 2023-08-13 20:16:06 +02:00
antonio
60da08cdbc feat: implemented folder download feature for on-screen visible files (not folder and files in subfolder) 2023-08-12 23:25:00 +02:00
antonio
bdda3743db style: made the distinction between folders and files visible in folder navigation 2023-08-12 23:14:44 +02:00
antonio
59fcf11ae3 build: started preparations for F-Droid release 2023-08-12 21:36:13 +02:00
antonio
77c80b7695 style: add graphic constraints 2023-08-12 11:27:55 +02:00
CappielloAntonio
13f168f78c Merge pull request #33 from dnno/fix/update-german-localization
fix: update german localization
2023-08-12 11:20:52 +02:00
antonio
324eed7e02 Merge remote-tracking branch 'origin/main' 2023-08-12 11:19:43 +02:00
antonio
4a99c7e9b1 feat: implemented last set filter as default grouping for downloaded tracks 2023-08-12 11:17:46 +02:00
antonio
01e5917642 clean: increase distance between the title and the list 2023-08-12 10:57:36 +02:00
Reinhard Prechtl
ae00f4279e Fix translation error 2023-08-11 21:05:10 +02:00
Reinhard Prechtl
642c69eb96 Update german localization resource
- Remove non-translatable strings
- Add previously untranslated strings
2023-08-11 19:59:56 +02:00
Reinhard Prechtl
ac69361735 Mark placeholder and separator as not translatable 2023-08-11 19:59:35 +02:00
CappielloAntonio
e24efc4948 Merge pull request #32 from dnno/feat/add-german-localization
feat: add german localization
2023-08-11 16:44:01 +02:00
CappielloAntonio
f8ad18ed5a Merge pull request #30 from dnno/fix/hardcoded-strings
clean: extract hard coded strings to resources
2023-08-11 16:42:30 +02:00
antonio
17345372a2 fix: set placeholder if track number is null 2023-08-11 16:37:57 +02:00
antonio
db76494525 fix: mediaitem null-proofing 2023-08-11 16:37:14 +02:00
antonio
e87eda2757 feat: added the ability to filter and group downloaded songs 2023-08-11 16:23:53 +02:00
antonio
06e2729aca feat: created contextual menu for filtering and grouping downloaded songs 2023-08-11 16:23:01 +02:00
antonio
37ffb88d67 fix: null checking 2023-08-11 16:20:28 +02:00
antonio
3d4437151a build: dependencies update 2023-08-11 16:20:02 +02:00
Reinhard Prechtl
af1961b185 Add further localized strings and mark settings version as non translatable 2023-08-07 20:12:52 +02:00
Reinhard Prechtl
c983e33522 Fix typo in localization 2023-08-07 20:01:21 +02:00
Reinhard Prechtl
b18daec708 Add further localized strings 2023-08-07 20:01:11 +02:00
Reinhard Prechtl
6d20995e70 Add german localization to resources 2023-08-07 20:01:02 +02:00
Reinhard Prechtl
14cacd1bbc Extract hard coded strings to resources 2023-08-06 22:39:57 +02:00
antonio
9d7acdb892 feat: added the option in the settings to delete all saved offline content. 2023-08-06 00:01:02 +02:00
antonio
05325913f7 fix: semantic error 2023-08-05 11:57:22 +02:00
antonio
5733dca68a feat: implemented the ability to choose external storage (if available) as storage for offline file downloads 2023-08-04 23:46:33 +02:00
antonio
838d4496e5 fix: semantic error 2023-08-04 23:42:43 +02:00
antonio
4967363116 build: added foreground service types and permissions 2023-08-04 23:41:30 +02:00
antonio
fc61308be5 feat: added downloaded file title to notification 2023-08-03 07:22:28 +02:00
antonio
5c4a292542 feat: preventing closing of bottom sheet dialog when heart icon is clicked 2023-08-02 15:13:19 +02:00
antonio
aa68c2c3d8 ci: upload database schemas 2023-08-02 11:30:26 +02:00
antonio
9c0ebca66f feat: created and invoked methods for checking starred/unstarred items from favorites when offline 2023-08-02 10:53:21 +02:00
antonio
888f177597 refactor: update repository references for adding/removing favorites 2023-08-02 10:51:13 +02:00
antonio
c69fbcfa68 feat: centralized star/unstar handling and added offline support 2023-08-02 10:49:21 +02:00
antonio
f044db142c feat: created class for storing information on tracks, albums, and artists starred or unstarred while offline or when the server was unreachable 2023-08-02 10:47:37 +02:00
antonio
9af7bc3ac8 feat: started work on centralizing network state monitoring controls 2023-08-02 10:44:53 +02:00
antonio
cd87fcde26 refactor: removed common starring methods from repository classes 2023-08-02 10:43:29 +02:00
antonio
9e1a6c804f fix: null checking 2023-08-01 21:37:47 +02:00
antonio
4c15f6eb01 fix: set new default color for artist folder images 2023-08-01 10:35:46 +02:00
antonio
4ad2722e81 feat: added fast scrollbar to folder navigation screen. 2023-08-01 10:34:39 +02:00
antonio
b267b904cc feat: added the ability to request download for an unplayed podcast episode 2023-07-31 11:39:35 +02:00
antonio
1ebe9ff8ba fix: resized splash icon 2023-07-30 18:13:32 +02:00
antonio
623a4956a5 fix: limit the number of playlists' tracks queued 2023-07-30 16:58:26 +02:00
antonio
6572b846c8 fix: fixed boolean evaluation in the code 2023-07-30 16:57:18 +02:00
antonio
fe3ba9fb89 fix: null checking 2023-07-30 16:12:32 +02:00
antonio
c4b9db303a feat: improved stability and added cover image for playlist section 2023-07-30 15:51:03 +02:00
antonio
aed52fdbf8 clean: code cleanup 2023-07-30 12:45:51 +02:00
antonio
f9573b3eab feat: updated podcast channel UI 2023-07-30 12:23:49 +02:00
antonio
7a8880ee68 clean: code cleanup 2023-07-30 12:22:38 +02:00
antonio
68aae32d06 feat: add filter to display podcast episodes not yet downloaded 2023-07-30 12:22:02 +02:00
antonio
4b07f37378 fix: fixed the issue with opening podcast channels from the catalog view. 2023-07-29 23:46:47 +02:00
antonio
4d573c6b9d fix: resolved issue with scrobbling the last track 2023-07-29 23:45:34 +02:00
antonio
10dcb2380c gradle: dependency update 2023-07-29 17:21:05 +02:00
antonio
fcbe4377aa gradle: bump up code version 2023-07-20 17:46:49 +02:00
antonio
84db4060e6 feat: added server-side track transcoding settings option 2023-07-20 17:20:57 +02:00
antonio
b73a1c532b fix: checking for the presence of a system equalizer 2023-07-20 12:33:10 +02:00
antonio
4fe27067e9 fix: null checking 2023-07-20 09:48:12 +02:00
antonio
9c6981ed19 gradle: bump up code version 2023-07-18 12:45:03 +02:00
antonio
560ac2df68 fix: fixed search functionality 2023-07-18 10:37:17 +02:00
antonio
260e6e9daa style: temp - icon splash 2023-07-17 11:11:12 +02:00
antonio
38a1368c76 style: icon toolbar 2023-07-17 11:10:56 +02:00
antonio
9bf3139bd2 style: icon launcher 2023-07-17 11:10:44 +02:00
antonio
d389d1d62a style: icon launcher 2023-07-17 11:10:06 +02:00
antonio
e60c0e312c fix: null checking 2023-07-17 10:55:40 +02:00
CappielloAntonio
dc13beca49 Update github_release.yml 2023-07-12 10:16:27 +02:00
antonio
6399488819 Merge remote-tracking branch 'origin/main' 2023-07-12 10:04:45 +02:00
antonio
69c1b93d28 gradle: bump up code version 2023-07-12 10:04:36 +02:00
antonio
50c240793a gradle: update dependencies 2023-07-12 10:03:52 +02:00
CappielloAntonio
80f7783b7d Update github_release.yml 2023-07-12 10:03:17 +02:00
218 changed files with 8048 additions and 953 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
@@ -34,13 +30,13 @@ jobs:
- name: Build APK - name: Build APK
id: build id: build
run: bash ./gradlew assembleRelease run: bash ./gradlew assembleTempoRelease
- name: Sign APK - name: Sign APK
id: sign_apk id: sign_apk
uses: r0adkll/sign-android-release@v1 uses: r0adkll/sign-android-release@v1
with: with:
releaseDirectory: app/build/outputs/apk/release releaseDirectory: app/build/outputs/apk/tempo/release
signingKeyBase64: ${{ secrets.KEYSTORE_BASE64 }} signingKeyBase64: ${{ secrets.KEYSTORE_BASE64 }}
alias: ${{ secrets.KEY_ALIAS_GITHUB }} alias: ${{ secrets.KEY_ALIAS_GITHUB }}
keyStorePassword: ${{ secrets.KEYSTORE_PASSWORD }} keyStorePassword: ${{ secrets.KEYSTORE_PASSWORD }}
@@ -98,7 +94,7 @@ jobs:
with: with:
upload_url: ${{ steps.create_release.outputs.upload_url }} upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ${{steps.sign_apk.outputs.signedReleaseFile}} asset_path: ${{steps.sign_apk.outputs.signedReleaseFile}}
asset_name: app-release.apk asset_name: app-tempo-release.apk
asset_content_type: application/zip asset_content_type: application/zip
# - name: Upload AAB # - name: Upload AAB

2
.idea/gradle.xml generated
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 11 versionCode 21
versionName '3.4.4' versionName '3.5.6'
} }
notquitemy { notquitemy {
@@ -71,28 +71,29 @@ dependencies {
// AndroidX // AndroidX
implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.2.0' implementation 'androidx.coordinatorlayout:coordinatorlayout:1.2.0'
implementation 'androidx.preference:preference-ktx:1.2.0' implementation 'androidx.preference:preference-ktx:1.2.1'
implementation 'androidx.navigation:navigation-fragment-ktx:2.6.0' implementation 'androidx.navigation:navigation-fragment-ktx:2.7.2'
implementation 'androidx.navigation:navigation-ui-ktx:2.6.0' implementation 'androidx.navigation:navigation-ui-ktx:2.7.2'
implementation 'androidx.recyclerview:recyclerview:1.3.0' implementation 'androidx.recyclerview:recyclerview:1.3.1'
implementation 'androidx.room:room-runtime:2.5.2' implementation 'androidx.room:room-runtime:2.5.2'
implementation 'androidx.core:core-splashscreen:1.0.1' implementation 'androidx.core:core-splashscreen:1.0.1'
implementation "androidx.appcompat:appcompat:1.6.1"
// Android Material // Android Material
implementation 'com.google.android.material:material:1.9.0' implementation 'com.google.android.material:material:1.9.0'
// Glide // Glide
implementation 'com.github.bumptech.glide:glide:4.15.1' implementation 'com.github.bumptech.glide:glide:4.16.0'
implementation 'com.github.bumptech.glide:annotations:4.15.1' implementation 'com.github.bumptech.glide:annotations:4.16.0'
// Media3 // Media3
implementation 'androidx.media3:media3-session:1.0.2' implementation 'androidx.media3:media3-session:1.1.1'
implementation 'androidx.media3:media3-common:1.0.2' implementation 'androidx.media3:media3-common:1.1.1'
implementation 'androidx.media3:media3-exoplayer:1.0.2' implementation 'androidx.media3:media3-exoplayer:1.1.1'
implementation 'androidx.media3:media3-ui:1.0.2' implementation 'androidx.media3:media3-ui:1.1.1'
tempoImplementation 'androidx.media3:media3-cast:1.0.2' tempoImplementation 'androidx.media3:media3-cast:1.1.1'
annotationProcessor 'com.github.bumptech.glide:compiler:4.15.1' annotationProcessor 'com.github.bumptech.glide:compiler:4.16.0'
annotationProcessor 'androidx.room:room-compiler:2.5.2' annotationProcessor 'androidx.room:room-compiler:2.5.2'
// Retrofit // Retrofit

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,746 @@
{
"formatVersion": 1,
"database": {
"version": 1,
"identityHash": "1f4e50f90f58fb9cb53c89747d142fd9",
"entities": [
{
"tableName": "queue",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `track_order` INTEGER NOT NULL, `last_play` INTEGER NOT NULL, `playing_changed` INTEGER NOT NULL, `stream_id` TEXT, `parent_id` TEXT, `is_dir` INTEGER NOT NULL, `title` TEXT, `album` TEXT, `artist` TEXT, `track` INTEGER, `year` INTEGER, `genre` TEXT, `cover_art_id` TEXT, `size` INTEGER, `content_type` TEXT, `suffix` TEXT, `transcoding_content_type` TEXT, `transcoded_suffix` TEXT, `duration` INTEGER, `bitrate` INTEGER, `path` TEXT, `is_video` INTEGER NOT NULL, `user_rating` INTEGER, `average_rating` REAL, `play_count` INTEGER, `disc_number` INTEGER, `created` INTEGER, `starred` INTEGER, `album_id` TEXT, `artist_id` TEXT, `type` TEXT, `bookmark_position` INTEGER, `original_width` INTEGER, `original_height` INTEGER, PRIMARY KEY(`track_order`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "trackOrder",
"columnName": "track_order",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "lastPlay",
"columnName": "last_play",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "playingChanged",
"columnName": "playing_changed",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "streamId",
"columnName": "stream_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "parentId",
"columnName": "parent_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "isDir",
"columnName": "is_dir",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "album",
"columnName": "album",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "artist",
"columnName": "artist",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "track",
"columnName": "track",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "year",
"columnName": "year",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "genre",
"columnName": "genre",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "coverArtId",
"columnName": "cover_art_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "size",
"columnName": "size",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "contentType",
"columnName": "content_type",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "suffix",
"columnName": "suffix",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "transcodedContentType",
"columnName": "transcoding_content_type",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "transcodedSuffix",
"columnName": "transcoded_suffix",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "duration",
"columnName": "duration",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "bitrate",
"columnName": "bitrate",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "path",
"columnName": "path",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "isVideo",
"columnName": "is_video",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "userRating",
"columnName": "user_rating",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "averageRating",
"columnName": "average_rating",
"affinity": "REAL",
"notNull": false
},
{
"fieldPath": "playCount",
"columnName": "play_count",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "discNumber",
"columnName": "disc_number",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "created",
"columnName": "created",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "starred",
"columnName": "starred",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "albumId",
"columnName": "album_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "artistId",
"columnName": "artist_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "bookmarkPosition",
"columnName": "bookmark_position",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "originalWidth",
"columnName": "original_width",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "originalHeight",
"columnName": "original_height",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"track_order"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "server",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `server_name` TEXT NOT NULL, `username` TEXT NOT NULL, `password` TEXT NOT NULL, `address` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, `low_security` INTEGER NOT NULL DEFAULT false, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "serverId",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "serverName",
"columnName": "server_name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "username",
"columnName": "username",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "password",
"columnName": "password",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "address",
"columnName": "address",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "timestamp",
"columnName": "timestamp",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "isLowSecurity",
"columnName": "low_security",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "recent_search",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`search` TEXT NOT NULL, PRIMARY KEY(`search`))",
"fields": [
{
"fieldPath": "search",
"columnName": "search",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"search"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "download",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `playlist_id` TEXT, `playlist_name` TEXT, `download_state` INTEGER NOT NULL DEFAULT 1, `parent_id` TEXT, `is_dir` INTEGER NOT NULL, `title` TEXT, `album` TEXT, `artist` TEXT, `track` INTEGER, `year` INTEGER, `genre` TEXT, `cover_art_id` TEXT, `size` INTEGER, `content_type` TEXT, `suffix` TEXT, `transcoding_content_type` TEXT, `transcoded_suffix` TEXT, `duration` INTEGER, `bitrate` INTEGER, `path` TEXT, `is_video` INTEGER NOT NULL, `user_rating` INTEGER, `average_rating` REAL, `play_count` INTEGER, `disc_number` INTEGER, `created` INTEGER, `starred` INTEGER, `album_id` TEXT, `artist_id` TEXT, `type` TEXT, `bookmark_position` INTEGER, `original_width` INTEGER, `original_height` INTEGER, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "playlistId",
"columnName": "playlist_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "playlistName",
"columnName": "playlist_name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "downloadState",
"columnName": "download_state",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "1"
},
{
"fieldPath": "parentId",
"columnName": "parent_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "isDir",
"columnName": "is_dir",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "album",
"columnName": "album",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "artist",
"columnName": "artist",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "track",
"columnName": "track",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "year",
"columnName": "year",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "genre",
"columnName": "genre",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "coverArtId",
"columnName": "cover_art_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "size",
"columnName": "size",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "contentType",
"columnName": "content_type",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "suffix",
"columnName": "suffix",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "transcodedContentType",
"columnName": "transcoding_content_type",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "transcodedSuffix",
"columnName": "transcoded_suffix",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "duration",
"columnName": "duration",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "bitrate",
"columnName": "bitrate",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "path",
"columnName": "path",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "isVideo",
"columnName": "is_video",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "userRating",
"columnName": "user_rating",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "averageRating",
"columnName": "average_rating",
"affinity": "REAL",
"notNull": false
},
{
"fieldPath": "playCount",
"columnName": "play_count",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "discNumber",
"columnName": "disc_number",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "created",
"columnName": "created",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "starred",
"columnName": "starred",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "albumId",
"columnName": "album_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "artistId",
"columnName": "artist_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "bookmarkPosition",
"columnName": "bookmark_position",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "originalWidth",
"columnName": "original_width",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "originalHeight",
"columnName": "original_height",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "chronology",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, `server` TEXT, `parent_id` TEXT, `is_dir` INTEGER NOT NULL, `title` TEXT, `album` TEXT, `artist` TEXT, `track` INTEGER, `year` INTEGER, `genre` TEXT, `cover_art_id` TEXT, `size` INTEGER, `content_type` TEXT, `suffix` TEXT, `transcoding_content_type` TEXT, `transcoded_suffix` TEXT, `duration` INTEGER, `bitrate` INTEGER, `path` TEXT, `is_video` INTEGER NOT NULL, `user_rating` INTEGER, `average_rating` REAL, `play_count` INTEGER, `disc_number` INTEGER, `created` INTEGER, `starred` INTEGER, `album_id` TEXT, `artist_id` TEXT, `type` TEXT, `bookmark_position` INTEGER, `original_width` INTEGER, `original_height` INTEGER, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "timestamp",
"columnName": "timestamp",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "server",
"columnName": "server",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "parentId",
"columnName": "parent_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "isDir",
"columnName": "is_dir",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "album",
"columnName": "album",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "artist",
"columnName": "artist",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "track",
"columnName": "track",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "year",
"columnName": "year",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "genre",
"columnName": "genre",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "coverArtId",
"columnName": "cover_art_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "size",
"columnName": "size",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "contentType",
"columnName": "content_type",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "suffix",
"columnName": "suffix",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "transcodedContentType",
"columnName": "transcoding_content_type",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "transcodedSuffix",
"columnName": "transcoded_suffix",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "duration",
"columnName": "duration",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "bitrate",
"columnName": "bitrate",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "path",
"columnName": "path",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "isVideo",
"columnName": "is_video",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "userRating",
"columnName": "user_rating",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "averageRating",
"columnName": "average_rating",
"affinity": "REAL",
"notNull": false
},
{
"fieldPath": "playCount",
"columnName": "play_count",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "discNumber",
"columnName": "disc_number",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "created",
"columnName": "created",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "starred",
"columnName": "starred",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "albumId",
"columnName": "album_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "artistId",
"columnName": "artist_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "bookmarkPosition",
"columnName": "bookmark_position",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "originalWidth",
"columnName": "original_width",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "originalHeight",
"columnName": "original_height",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '1f4e50f90f58fb9cb53c89747d142fd9')"
]
}
}

View File

@@ -0,0 +1,790 @@
{
"formatVersion": 1,
"database": {
"version": 2,
"identityHash": "ff99e331b4c34a82c560588c4dd5735f",
"entities": [
{
"tableName": "queue",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `track_order` INTEGER NOT NULL, `last_play` INTEGER NOT NULL, `playing_changed` INTEGER NOT NULL, `stream_id` TEXT, `parent_id` TEXT, `is_dir` INTEGER NOT NULL, `title` TEXT, `album` TEXT, `artist` TEXT, `track` INTEGER, `year` INTEGER, `genre` TEXT, `cover_art_id` TEXT, `size` INTEGER, `content_type` TEXT, `suffix` TEXT, `transcoding_content_type` TEXT, `transcoded_suffix` TEXT, `duration` INTEGER, `bitrate` INTEGER, `path` TEXT, `is_video` INTEGER NOT NULL, `user_rating` INTEGER, `average_rating` REAL, `play_count` INTEGER, `disc_number` INTEGER, `created` INTEGER, `starred` INTEGER, `album_id` TEXT, `artist_id` TEXT, `type` TEXT, `bookmark_position` INTEGER, `original_width` INTEGER, `original_height` INTEGER, PRIMARY KEY(`track_order`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "trackOrder",
"columnName": "track_order",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "lastPlay",
"columnName": "last_play",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "playingChanged",
"columnName": "playing_changed",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "streamId",
"columnName": "stream_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "parentId",
"columnName": "parent_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "isDir",
"columnName": "is_dir",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "album",
"columnName": "album",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "artist",
"columnName": "artist",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "track",
"columnName": "track",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "year",
"columnName": "year",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "genre",
"columnName": "genre",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "coverArtId",
"columnName": "cover_art_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "size",
"columnName": "size",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "contentType",
"columnName": "content_type",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "suffix",
"columnName": "suffix",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "transcodedContentType",
"columnName": "transcoding_content_type",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "transcodedSuffix",
"columnName": "transcoded_suffix",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "duration",
"columnName": "duration",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "bitrate",
"columnName": "bitrate",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "path",
"columnName": "path",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "isVideo",
"columnName": "is_video",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "userRating",
"columnName": "user_rating",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "averageRating",
"columnName": "average_rating",
"affinity": "REAL",
"notNull": false
},
{
"fieldPath": "playCount",
"columnName": "play_count",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "discNumber",
"columnName": "disc_number",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "created",
"columnName": "created",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "starred",
"columnName": "starred",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "albumId",
"columnName": "album_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "artistId",
"columnName": "artist_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "bookmarkPosition",
"columnName": "bookmark_position",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "originalWidth",
"columnName": "original_width",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "originalHeight",
"columnName": "original_height",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"track_order"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "server",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `server_name` TEXT NOT NULL, `username` TEXT NOT NULL, `password` TEXT NOT NULL, `address` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, `low_security` INTEGER NOT NULL DEFAULT false, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "serverId",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "serverName",
"columnName": "server_name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "username",
"columnName": "username",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "password",
"columnName": "password",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "address",
"columnName": "address",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "timestamp",
"columnName": "timestamp",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "isLowSecurity",
"columnName": "low_security",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "recent_search",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`search` TEXT NOT NULL, PRIMARY KEY(`search`))",
"fields": [
{
"fieldPath": "search",
"columnName": "search",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"search"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "download",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `playlist_id` TEXT, `playlist_name` TEXT, `download_state` INTEGER NOT NULL DEFAULT 1, `parent_id` TEXT, `is_dir` INTEGER NOT NULL, `title` TEXT, `album` TEXT, `artist` TEXT, `track` INTEGER, `year` INTEGER, `genre` TEXT, `cover_art_id` TEXT, `size` INTEGER, `content_type` TEXT, `suffix` TEXT, `transcoding_content_type` TEXT, `transcoded_suffix` TEXT, `duration` INTEGER, `bitrate` INTEGER, `path` TEXT, `is_video` INTEGER NOT NULL, `user_rating` INTEGER, `average_rating` REAL, `play_count` INTEGER, `disc_number` INTEGER, `created` INTEGER, `starred` INTEGER, `album_id` TEXT, `artist_id` TEXT, `type` TEXT, `bookmark_position` INTEGER, `original_width` INTEGER, `original_height` INTEGER, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "playlistId",
"columnName": "playlist_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "playlistName",
"columnName": "playlist_name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "downloadState",
"columnName": "download_state",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "1"
},
{
"fieldPath": "parentId",
"columnName": "parent_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "isDir",
"columnName": "is_dir",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "album",
"columnName": "album",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "artist",
"columnName": "artist",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "track",
"columnName": "track",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "year",
"columnName": "year",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "genre",
"columnName": "genre",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "coverArtId",
"columnName": "cover_art_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "size",
"columnName": "size",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "contentType",
"columnName": "content_type",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "suffix",
"columnName": "suffix",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "transcodedContentType",
"columnName": "transcoding_content_type",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "transcodedSuffix",
"columnName": "transcoded_suffix",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "duration",
"columnName": "duration",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "bitrate",
"columnName": "bitrate",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "path",
"columnName": "path",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "isVideo",
"columnName": "is_video",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "userRating",
"columnName": "user_rating",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "averageRating",
"columnName": "average_rating",
"affinity": "REAL",
"notNull": false
},
{
"fieldPath": "playCount",
"columnName": "play_count",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "discNumber",
"columnName": "disc_number",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "created",
"columnName": "created",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "starred",
"columnName": "starred",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "albumId",
"columnName": "album_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "artistId",
"columnName": "artist_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "bookmarkPosition",
"columnName": "bookmark_position",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "originalWidth",
"columnName": "original_width",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "originalHeight",
"columnName": "original_height",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "chronology",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, `server` TEXT, `parent_id` TEXT, `is_dir` INTEGER NOT NULL, `title` TEXT, `album` TEXT, `artist` TEXT, `track` INTEGER, `year` INTEGER, `genre` TEXT, `cover_art_id` TEXT, `size` INTEGER, `content_type` TEXT, `suffix` TEXT, `transcoding_content_type` TEXT, `transcoded_suffix` TEXT, `duration` INTEGER, `bitrate` INTEGER, `path` TEXT, `is_video` INTEGER NOT NULL, `user_rating` INTEGER, `average_rating` REAL, `play_count` INTEGER, `disc_number` INTEGER, `created` INTEGER, `starred` INTEGER, `album_id` TEXT, `artist_id` TEXT, `type` TEXT, `bookmark_position` INTEGER, `original_width` INTEGER, `original_height` INTEGER, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "timestamp",
"columnName": "timestamp",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "server",
"columnName": "server",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "parentId",
"columnName": "parent_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "isDir",
"columnName": "is_dir",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "album",
"columnName": "album",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "artist",
"columnName": "artist",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "track",
"columnName": "track",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "year",
"columnName": "year",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "genre",
"columnName": "genre",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "coverArtId",
"columnName": "cover_art_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "size",
"columnName": "size",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "contentType",
"columnName": "content_type",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "suffix",
"columnName": "suffix",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "transcodedContentType",
"columnName": "transcoding_content_type",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "transcodedSuffix",
"columnName": "transcoded_suffix",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "duration",
"columnName": "duration",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "bitrate",
"columnName": "bitrate",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "path",
"columnName": "path",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "isVideo",
"columnName": "is_video",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "userRating",
"columnName": "user_rating",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "averageRating",
"columnName": "average_rating",
"affinity": "REAL",
"notNull": false
},
{
"fieldPath": "playCount",
"columnName": "play_count",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "discNumber",
"columnName": "disc_number",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "created",
"columnName": "created",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "starred",
"columnName": "starred",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "albumId",
"columnName": "album_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "artistId",
"columnName": "artist_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "bookmarkPosition",
"columnName": "bookmark_position",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "originalWidth",
"columnName": "original_width",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "originalHeight",
"columnName": "original_height",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "favorite",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`timestamp` INTEGER NOT NULL, `songId` TEXT, `albumId` TEXT, `artistId` TEXT, `toStar` INTEGER NOT NULL, PRIMARY KEY(`timestamp`))",
"fields": [
{
"fieldPath": "timestamp",
"columnName": "timestamp",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "songId",
"columnName": "songId",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "albumId",
"columnName": "albumId",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "artistId",
"columnName": "artistId",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "toStar",
"columnName": "toStar",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"timestamp"
]
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'ff99e331b4c34a82c560588c4dd5735f')"
]
}
}

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,50 +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_MEDIA_PLAYBACK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<application <application
android:name="App" android:name="App"
android:allowBackup="false"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:localeConfig="@xml/locale_config"
android:roundIcon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/AppTheme.SplashScreen" android:theme="@style/AppTheme.SplashScreen"
android:usesCleartextTraffic="true" android:usesCleartextTraffic="true">
android:allowBackup="false">
<meta-data <meta-data
android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME" android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
android:value="androidx.media3.cast.DefaultCastOptionsProvider"/> android:value="androidx.media3.cast.DefaultCastOptionsProvider" />
<meta-data
android:name="androidx.car.app.TintableAttributionIcon"
android:resource="@drawable/ic_graphic_eq" />
<activity <activity
android:name=".ui.activity.MainActivity" android:name=".ui.activity.MainActivity"
android:windowSoftInputMode="adjustPan|adjustResize" android:exported="true"
android:exported="true"> android:windowSoftInputMode="adjustPan|adjustResize">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<service <service
android:name=".service.MediaService" android:name=".service.MediaService"
android:foregroundServiceType="mediaPlayback" android:exported="true"
android:exported="true"> android:foregroundServiceType="mediaPlayback">
<intent-filter> <intent-filter>
<action android:name="androidx.media3.session.MediaLibraryService" /> <action android:name="androidx.media3.session.MediaLibraryService" />
<action android:name="androidx.media3.session.MediaBrowserService" /> <action android:name="androidx.media3.session.MediaBrowserService" />
</intent-filter> </intent-filter>
</service> </service>
<service android:name=".service.DownloaderService"
android:exported="true"> <service
android:name=".service.DownloaderService"
android:exported="true"
android:foregroundServiceType="dataSync">
<intent-filter> <intent-filter>
<action android:name="androidx.media3.exoplayer.downloadService.action.RESTART"/> <action android:name="androidx.media3.exoplayer.downloadService.action.RESTART" />
<category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.DEFAULT" />
</intent-filter> </intent-filter>
</service> </service>
<service
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
android:enabled="false"
android:exported="false">
<meta-data
android:name="autoStoreLocales"
android:value="true" />
</service>
</application> </application>
</manifest> </manifest>

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

@@ -1,5 +1,6 @@
package com.cappielloantonio.tempo.database; package com.cappielloantonio.tempo.database;
import androidx.room.AutoMigration;
import androidx.room.Database; import androidx.room.Database;
import androidx.room.Room; import androidx.room.Room;
import androidx.room.RoomDatabase; import androidx.room.RoomDatabase;
@@ -9,19 +10,21 @@ import com.cappielloantonio.tempo.App;
import com.cappielloantonio.tempo.database.converter.DateConverters; import com.cappielloantonio.tempo.database.converter.DateConverters;
import com.cappielloantonio.tempo.database.dao.ChronologyDao; import com.cappielloantonio.tempo.database.dao.ChronologyDao;
import com.cappielloantonio.tempo.database.dao.DownloadDao; import com.cappielloantonio.tempo.database.dao.DownloadDao;
import com.cappielloantonio.tempo.database.dao.FavoriteDao;
import com.cappielloantonio.tempo.database.dao.QueueDao; import com.cappielloantonio.tempo.database.dao.QueueDao;
import com.cappielloantonio.tempo.database.dao.RecentSearchDao; import com.cappielloantonio.tempo.database.dao.RecentSearchDao;
import com.cappielloantonio.tempo.database.dao.ServerDao; import com.cappielloantonio.tempo.database.dao.ServerDao;
import com.cappielloantonio.tempo.model.Chronology; import com.cappielloantonio.tempo.model.Chronology;
import com.cappielloantonio.tempo.model.Download; import com.cappielloantonio.tempo.model.Download;
import com.cappielloantonio.tempo.model.Favorite;
import com.cappielloantonio.tempo.model.Queue; import com.cappielloantonio.tempo.model.Queue;
import com.cappielloantonio.tempo.model.RecentSearch; import com.cappielloantonio.tempo.model.RecentSearch;
import com.cappielloantonio.tempo.model.Server; import com.cappielloantonio.tempo.model.Server;
@Database( @Database(
version = 1, version = 3,
entities = {Queue.class, Server.class, RecentSearch.class, Download.class, Chronology.class} entities = {Queue.class, Server.class, RecentSearch.class, Download.class, Chronology.class, Favorite.class},
// autoMigrations = {@AutoMigration(from = 61, to = 62)} autoMigrations = {@AutoMigration(from = 2, to = 3)}
) )
@TypeConverters({DateConverters.class}) @TypeConverters({DateConverters.class})
public abstract class AppDatabase extends RoomDatabase { public abstract class AppDatabase extends RoomDatabase {
@@ -47,4 +50,6 @@ public abstract class AppDatabase extends RoomDatabase {
public abstract DownloadDao downloadDao(); public abstract DownloadDao downloadDao();
public abstract ChronologyDao chronologyDao(); public abstract ChronologyDao chronologyDao();
public abstract FavoriteDao favoriteDao();
} }

View File

@@ -15,6 +15,9 @@ public interface DownloadDao {
@Query("SELECT * FROM download WHERE download_state = 1 ORDER BY artist, album, track ASC") @Query("SELECT * FROM download WHERE download_state = 1 ORDER BY artist, album, track ASC")
LiveData<List<Download>> getAll(); LiveData<List<Download>> getAll();
@Query("SELECT * FROM download WHERE id = :id")
Download getOne(String id);
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
void insert(Download download); void insert(Download download);

View File

@@ -0,0 +1,26 @@
package com.cappielloantonio.tempo.database.dao;
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.OnConflictStrategy;
import androidx.room.Query;
import com.cappielloantonio.tempo.model.Favorite;
import java.util.List;
@Dao
public interface FavoriteDao {
@Query("SELECT * FROM favorite")
List<Favorite> getAll();
@Insert(onConflict = OnConflictStrategy.IGNORE)
void insert(Favorite favorite);
@Delete
void delete(Favorite favorite);
@Query("DELETE FROM favorite")
void deleteAll();
}

View File

@@ -12,7 +12,7 @@ import java.util.List;
@Dao @Dao
public interface RecentSearchDao { public interface RecentSearchDao {
@Query("SELECT * FROM recent_search ORDER BY search ASC") @Query("SELECT * FROM recent_search ORDER BY search DESC")
List<String> getRecent(); List<String> getRecent();
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)

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

@@ -0,0 +1,197 @@
package com.cappielloantonio.tempo.helper.recyclerview;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.IdRes;
import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
import androidx.core.view.ViewCompat;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
public class FastScrollbar extends LinearLayout {
private static final int BUBBLE_ANIMATION_DURATION = 100;
private static final int TRACK_SNAP_RANGE = 5;
private TextView bubble;
private View handle;
private RecyclerView recyclerView;
private int height;
private boolean isInitialized = false;
private ObjectAnimator currentAnimator = null;
private final RecyclerView.OnScrollListener onScrollListener = new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(@NonNull final RecyclerView recyclerView, final int dx, final int dy) {
updateBubbleAndHandlePosition();
}
};
public interface BubbleTextGetter {
String getTextToShowInBubble(int pos);
}
public FastScrollbar(final Context context, final AttributeSet attrs, final int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
public FastScrollbar(final Context context) {
super(context);
init(context);
}
public FastScrollbar(final Context context, final AttributeSet attrs) {
super(context, attrs);
init(context);
}
protected void init(Context context) {
if (isInitialized) return;
isInitialized = true;
setOrientation(HORIZONTAL);
setClipChildren(false);
}
public void setViewsToUse(@LayoutRes int layoutResId, @IdRes int bubbleResId, @IdRes int handleResId) {
final LayoutInflater inflater = LayoutInflater.from(getContext());
inflater.inflate(layoutResId, this, true);
bubble = findViewById(bubbleResId);
if (bubble != null) bubble.setVisibility(INVISIBLE);
handle = findViewById(handleResId);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
height = h;
updateBubbleAndHandlePosition();
}
@Override
public boolean onTouchEvent(@NonNull MotionEvent event) {
final int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
if (event.getX() < handle.getX() - ViewCompat.getPaddingStart(handle)) return false;
if (currentAnimator != null) currentAnimator.cancel();
if (bubble != null && bubble.getVisibility() == INVISIBLE) showBubble();
handle.setSelected(true);
case MotionEvent.ACTION_MOVE:
final float y = event.getY();
setBubbleAndHandlePosition(y);
setRecyclerViewPosition(y);
return true;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
handle.setSelected(false);
hideBubble();
return true;
}
return super.onTouchEvent(event);
}
public void setRecyclerView(final RecyclerView recyclerView) {
if (this.recyclerView != recyclerView) {
if (this.recyclerView != null)
this.recyclerView.removeOnScrollListener(onScrollListener);
this.recyclerView = recyclerView;
if (this.recyclerView == null) return;
recyclerView.addOnScrollListener(onScrollListener);
}
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (recyclerView != null) {
recyclerView.removeOnScrollListener(onScrollListener);
recyclerView = null;
}
}
private void setRecyclerViewPosition(float y) {
if (recyclerView != null) {
final int itemCount = recyclerView.getAdapter().getItemCount();
float proportion;
if (handle.getY() == 0) proportion = 0f;
else if (handle.getY() + handle.getHeight() >= height - TRACK_SNAP_RANGE)
proportion = 1f;
else proportion = y / (float) height;
final int targetPos = getValueInRange(0, itemCount - 1, (int) (proportion * (float) itemCount));
((LinearLayoutManager) recyclerView.getLayoutManager()).scrollToPositionWithOffset(targetPos, 0);
final String bubbleText = ((BubbleTextGetter) recyclerView.getAdapter()).getTextToShowInBubble(targetPos);
if (bubble != null) {
bubble.setText(bubbleText);
if (TextUtils.isEmpty(bubbleText)) {
hideBubble();
} else if (bubble.getVisibility() == View.INVISIBLE) {
showBubble();
}
}
}
}
private int getValueInRange(int min, int max, int value) {
int minimum = Math.max(min, value);
return Math.min(minimum, max);
}
private void updateBubbleAndHandlePosition() {
if (bubble == null || handle.isSelected()) return;
final int verticalScrollOffset = recyclerView.computeVerticalScrollOffset();
final int verticalScrollRange = recyclerView.computeVerticalScrollRange();
float proportion = (float) verticalScrollOffset / ((float) verticalScrollRange - height);
setBubbleAndHandlePosition(height * proportion);
}
private void setBubbleAndHandlePosition(float y) {
final int handleHeight = handle.getHeight();
handle.setY(getValueInRange(0, height - handleHeight, (int) (y - handleHeight / 2)));
if (bubble != null) {
int bubbleHeight = bubble.getHeight();
bubble.setY(getValueInRange(0, height - bubbleHeight - handleHeight / 2, (int) (y - bubbleHeight)));
}
}
private void showBubble() {
if (bubble == null) return;
bubble.setVisibility(VISIBLE);
if (currentAnimator != null) currentAnimator.cancel();
currentAnimator = ObjectAnimator.ofFloat(bubble, "alpha", 0f, 1f).setDuration(BUBBLE_ANIMATION_DURATION);
currentAnimator.start();
}
private void hideBubble() {
if (bubble == null) return;
if (currentAnimator != null) currentAnimator.cancel();
currentAnimator = ObjectAnimator.ofFloat(bubble, "alpha", 1f, 0f).setDuration(BUBBLE_ANIMATION_DURATION);
currentAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
bubble.setVisibility(INVISIBLE);
currentAnimator = null;
}
@Override
public void onAnimationCancel(Animator animation) {
super.onAnimationCancel(animation);
bubble.setVisibility(INVISIBLE);
currentAnimator = null;
}
});
currentAnimator.start();
}
}

View File

@@ -20,6 +20,7 @@ public interface ClickCallback {
default void onServerClick(Bundle bundle) {} default void onServerClick(Bundle bundle) {}
default void onServerLongClick(Bundle bundle) {} default void onServerLongClick(Bundle bundle) {}
default void onPodcastEpisodeClick(Bundle bundle) {} default void onPodcastEpisodeClick(Bundle bundle) {}
default void onPodcastEpisodeAltClick(Bundle bundle) {}
default void onPodcastEpisodeLongClick(Bundle bundle) {} default void onPodcastEpisodeLongClick(Bundle bundle) {}
default void onPodcastChannelClick(Bundle bundle) {} default void onPodcastChannelClick(Bundle bundle) {}
default void onPodcastChannelLongClick(Bundle bundle) {} default void onPodcastChannelLongClick(Bundle bundle) {}
@@ -28,4 +29,5 @@ 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) {}
} }

View File

@@ -0,0 +1,13 @@
package com.cappielloantonio.tempo.interfaces;
import androidx.annotation.Keep;
@Keep
public interface DialogClickCallback {
default void onPositiveClick() {}
default void onNegativeClick() {}
default void onNeutralClick() {}
}

View File

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

View File

@@ -0,0 +1,9 @@
package com.cappielloantonio.tempo.interfaces;
import androidx.annotation.Keep;
@Keep
public interface StarCallback {
default void onError() {}
default void onSuccess() {}
}

View File

@@ -20,6 +20,9 @@ class Download(@PrimaryKey override val id: String) : Child(id) {
@ColumnInfo(name = "download_state", defaultValue = "1") @ColumnInfo(name = "download_state", defaultValue = "1")
var downloadState: Int = 0 var downloadState: Int = 0
@ColumnInfo(name = "download_uri", defaultValue = "")
var downloadUri: String? = null
constructor(child: Child) : this(child.id) { constructor(child: Child) : this(child.id) {
parentId = child.parentId parentId = child.parentId
isDir = child.isDir isDir = child.isDir
@@ -52,4 +55,10 @@ class Download(@PrimaryKey override val id: String) : Child(id) {
originalWidth = child.originalWidth originalWidth = child.originalWidth
originalHeight = child.originalHeight originalHeight = child.originalHeight
} }
} }
@Keep
data class DownloadStack(
var id: String,
var view: String?
)

View File

@@ -0,0 +1,32 @@
package com.cappielloantonio.tempo.model
import android.os.Parcelable
import androidx.annotation.Keep
import androidx.annotation.Nullable
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import kotlinx.parcelize.Parcelize
@Keep
@Parcelize
@Entity(tableName = "favorite")
data class Favorite(
@PrimaryKey
@ColumnInfo(name = "timestamp")
var timestamp: Long,
@ColumnInfo(name = "songId")
val songId: String?,
@ColumnInfo(name = "albumId")
val albumId: String?,
@ColumnInfo(name = "artistId")
val artistId: String?,
@ColumnInfo(name = "toStar")
val toStar: Boolean,
) : Parcelable {
override fun toString(): String = (songId ?: "null") + (albumId ?: "null") + (artistId ?: "null")
}

View File

@@ -21,8 +21,6 @@ import retrofit2.Callback;
import retrofit2.Response; import retrofit2.Response;
public class AlbumRepository { public class AlbumRepository {
private static final String TAG = "AlbumRepository";
public MutableLiveData<List<AlbumID3>> getAlbums(String type, int size, Integer fromYear, Integer toYear) { public MutableLiveData<List<AlbumID3>> getAlbums(String type, int size, Integer fromYear, Integer toYear) {
MutableLiveData<List<AlbumID3>> listLiveAlbums = new MutableLiveData<>(new ArrayList<>()); MutableLiveData<List<AlbumID3>> listLiveAlbums = new MutableLiveData<>(new ArrayList<>());
@@ -78,40 +76,6 @@ public class AlbumRepository {
return starredAlbums; return starredAlbums;
} }
public void star(String id) {
App.getSubsonicClientInstance(false)
.getMediaAnnotationClient()
.star(null, id, null)
.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
}
@Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
}
});
}
public void unstar(String id) {
App.getSubsonicClientInstance(false)
.getMediaAnnotationClient()
.unstar(null, id, null)
.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
}
@Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
}
});
}
public void setRating(String id, int rating) { public void setRating(String id, int rating) {
App.getSubsonicClientInstance(false) App.getSubsonicClientInstance(false)
.getMediaAnnotationClient() .getMediaAnnotationClient()
@@ -141,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);
@@ -283,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) {
@@ -135,9 +139,6 @@ public class ArtistRepository {
return artist; return artist;
} }
/*
* Metodo che mi restituisce le informazioni complete dell'artista (bio, immagini prese da last.fm, artisti simili...)
*/
public MutableLiveData<ArtistInfo2> getArtistFullInfo(String id) { public MutableLiveData<ArtistInfo2> getArtistFullInfo(String id) {
MutableLiveData<ArtistInfo2> artistFullInfo = new MutableLiveData<>(null); MutableLiveData<ArtistInfo2> artistFullInfo = new MutableLiveData<>(null);
@@ -161,40 +162,6 @@ public class ArtistRepository {
return artistFullInfo; return artistFullInfo;
} }
public void star(String id) {
App.getSubsonicClientInstance(false)
.getMediaAnnotationClient()
.star(null, null, id)
.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
}
@Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
}
});
}
public void unstar(String id) {
App.getSubsonicClientInstance(false)
.getMediaAnnotationClient()
.unstar(null, null, id)
.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
}
@Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
}
});
}
public void setRating(String id, int rating) { public void setRating(String id, int rating) {
App.getSubsonicClientInstance(false) App.getSubsonicClientInstance(false)
.getMediaAnnotationClient() .getMediaAnnotationClient()

View File

@@ -4,8 +4,11 @@ import androidx.lifecycle.LiveData;
import com.cappielloantonio.tempo.database.AppDatabase; import com.cappielloantonio.tempo.database.AppDatabase;
import com.cappielloantonio.tempo.database.dao.DownloadDao; import com.cappielloantonio.tempo.database.dao.DownloadDao;
import com.cappielloantonio.tempo.database.dao.FavoriteDao;
import com.cappielloantonio.tempo.model.Download; import com.cappielloantonio.tempo.model.Download;
import com.cappielloantonio.tempo.model.Favorite;
import java.util.ArrayList;
import java.util.List; import java.util.List;
public class DownloadRepository { public class DownloadRepository {
@@ -15,6 +18,43 @@ public class DownloadRepository {
return downloadDao.getAll(); return downloadDao.getAll();
} }
public Download getDownload(String id) {
Download download = null;
GetDownloadThreadSafe getDownloadThreadSafe = new GetDownloadThreadSafe(downloadDao, id);
Thread thread = new Thread(getDownloadThreadSafe);
thread.start();
try {
thread.join();
download = getDownloadThreadSafe.getDownload();
} catch (InterruptedException e) {
e.printStackTrace();
}
return download;
}
private static class GetDownloadThreadSafe implements Runnable {
private final DownloadDao downloadDao;
private final String id;
private Download download;
public GetDownloadThreadSafe(DownloadDao downloadDao, String id) {
this.downloadDao = downloadDao;
this.id = id;
}
@Override
public void run() {
download = downloadDao.getOne(id);
}
public Download getDownload() {
return download;
}
}
public void insert(Download download) { public void insert(Download download) {
InsertThreadSafe insert = new InsertThreadSafe(downloadDao, download); InsertThreadSafe insert = new InsertThreadSafe(downloadDao, download);
Thread thread = new Thread(insert); Thread thread = new Thread(insert);

View File

@@ -0,0 +1,140 @@
package com.cappielloantonio.tempo.repository;
import androidx.annotation.NonNull;
import com.cappielloantonio.tempo.App;
import com.cappielloantonio.tempo.database.AppDatabase;
import com.cappielloantonio.tempo.database.dao.FavoriteDao;
import com.cappielloantonio.tempo.interfaces.StarCallback;
import com.cappielloantonio.tempo.model.Favorite;
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
import java.util.ArrayList;
import java.util.List;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class FavoriteRepository {
private final FavoriteDao favoriteDao = AppDatabase.getInstance().favoriteDao();
public void star(String id, String albumId, String artistId, StarCallback starCallback) {
App.getSubsonicClientInstance(false)
.getMediaAnnotationClient()
.star(id, albumId, artistId)
.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
if (response.isSuccessful()) {
starCallback.onSuccess();
} else {
starCallback.onError();
}
}
@Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
starCallback.onError();
}
});
}
public void unstar(String id, String albumId, String artistId, StarCallback starCallback) {
App.getSubsonicClientInstance(false)
.getMediaAnnotationClient()
.unstar(id, albumId, artistId)
.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
if (response.isSuccessful()) {
starCallback.onSuccess();
} else {
starCallback.onError();
}
}
@Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
starCallback.onError();
}
});
}
public List<Favorite> getFavorites() {
List<Favorite> favorites = new ArrayList<>();
GetAllThreadSafe getAllThreadSafe = new GetAllThreadSafe(favoriteDao);
Thread thread = new Thread(getAllThreadSafe);
thread.start();
try {
thread.join();
favorites = getAllThreadSafe.getFavorites();
} catch (InterruptedException e) {
e.printStackTrace();
}
return favorites;
}
private static class GetAllThreadSafe implements Runnable {
private final FavoriteDao favoriteDao;
private List<Favorite> favorites = new ArrayList<>();
public GetAllThreadSafe(FavoriteDao favoriteDao) {
this.favoriteDao = favoriteDao;
}
@Override
public void run() {
favorites = favoriteDao.getAll();
}
public List<Favorite> getFavorites() {
return favorites;
}
}
public void starLater(String id, String albumId, String artistId, boolean toStar) {
InsertThreadSafe insert = new InsertThreadSafe(favoriteDao, new Favorite(System.currentTimeMillis(), id, albumId, artistId, toStar));
Thread thread = new Thread(insert);
thread.start();
}
private static class InsertThreadSafe implements Runnable {
private final FavoriteDao favoriteDao;
private final Favorite favorite;
public InsertThreadSafe(FavoriteDao favoriteDao, Favorite favorite) {
this.favoriteDao = favoriteDao;
this.favorite = favorite;
}
@Override
public void run() {
favoriteDao.insert(favorite);
}
}
public void delete(Favorite favorite) {
DeleteThreadSafe delete = new DeleteThreadSafe(favoriteDao, favorite);
Thread thread = new Thread(delete);
thread.start();
}
private static class DeleteThreadSafe implements Runnable {
private final FavoriteDao favoriteDao;
private final Favorite favorite;
public DeleteThreadSafe(FavoriteDao favoriteDao, Favorite favorite) {
this.favoriteDao = favoriteDao;
this.favorite = favorite;
}
@Override
public void run() {
favoriteDao.delete(favorite);
}
}
}

View File

@@ -24,9 +24,14 @@ public class GenreRepository {
.enqueue(new Callback<ApiResponse>() { .enqueue(new Callback<ApiResponse>() {
@Override @Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) { public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getGenres() != null) { if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse() != null && response.body().getSubsonicResponse().getGenres() != null) {
List<Genre> genreList = response.body().getSubsonicResponse().getGenres().getGenres(); List<Genre> genreList = response.body().getSubsonicResponse().getGenres().getGenres();
if (genreList == null || genreList.isEmpty()) {
genres.setValue(Collections.emptyList());
return;
}
if (random) { if (random) {
Collections.shuffle(genreList); Collections.shuffle(genreList);
} }

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()");
} }
}); });
@@ -133,4 +133,21 @@ public class PodcastRepository {
} }
}); });
} }
public void downloadPodcastEpisode(String episodeId) {
App.getSubsonicClientInstance(false)
.getPodcastClient()
.downloadPodcastEpisode(episodeId)
.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
}
@Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
}
});
}
} }

View File

@@ -34,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
@@ -55,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
@@ -78,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());

View File

@@ -6,6 +6,8 @@ import androidx.annotation.NonNull;
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
import com.cappielloantonio.tempo.App; import com.cappielloantonio.tempo.App;
import com.cappielloantonio.tempo.database.AppDatabase;
import com.cappielloantonio.tempo.database.dao.FavoriteDao;
import com.cappielloantonio.tempo.subsonic.base.ApiResponse; import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
import com.cappielloantonio.tempo.subsonic.models.Child; import com.cappielloantonio.tempo.subsonic.models.Child;
@@ -95,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: ");
} }
}); });
@@ -119,40 +121,6 @@ public class SongRepository {
}); });
} }
public void star(String id) {
App.getSubsonicClientInstance(false)
.getMediaAnnotationClient()
.star(id, null, null)
.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
}
@Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
}
});
}
public void unstar(String id) {
App.getSubsonicClientInstance(false)
.getMediaAnnotationClient()
.unstar(id, null, null)
.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
}
@Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
}
});
}
public void setRating(String id, int rating) { public void setRating(String id, int rating) {
App.getSubsonicClientInstance(false) App.getSubsonicClientInstance(false)
.getMediaAnnotationClient() .getMediaAnnotationClient()
@@ -173,8 +141,6 @@ public class SongRepository {
public MutableLiveData<List<Child>> getSongsByGenre(String id, int page) { public MutableLiveData<List<Child>> getSongsByGenre(String id, int page) {
MutableLiveData<List<Child>> songsByGenre = new MutableLiveData<>(); MutableLiveData<List<Child>> songsByGenre = new MutableLiveData<>();
Log.d(TAG, "onScrolled PAGE: " + page);
App.getSubsonicClientInstance(false) App.getSubsonicClientInstance(false)
.getAlbumSongListClient() .getAlbumSongListClient()
.getSongsByGenre(id, 100, 100 * page) .getSongsByGenre(id, 100, 100 * page)
@@ -239,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

@@ -77,6 +77,8 @@ public class DownloaderManager {
} }
public void download(MediaItem mediaItem, com.cappielloantonio.tempo.model.Download download) { public void download(MediaItem mediaItem, com.cappielloantonio.tempo.model.Download download) {
download.setDownloadUri(mediaItem.requestMetadata.mediaUri.toString());
DownloadService.sendAddDownload(context, DownloaderService.class, buildDownloadRequest(mediaItem), false); DownloadService.sendAddDownload(context, DownloaderService.class, buildDownloadRequest(mediaItem), false);
insertDatabase(download); insertDatabase(download);
} }
@@ -89,6 +91,7 @@ public class DownloaderManager {
public void remove(MediaItem mediaItem, com.cappielloantonio.tempo.model.Download download) { public void remove(MediaItem mediaItem, com.cappielloantonio.tempo.model.Download download) {
DownloadService.sendRemoveDownload(context, DownloaderService.class, buildDownloadRequest(mediaItem).id, false); DownloadService.sendRemoveDownload(context, DownloaderService.class, buildDownloadRequest(mediaItem).id, false);
deleteDatabase(download.getId());
} }
public void remove(List<MediaItem> mediaItems, List<com.cappielloantonio.tempo.model.Download> downloads) { public void remove(List<MediaItem> mediaItems, List<com.cappielloantonio.tempo.model.Download> downloads) {
@@ -97,6 +100,12 @@ public class DownloaderManager {
} }
} }
public void removeAll() {
DownloadService.sendRemoveAllDownloads(context, DownloaderService.class, false);
deleteAllDatabase();
DownloadUtil.eraseDownloadFolder(context);
}
private void loadDownloads() { private void loadDownloads() {
try (DownloadCursor loadedDownloads = downloadIndex.getDownloads()) { try (DownloadCursor loadedDownloads = downloadIndex.getDownloads()) {
while (loadedDownloads.moveToNext()) { while (loadedDownloads.moveToNext()) {
@@ -108,6 +117,11 @@ public class DownloaderManager {
} }
} }
public static String getDownloadNotificationMessage(String id) {
com.cappielloantonio.tempo.model.Download download = getDownloadRepository().getDownload(id);
return download != null ? download.getTitle() : null;
}
private static DownloadRepository getDownloadRepository() { private static DownloadRepository getDownloadRepository() {
return new DownloadRepository(); return new DownloadRepository();
} }
@@ -120,6 +134,10 @@ public class DownloaderManager {
getDownloadRepository().delete(id); getDownloadRepository().delete(id);
} }
public static void deleteAllDatabase() {
getDownloadRepository().deleteAll();
}
public static void updateDatabase(String id) { public static void updateDatabase(String id) {
getDownloadRepository().update(id); getDownloadRepository().update(id);
} }

View File

@@ -7,7 +7,6 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.media3.common.util.NotificationUtil; import androidx.media3.common.util.NotificationUtil;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import androidx.media3.exoplayer.offline.Download; import androidx.media3.exoplayer.offline.Download;
import androidx.media3.exoplayer.offline.DownloadManager; import androidx.media3.exoplayer.offline.DownloadManager;
import androidx.media3.exoplayer.offline.DownloadNotificationHelper; import androidx.media3.exoplayer.offline.DownloadNotificationHelper;
@@ -55,12 +54,37 @@ public class DownloaderService extends androidx.media3.exoplayer.offline.Downloa
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
@@ -68,10 +92,14 @@ public class DownloaderService extends androidx.media3.exoplayer.offline.Downloa
Notification notification; Notification notification;
if (download.state == Download.STATE_COMPLETED) { if (download.state == Download.STATE_COMPLETED) {
notification = notificationHelper.buildDownloadCompletedNotification(context, R.drawable.ic_check_circle, null, Util.fromUtf8Bytes(download.request.data)); notification = notificationHelper.buildDownloadCompletedNotification(context, R.drawable.ic_check_circle, null, DownloaderManager.getDownloadNotificationMessage(download.request.id));
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, Util.fromUtf8Bytes(download.request.data)); notification = notificationHelper.buildDownloadFailedNotification(context, R.drawable.ic_error, null, DownloaderManager.getDownloadNotificationMessage(download.request.id));
notification = Notification.Builder.recoverBuilder(context, notification).setGroup(DownloadUtil.DOWNLOAD_NOTIFICATION_FAILED_GROUP).build();
NotificationUtil.setNotification(this.context, failedDownloadGroupNotificationId, failedDownloadGroupNotification);
} else { } 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

@@ -48,4 +48,9 @@ public class PodcastClient {
Log.d(TAG, "deletePodcastEpisode()"); Log.d(TAG, "deletePodcastEpisode()");
return podcastService.deletePodcastEpisode(subsonic.getParams(), episodeId); return podcastService.deletePodcastEpisode(subsonic.getParams(), episodeId);
} }
public Call<ApiResponse> downloadPodcastEpisode(String episodeId) {
Log.d(TAG, "downloadPodcastEpisode()");
return podcastService.downloadPodcastEpisode(subsonic.getParams(), episodeId);
}
} }

View File

@@ -27,4 +27,7 @@ public interface PodcastService {
@GET("deletePodcastEpisode") @GET("deletePodcastEpisode")
Call<ApiResponse> deletePodcastEpisode(@QueryMap Map<String, String> params, @Query("id") String id); Call<ApiResponse> deletePodcastEpisode(@QueryMap Map<String, String> params, @Query("id") String id);
@GET("downloadPodcastEpisode")
Call<ApiResponse> downloadPodcastEpisode(@QueryMap Map<String, String> params, @Query("id") String id);
} }

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

@@ -2,7 +2,6 @@ package com.cappielloantonio.tempo.subsonic.models
import android.os.Parcelable import android.os.Parcelable
import androidx.annotation.Keep import androidx.annotation.Keep
import androidx.room.ColumnInfo
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import java.util.* import java.util.*
@@ -29,11 +28,9 @@ class PodcastEpisode : Parcelable {
var transcodedContentType: String? = null var transcodedContentType: String? = null
var transcodedSuffix: String? = null var transcodedSuffix: String? = null
var duration: Int? = null var duration: Int? = null
@ColumnInfo("bitrate")
@SerializedName("bitRate") @SerializedName("bitRate")
var bitrate: Int? = null var bitrate: Int? = null
var path: String? = null var path: String? = null
@ColumnInfo(name = "is_video")
@SerializedName("isVideo") @SerializedName("isVideo")
var isVideo: Boolean = false var isVideo: Boolean = false
var userRating: Int? = null var userRating: Int? = null

View File

@@ -3,6 +3,7 @@ package com.cappielloantonio.tempo.ui.activity;
import android.content.Context; import android.content.Context;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.net.ConnectivityManager; import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.view.View; import android.view.View;
@@ -30,6 +31,7 @@ import com.cappielloantonio.tempo.util.Preferences;
import com.cappielloantonio.tempo.viewmodel.MainViewModel; import com.cappielloantonio.tempo.viewmodel.MainViewModel;
import com.google.android.material.bottomnavigation.BottomNavigationView; import com.google.android.material.bottomnavigation.BottomNavigationView;
import com.google.android.material.bottomsheet.BottomSheetBehavior; import com.google.android.material.bottomsheet.BottomSheetBehavior;
import com.google.android.material.color.DynamicColors;
import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.MoreExecutors;
import java.util.Objects; import java.util.Objects;
@@ -50,10 +52,10 @@ public class MainActivity extends BaseActivity {
ConnectivityStatusBroadcastReceiver connectivityStatusBroadcastReceiver; ConnectivityStatusBroadcastReceiver connectivityStatusBroadcastReceiver;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
SplashScreen.installSplashScreen(this); SplashScreen.installSplashScreen(this);
DynamicColors.applyToActivityIfAvailable(this);
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@@ -117,6 +119,8 @@ public class MainActivity extends BaseActivity {
fragmentManager.beginTransaction().replace(R.id.player_bottom_sheet, new PlayerBottomSheetFragment(), "PlayerBottomSheet").commit(); fragmentManager.beginTransaction().replace(R.id.player_bottom_sheet, new PlayerBottomSheetFragment(), "PlayerBottomSheet").commit();
setBottomSheetInPeek(mainViewModel.isQueueLoaded()); setBottomSheetInPeek(mainViewModel.isQueueLoaded());
collapseBottomSheet();
} }
public void setBottomSheetInPeek(Boolean isVisible) { public void setBottomSheetInPeek(Boolean isVisible) {
@@ -338,7 +342,9 @@ public class MainActivity extends BaseActivity {
private void checkConnectionType() { private void checkConnectionType() {
if (Preferences.isWifiOnly()) { if (Preferences.isWifiOnly()) {
ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
if (connectivityManager.getActiveNetworkInfo().getType() != ConnectivityManager.TYPE_WIFI) { NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
if (networkInfo != null && networkInfo.getType() != ConnectivityManager.TYPE_WIFI) {
ConnectionAlertDialog dialog = new ConnectionAlertDialog(); ConnectionAlertDialog dialog = new ConnectionAlertDialog();
dialog.show(getSupportFragmentManager(), null); dialog.show(getSupportFragmentManager(), null);
} }

View File

@@ -53,7 +53,7 @@ public class BaseActivity extends AppCompatActivity {
} }
private void checkBatteryOptimization() { private void checkBatteryOptimization() {
if (detectBatteryOptimization() && Boolean.TRUE.equals(Preferences.askForOptimization())) { if (detectBatteryOptimization() && Preferences.askForOptimization()) {
showBatteryOptimizationDialog(); showBatteryOptimizationDialog();
} }
} }
@@ -98,6 +98,7 @@ public class BaseActivity extends AppCompatActivity {
} }
private void setNavigationBarColor() { private void setNavigationBarColor() {
getWindow().setNavigationBarColor(SurfaceColors.getColorForElevation(this, 10)); getWindow().setNavigationBarColor(SurfaceColors.getColorForElevation(this, 8));
getWindow().setStatusBarColor(SurfaceColors.getColorForElevation(this, 0));
} }
} }

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

@@ -11,25 +11,35 @@ import androidx.recyclerview.widget.RecyclerView;
import com.cappielloantonio.tempo.R; import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.ItemHorizontalDownloadBinding; import com.cappielloantonio.tempo.databinding.ItemHorizontalDownloadBinding;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.interfaces.ClickCallback; import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.subsonic.models.Child; import com.cappielloantonio.tempo.subsonic.models.Child;
import com.cappielloantonio.tempo.util.Constants; import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil; import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.util.Util;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.stream.Collectors;
@UnstableApi @UnstableApi
public class DownloadHorizontalAdapter extends RecyclerView.Adapter<DownloadHorizontalAdapter.ViewHolder> { public class DownloadHorizontalAdapter extends RecyclerView.Adapter<DownloadHorizontalAdapter.ViewHolder> {
private final ClickCallback click; private final ClickCallback click;
private String view;
private String filterKey;
private String filterValue;
private List<Child> songs; private List<Child> songs;
private List<Child> grouped;
public DownloadHorizontalAdapter(ClickCallback click) { public DownloadHorizontalAdapter(ClickCallback click) {
this.click = click; this.click = click;
this.view = Constants.DOWNLOAD_TYPE_TRACK;
this.songs = Collections.emptyList(); this.songs = Collections.emptyList();
this.grouped = Collections.emptyList();
} }
@NonNull @NonNull
@@ -41,31 +51,43 @@ public class DownloadHorizontalAdapter extends RecyclerView.Adapter<DownloadHori
@Override @Override
public void onBindViewHolder(ViewHolder holder, int position) { public void onBindViewHolder(ViewHolder holder, int position) {
Child song = songs.get(position); switch (view) {
case Constants.DOWNLOAD_TYPE_TRACK:
holder.item.downloadedSongTitleTextView.setText(MusicUtil.getReadableString(song.getTitle())); initTrackLayout(holder, position);
holder.item.downloadedSongArtistTextView.setText(holder.itemView.getContext().getString(R.string.song_subtitle_formatter, MusicUtil.getReadableString(song.getArtist()), MusicUtil.getReadableDurationString(song.getDuration(), false))); break;
holder.item.downloadedSongAlbumTextView.setText(MusicUtil.getReadableString(song.getAlbum())); case Constants.DOWNLOAD_TYPE_ALBUM:
initAlbumLayout(holder, position);
if (position > 0 && songs.get(position - 1) != null && !Objects.equals(songs.get(position - 1).getAlbum(), songs.get(position).getAlbum())) { break;
holder.item.divider.setPadding(0, (int) holder.itemView.getContext().getResources().getDimension(R.dimen.downloaded_item_padding), 0, 0); case Constants.DOWNLOAD_TYPE_ARTIST:
} else { initArtistLayout(holder, position);
if (position > 0) holder.item.divider.setVisibility(View.GONE); break;
case Constants.DOWNLOAD_TYPE_GENRE:
initGenreLayout(holder, position);
break;
case Constants.DOWNLOAD_TYPE_YEAR:
initYearLayout(holder, position);
break;
} }
} }
@Override @Override
public int getItemCount() { public int getItemCount() {
return songs.size(); return grouped.size();
} }
public void setItems(List<Child> songs) { public void setItems(String view, String filterKey, String filterValue, List<Child> songs) {
this.view = filterValue != null ? view : filterKey;
this.filterKey = filterKey;
this.filterValue = filterValue;
this.songs = songs; this.songs = songs;
this.grouped = groupSong(songs);
notifyDataSetChanged(); notifyDataSetChanged();
} }
public Child getItem(int id) { public Child getItem(int id) {
return songs.get(id); return grouped.get(id);
} }
@Override @Override
@@ -78,6 +100,145 @@ public class DownloadHorizontalAdapter extends RecyclerView.Adapter<DownloadHori
return position; return position;
} }
private List<Child> groupSong(List<Child> songs) {
switch (view) {
case Constants.DOWNLOAD_TYPE_TRACK:
return filterSong(filterKey, filterValue, songs.stream().filter(song -> Objects.nonNull(song.getId())).filter(Util.distinctByKey(Child::getId)).collect(Collectors.toList()));
case Constants.DOWNLOAD_TYPE_ALBUM:
return filterSong(filterKey, filterValue, songs.stream().filter(song -> Objects.nonNull(song.getAlbumId())).filter(Util.distinctByKey(Child::getAlbumId)).collect(Collectors.toList()));
case Constants.DOWNLOAD_TYPE_ARTIST:
return filterSong(filterKey, filterValue, songs.stream().filter(song -> Objects.nonNull(song.getArtistId())).filter(Util.distinctByKey(Child::getArtistId)).collect(Collectors.toList()));
case Constants.DOWNLOAD_TYPE_GENRE:
return filterSong(filterKey, filterValue, songs.stream().filter(song -> Objects.nonNull(song.getGenre())).filter(Util.distinctByKey(Child::getGenre)).collect(Collectors.toList()));
case Constants.DOWNLOAD_TYPE_YEAR:
return filterSong(filterKey, filterValue, songs.stream().filter(song -> Objects.nonNull(song.getYear())).filter(Util.distinctByKey(Child::getYear)).collect(Collectors.toList()));
}
return Collections.emptyList();
}
private List<Child> filterSong(String filterKey, String filterValue, List<Child> songs) {
if (filterValue != null) {
switch (filterKey) {
case Constants.DOWNLOAD_TYPE_TRACK:
return songs.stream().filter(child -> child.getId().equals(filterValue)).collect(Collectors.toList());
case Constants.DOWNLOAD_TYPE_ALBUM:
return songs.stream().filter(child -> Objects.equals(child.getAlbumId(), filterValue)).collect(Collectors.toList());
case Constants.DOWNLOAD_TYPE_GENRE:
return songs.stream().filter(child -> Objects.equals(child.getGenre(), filterValue)).collect(Collectors.toList());
case Constants.DOWNLOAD_TYPE_YEAR:
return songs.stream().filter(child -> Objects.equals(child.getYear(), Integer.valueOf(filterValue))).collect(Collectors.toList());
case Constants.DOWNLOAD_TYPE_ARTIST:
return songs.stream().filter(child -> Objects.equals(child.getArtistId(), filterValue)).collect(Collectors.toList());
}
}
return songs;
}
private String countSong(String filterKey, String filterValue, List<Child> songs) {
if (filterValue != null) {
switch (filterKey) {
case Constants.DOWNLOAD_TYPE_TRACK:
return String.valueOf(songs.stream().filter(child -> child.getId().equals(filterValue)).count());
case Constants.DOWNLOAD_TYPE_ALBUM:
return String.valueOf(songs.stream().filter(child -> Objects.equals(child.getAlbumId(), filterValue)).count());
case Constants.DOWNLOAD_TYPE_GENRE:
return String.valueOf(songs.stream().filter(child -> Objects.equals(child.getGenre(), filterValue)).count());
case Constants.DOWNLOAD_TYPE_YEAR:
return String.valueOf(songs.stream().filter(child -> Objects.equals(child.getYear(), Integer.valueOf(filterValue))).count());
case Constants.DOWNLOAD_TYPE_ARTIST:
return String.valueOf(songs.stream().filter(child -> Objects.equals(child.getArtistId(), filterValue)).count());
}
}
return "0";
}
private void initTrackLayout(ViewHolder holder, int position) {
Child song = grouped.get(position);
holder.item.downloadedItemTitleTextView.setText(MusicUtil.getReadableString(song.getTitle()));
holder.item.downloadedItemSubtitleTextView.setText(holder.itemView.getContext().getString(R.string.song_subtitle_formatter, MusicUtil.getReadableString(song.getArtist()), MusicUtil.getReadableDurationString(song.getDuration(), false)));
holder.item.downloadedItemPreTextView.setText(MusicUtil.getReadableString(song.getAlbum()));
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), song.getCoverArtId(), CustomGlideRequest.ResourceType.Song)
.build()
.into(holder.item.itemCoverImageView);
holder.item.itemCoverImageView.setVisibility(View.VISIBLE);
holder.item.downloadedItemMoreButton.setVisibility(View.VISIBLE);
holder.item.divider.setVisibility(View.VISIBLE);
if (position > 0 && grouped.get(position - 1) != null && !Objects.equals(grouped.get(position - 1).getAlbum(), grouped.get(position).getAlbum())) {
holder.item.divider.setPadding(0, (int) holder.itemView.getContext().getResources().getDimension(R.dimen.downloaded_item_padding), 0, 0);
} else {
if (position > 0) holder.item.divider.setVisibility(View.GONE);
}
}
private void initAlbumLayout(ViewHolder holder, int position) {
Child song = grouped.get(position);
holder.item.downloadedItemTitleTextView.setText(MusicUtil.getReadableString(song.getAlbum()));
holder.item.downloadedItemSubtitleTextView.setText(holder.itemView.getContext().getString(R.string.download_item_single_subtitle_formatter, countSong(Constants.DOWNLOAD_TYPE_ALBUM, song.getAlbumId(), songs)));
holder.item.downloadedItemPreTextView.setText(MusicUtil.getReadableString(song.getArtist()));
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), song.getCoverArtId(), CustomGlideRequest.ResourceType.Song)
.build()
.into(holder.item.itemCoverImageView);
holder.item.itemCoverImageView.setVisibility(View.VISIBLE);
holder.item.downloadedItemMoreButton.setVisibility(View.VISIBLE);
holder.item.divider.setVisibility(View.VISIBLE);
if (position > 0 && grouped.get(position - 1) != null && !Objects.equals(grouped.get(position - 1).getArtist(), grouped.get(position).getArtist())) {
holder.item.divider.setPadding(0, (int) holder.itemView.getContext().getResources().getDimension(R.dimen.downloaded_item_padding), 0, 0);
} else {
if (position > 0) holder.item.divider.setVisibility(View.GONE);
}
}
private void initArtistLayout(ViewHolder holder, int position) {
Child song = grouped.get(position);
holder.item.downloadedItemTitleTextView.setText(MusicUtil.getReadableString(song.getArtist()));
holder.item.downloadedItemSubtitleTextView.setText(holder.itemView.getContext().getString(R.string.download_item_single_subtitle_formatter, countSong(Constants.DOWNLOAD_TYPE_ARTIST, song.getArtistId(), songs)));
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), song.getCoverArtId(), CustomGlideRequest.ResourceType.Song)
.build()
.into(holder.item.itemCoverImageView);
holder.item.itemCoverImageView.setVisibility(View.VISIBLE);
holder.item.downloadedItemMoreButton.setVisibility(View.VISIBLE);
holder.item.divider.setVisibility(View.GONE);
}
private void initGenreLayout(ViewHolder holder, int position) {
Child song = grouped.get(position);
holder.item.downloadedItemTitleTextView.setText(MusicUtil.getReadableString(song.getGenre()));
holder.item.downloadedItemSubtitleTextView.setText(holder.itemView.getContext().getString(R.string.download_item_single_subtitle_formatter, countSong(Constants.DOWNLOAD_TYPE_GENRE, song.getGenre(), songs)));
holder.item.itemCoverImageView.setVisibility(View.GONE);
holder.item.downloadedItemMoreButton.setVisibility(View.VISIBLE);
holder.item.divider.setVisibility(View.GONE);
}
private void initYearLayout(ViewHolder holder, int position) {
Child song = grouped.get(position);
holder.item.downloadedItemTitleTextView.setText(String.valueOf(song.getYear()));
holder.item.downloadedItemSubtitleTextView.setText(holder.itemView.getContext().getString(R.string.download_item_single_subtitle_formatter, countSong(Constants.DOWNLOAD_TYPE_YEAR, song.getYear().toString(), songs)));
holder.item.itemCoverImageView.setVisibility(View.GONE);
holder.item.downloadedItemMoreButton.setVisibility(View.VISIBLE);
holder.item.divider.setVisibility(View.GONE);
}
public class ViewHolder extends RecyclerView.ViewHolder { public class ViewHolder extends RecyclerView.ViewHolder {
ItemHorizontalDownloadBinding item; ItemHorizontalDownloadBinding item;
@@ -86,28 +247,72 @@ public class DownloadHorizontalAdapter extends RecyclerView.Adapter<DownloadHori
this.item = item; this.item = item;
item.downloadedSongTitleTextView.setSelected(true); item.downloadedItemTitleTextView.setSelected(true);
item.downloadedSongArtistTextView.setSelected(true); item.downloadedItemSubtitleTextView.setSelected(true);
itemView.setOnClickListener(v -> onClick()); itemView.setOnClickListener(v -> onClick());
itemView.setOnLongClickListener(v -> onLongClick()); itemView.setOnLongClickListener(v -> onLongClick());
item.downloadedSongMoreButton.setOnClickListener(v -> onLongClick()); item.downloadedItemMoreButton.setOnClickListener(v -> onLongClick());
} }
public void onClick() { public void onClick() {
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
bundle.putParcelableArrayList(Constants.TRACKS_OBJECT, new ArrayList<>(songs));
bundle.putInt(Constants.ITEM_POSITION, getBindingAdapterPosition());
click.onMediaClick(bundle); switch (view) {
case Constants.DOWNLOAD_TYPE_TRACK:
bundle.putParcelableArrayList(Constants.TRACKS_OBJECT, new ArrayList<>(grouped));
bundle.putInt(Constants.ITEM_POSITION, getBindingAdapterPosition());
click.onMediaClick(bundle);
break;
case Constants.DOWNLOAD_TYPE_ALBUM:
bundle.putString(Constants.DOWNLOAD_TYPE_ALBUM, grouped.get(getBindingAdapterPosition()).getAlbumId());
click.onAlbumClick(bundle);
break;
case Constants.DOWNLOAD_TYPE_ARTIST:
bundle.putString(Constants.DOWNLOAD_TYPE_ARTIST, grouped.get(getBindingAdapterPosition()).getArtistId());
click.onArtistClick(bundle);
break;
case Constants.DOWNLOAD_TYPE_GENRE:
bundle.putString(Constants.DOWNLOAD_TYPE_GENRE, grouped.get(getBindingAdapterPosition()).getGenre());
click.onGenreClick(bundle);
break;
case Constants.DOWNLOAD_TYPE_YEAR:
bundle.putString(Constants.DOWNLOAD_TYPE_YEAR, grouped.get(getBindingAdapterPosition()).getYear().toString());
click.onYearClick(bundle);
break;
}
} }
private boolean onLongClick() { private boolean onLongClick() {
Bundle bundle = new Bundle(); ArrayList<Child> filteredSongs = new ArrayList<>();
bundle.putParcelable(Constants.TRACK_OBJECT, songs.get(getBindingAdapterPosition()));
click.onMediaLongClick(bundle); Bundle bundle = new Bundle();
switch (view) {
case Constants.DOWNLOAD_TYPE_TRACK:
filteredSongs.add(grouped.get(getBindingAdapterPosition()));
break;
case Constants.DOWNLOAD_TYPE_ALBUM:
filteredSongs.addAll(filterSong(Constants.DOWNLOAD_TYPE_ALBUM, grouped.get(getBindingAdapterPosition()).getAlbumId(), songs));
break;
case Constants.DOWNLOAD_TYPE_ARTIST:
filteredSongs.addAll(filterSong(Constants.DOWNLOAD_TYPE_ARTIST, grouped.get(getBindingAdapterPosition()).getArtistId(), songs));
break;
case Constants.DOWNLOAD_TYPE_GENRE:
filteredSongs.addAll(filterSong(Constants.DOWNLOAD_TYPE_GENRE, grouped.get(getBindingAdapterPosition()).getGenre(), songs));
break;
case Constants.DOWNLOAD_TYPE_YEAR:
filteredSongs.addAll(filterSong(Constants.DOWNLOAD_TYPE_YEAR, grouped.get(getBindingAdapterPosition()).getYear().toString(), songs));
break;
}
if (filteredSongs.isEmpty()) return false;
bundle.putParcelableArrayList(Constants.DOWNLOAD_GROUP, new ArrayList<>(filteredSongs));
bundle.putString(Constants.DOWNLOAD_GROUP_TITLE, item.downloadedItemTitleTextView.getText().toString());
bundle.putString(Constants.DOWNLOAD_GROUP_SUBTITLE, item.downloadedItemSubtitleTextView.getText().toString());
click.onDownloadGroupLongClick(bundle);
return true; 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

@@ -2,6 +2,7 @@ package com.cappielloantonio.tempo.ui.adapter;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@@ -42,10 +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.INVISIBLE);
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

@@ -10,15 +10,17 @@ 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.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.helper.recyclerview.FastScrollbar;
import com.cappielloantonio.tempo.interfaces.ClickCallback; import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.subsonic.models.Artist; import com.cappielloantonio.tempo.subsonic.models.Artist;
import com.cappielloantonio.tempo.util.Constants; import com.cappielloantonio.tempo.util.Constants;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects;
@UnstableApi @UnstableApi
public class MusicIndexAdapter extends RecyclerView.Adapter<MusicIndexAdapter.ViewHolder> { public class MusicIndexAdapter extends RecyclerView.Adapter<MusicIndexAdapter.ViewHolder> implements FastScrollbar.BubbleTextGetter {
private final ClickCallback click; private final ClickCallback click;
private List<Artist> artists; private List<Artist> artists;
@@ -42,7 +44,7 @@ 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);
} }
@@ -57,6 +59,11 @@ public class MusicIndexAdapter extends RecyclerView.Adapter<MusicIndexAdapter.Vi
notifyDataSetChanged(); notifyDataSetChanged();
} }
@Override
public String getTextToShowInBubble(int pos) {
return artists != null && !artists.isEmpty() ? Character.toString(Objects.requireNonNull(artists.get(pos).getName().toUpperCase()).charAt(0)) : null;
}
public class ViewHolder extends RecyclerView.ViewHolder { public class ViewHolder extends RecyclerView.ViewHolder {
ItemLibraryMusicIndexBinding item; ItemLibraryMusicIndexBinding item;

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);
} }
@@ -128,14 +128,14 @@ public class PodcastChannelCatalogueAdapter extends RecyclerView.Adapter<Podcast
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
bundle.putParcelable(Constants.PODCAST_CHANNEL_OBJECT, podcastChannels.get(getBindingAdapterPosition())); bundle.putParcelable(Constants.PODCAST_CHANNEL_OBJECT, podcastChannels.get(getBindingAdapterPosition()));
click.onAlbumClick(bundle); click.onPodcastChannelClick(bundle);
} }
private boolean onLongClick() { private boolean onLongClick() {
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
bundle.putParcelable(Constants.PODCAST_CHANNEL_OBJECT, podcastChannels.get(getBindingAdapterPosition())); bundle.putParcelable(Constants.PODCAST_CHANNEL_OBJECT, podcastChannels.get(getBindingAdapterPosition()));
click.onAlbumLongClick(bundle); click.onPodcastChannelLongClick(bundle);
return true; return true;
} }

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

@@ -2,6 +2,7 @@ package com.cappielloantonio.tempo.ui.adapter;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@@ -18,11 +19,14 @@ import com.cappielloantonio.tempo.util.MusicUtil;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
public class PodcastEpisodeAdapter extends RecyclerView.Adapter<PodcastEpisodeAdapter.ViewHolder> { public class PodcastEpisodeAdapter extends RecyclerView.Adapter<PodcastEpisodeAdapter.ViewHolder> {
private final ClickCallback click; private final ClickCallback click;
private List<PodcastEpisode> podcastEpisodes; private List<PodcastEpisode> podcastEpisodes;
private List<PodcastEpisode> podcastEpisodesFull;
public PodcastEpisodeAdapter(ClickCallback click) { public PodcastEpisodeAdapter(ClickCallback click) {
this.click = click; this.click = click;
@@ -47,9 +51,13 @@ 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);
holder.item.podcastPlayButton.setEnabled(podcastEpisode.getStatus().equals("completed"));
holder.item.podcastMoreButton.setVisibility(podcastEpisode.getStatus().equals("completed") ? View.VISIBLE : View.GONE);
holder.item.podcastDownloadRequestButton.setVisibility(podcastEpisode.getStatus().equals("completed") ? View.GONE : View.VISIBLE);
} }
@Override @Override
@@ -58,7 +66,8 @@ public class PodcastEpisodeAdapter extends RecyclerView.Adapter<PodcastEpisodeAd
} }
public void setItems(List<PodcastEpisode> podcastEpisodes) { public void setItems(List<PodcastEpisode> podcastEpisodes) {
this.podcastEpisodes = podcastEpisodes; this.podcastEpisodesFull = podcastEpisodes;
this.podcastEpisodes = podcastEpisodesFull.stream().filter(podcastEpisode -> Objects.equals(podcastEpisode.getStatus(), "completed")).collect(Collectors.toList());
notifyDataSetChanged(); notifyDataSetChanged();
} }
@@ -85,22 +94,57 @@ public class PodcastEpisodeAdapter extends RecyclerView.Adapter<PodcastEpisodeAd
item.podcastPlayButton.setOnClickListener(v -> onClick()); item.podcastPlayButton.setOnClickListener(v -> onClick());
item.podcastMoreButton.setOnClickListener(v -> openMore()); item.podcastMoreButton.setOnClickListener(v -> openMore());
item.podcastDownloadRequestButton.setOnClickListener(v -> requestDownload());
} }
public void onClick() { public void onClick() {
Bundle bundle = new Bundle(); PodcastEpisode podcastEpisode = podcastEpisodes.get(getBindingAdapterPosition());
bundle.putParcelable(Constants.PODCAST_OBJECT, podcastEpisodes.get(getBindingAdapterPosition()));
click.onPodcastEpisodeClick(bundle); if (podcastEpisode.getStatus().equals("completed")) {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.PODCAST_OBJECT, podcastEpisodes.get(getBindingAdapterPosition()));
click.onPodcastEpisodeClick(bundle);
}
} }
private boolean openMore() { private boolean openMore() {
Bundle bundle = new Bundle(); PodcastEpisode podcastEpisode = podcastEpisodes.get(getBindingAdapterPosition());
bundle.putParcelable(Constants.PODCAST_OBJECT, podcastEpisodes.get(getBindingAdapterPosition()));
click.onPodcastEpisodeLongClick(bundle); if (podcastEpisode.getStatus().equals("completed")) {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.PODCAST_OBJECT, podcastEpisodes.get(getBindingAdapterPosition()));
return true; click.onPodcastEpisodeLongClick(bundle);
return true;
}
return false;
}
public void requestDownload() {
PodcastEpisode podcastEpisode = podcastEpisodes.get(getBindingAdapterPosition());
if (!podcastEpisode.getStatus().equals("completed")) {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.PODCAST_OBJECT, podcastEpisodes.get(getBindingAdapterPosition()));
click.onPodcastEpisodeAltClick(bundle);
}
} }
} }
public void sort(String order) {
switch (order) {
case Constants.PODCAST_FILTER_BY_DOWNLOAD:
podcastEpisodes = podcastEpisodesFull.stream().filter(podcastEpisode -> Objects.equals(podcastEpisode.getStatus(), "completed")).collect(Collectors.toList());
break;
case Constants.PODCAST_FILTER_BY_ALL:
podcastEpisodes = podcastEpisodesFull;
break;
}
notifyDataSetChanged();
}
} }

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,8 +49,8 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAd
Child song = songs.get(position); Child song = songs.get(position);
holder.item.searchResultSongTitleTextView.setText(MusicUtil.getReadableString(song.getTitle())); holder.item.searchResultSongTitleTextView.setText(MusicUtil.getReadableString(song.getTitle()));
holder.item.searchResultSongSubtitleTextView.setText(holder.itemView.getContext().getString(R.string.song_subtitle_formatter, MusicUtil.getReadableString(song.getArtist()), MusicUtil.getReadableDurationString(song.getDuration() != null ? song.getDuration() : 0, false))); holder.item.searchResultSongSubtitleTextView.setText(holder.itemView.getContext().getString(R.string.song_subtitle_formatter, MusicUtil.getReadableString(this.showAlbum ? song.getAlbum() : song.getArtist()), MusicUtil.getReadableDurationString(song.getDuration() != null ? song.getDuration() : 0, false)));
holder.item.trackNumberTextView.setText(String.valueOf(song.getTrack())); holder.item.trackNumberTextView.setText(MusicUtil.getReadableTrackNumber(holder.itemView.getContext(), song.getTrack()));
if (DownloadUtil.getDownloadTracker(holder.itemView.getContext()).isDownloaded(song.getId())) { if (DownloadUtil.getDownloadTracker(holder.itemView.getContext()).isDownloaded(song.getId())) {
holder.item.searchResultDowanloadIndicatorImageView.setVisibility(View.VISIBLE); holder.item.searchResultDowanloadIndicatorImageView.setVisibility(View.VISIBLE);
@@ -56,16 +58,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);
} }
} }
@@ -76,10 +77,20 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAd
} }
public void setItems(List<Child> songs) { public void setItems(List<Child> songs) {
this.songs = songs; this.songs = songs != null ? songs : Collections.emptyList();
notifyDataSetChanged(); notifyDataSetChanged();
} }
@Override
public int getItemViewType(int position) {
return position;
}
@Override
public long getItemId(int position) {
return position;
}
public Child getItem(int id) { public Child getItem(int id) {
return songs.get(id); return songs.get(id);
} }
@@ -104,7 +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

@@ -0,0 +1,65 @@
package com.cappielloantonio.tempo.ui.dialog;
import android.app.AlertDialog;
import android.app.Dialog;
import android.os.Bundle;
import android.widget.Button;
import androidx.annotation.NonNull;
import androidx.annotation.OptIn;
import androidx.fragment.app.DialogFragment;
import androidx.media3.common.util.UnstableApi;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.DialogDeleteDownloadStorageBinding;
import com.cappielloantonio.tempo.interfaces.DialogClickCallback;
import com.cappielloantonio.tempo.util.DownloadUtil;
@OptIn(markerClass = UnstableApi.class)
public class DeleteDownloadStorageDialog extends DialogFragment {
private DialogDeleteDownloadStorageBinding bind;
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
bind = DialogDeleteDownloadStorageBinding.inflate(getLayoutInflater());
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setView(bind.getRoot())
.setTitle(R.string.delete_download_storage_dialog_title)
.setPositiveButton(R.string.delete_download_storage_dialog_positive_button, null)
.setNegativeButton(R.string.delete_download_storage_dialog_negative_button, null);
return builder.create();
}
@Override
public void onResume() {
super.onResume();
setButtonAction();
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void setButtonAction() {
AlertDialog dialog = ((AlertDialog) getDialog());
if (dialog != null) {
Button positiveButton = dialog.getButton(Dialog.BUTTON_POSITIVE);
positiveButton.setOnClickListener(v -> {
DownloadUtil.getDownloadTracker(requireContext()).removeAll();
dialog.dismiss();
});
Button negativeButton = dialog.getButton(Dialog.BUTTON_NEGATIVE);
negativeButton.setOnClickListener(v -> {
dialog.dismiss();
});
}
}
}

View File

@@ -0,0 +1,89 @@
package com.cappielloantonio.tempo.ui.dialog;
import android.app.AlertDialog;
import android.app.Dialog;
import android.os.Bundle;
import android.widget.Button;
import androidx.annotation.NonNull;
import androidx.annotation.OptIn;
import androidx.fragment.app.DialogFragment;
import androidx.media3.common.util.UnstableApi;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.DialogDownloadStorageBinding;
import com.cappielloantonio.tempo.interfaces.DialogClickCallback;
import com.cappielloantonio.tempo.util.DownloadUtil;
import com.cappielloantonio.tempo.util.Preferences;
@OptIn(markerClass = UnstableApi.class)
public class DownloadStorageDialog extends DialogFragment {
private DialogDownloadStorageBinding bind;
private final DialogClickCallback dialogClickCallback;
public DownloadStorageDialog(DialogClickCallback dialogClickCallback) {
this.dialogClickCallback = dialogClickCallback;
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
bind = DialogDownloadStorageBinding.inflate(getLayoutInflater());
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setView(bind.getRoot())
.setTitle(R.string.download_storage_dialog_title)
.setPositiveButton(R.string.download_storage_external_dialog_positive_button, null)
.setNegativeButton(R.string.download_storage_internal_dialog_negative_button, null);
return builder.create();
}
@Override
public void onResume() {
super.onResume();
setButtonAction();
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void setButtonAction() {
AlertDialog dialog = ((AlertDialog) getDialog());
if (dialog != null) {
Button positiveButton = dialog.getButton(Dialog.BUTTON_POSITIVE);
positiveButton.setOnClickListener(v -> {
int currentPreference = Preferences.getDownloadStoragePreference();
int newPreference = 1;
if (currentPreference != newPreference) {
Preferences.setDownloadStoragePreference(newPreference);
DownloadUtil.getDownloadTracker(requireContext()).removeAll();
dialogClickCallback.onPositiveClick();
}
dialog.dismiss();
});
Button negativeButton = dialog.getButton(Dialog.BUTTON_NEGATIVE);
negativeButton.setOnClickListener(v -> {
int currentPreference = Preferences.getDownloadStoragePreference();
int newPreference = 0;
if (currentPreference != newPreference) {
Preferences.setDownloadStoragePreference(newPreference);
DownloadUtil.getDownloadTracker(requireContext()).removeAll();
dialogClickCallback.onNegativeClick();
}
dialog.dismiss();
});
}
}
}

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

@@ -14,6 +14,7 @@ import androidx.recyclerview.widget.RecyclerView;
import com.cappielloantonio.tempo.R; import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.DialogPlaylistEditorBinding; import com.cappielloantonio.tempo.databinding.DialogPlaylistEditorBinding;
import com.cappielloantonio.tempo.interfaces.PlaylistCallback;
import com.cappielloantonio.tempo.ui.adapter.PlaylistDialogSongHorizontalAdapter; import com.cappielloantonio.tempo.ui.adapter.PlaylistDialogSongHorizontalAdapter;
import com.cappielloantonio.tempo.util.Constants; import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil; import com.cappielloantonio.tempo.util.MusicUtil;
@@ -25,10 +26,15 @@ import java.util.Objects;
public class PlaylistEditorDialog extends DialogFragment { public class PlaylistEditorDialog extends DialogFragment {
private DialogPlaylistEditorBinding bind; private DialogPlaylistEditorBinding bind;
private PlaylistEditorViewModel playlistEditorViewModel; private PlaylistEditorViewModel playlistEditorViewModel;
private PlaylistCallback playlistCallback;
private String playlistName; private String playlistName;
private PlaylistDialogSongHorizontalAdapter playlistDialogSongHorizontalAdapter; private PlaylistDialogSongHorizontalAdapter playlistDialogSongHorizontalAdapter;
public PlaylistEditorDialog(PlaylistCallback playlistCallback) {
this.playlistCallback = playlistCallback;
}
@NonNull @NonNull
@Override @Override
public Dialog onCreateDialog(Bundle savedInstanceState) { public Dialog onCreateDialog(Bundle savedInstanceState) {
@@ -85,13 +91,13 @@ public class PlaylistEditorDialog extends DialogFragment {
playlistEditorViewModel.updatePlaylist(playlistName); playlistEditorViewModel.updatePlaylist(playlistName);
} }
Objects.requireNonNull(getDialog()).dismiss(); dialogDismiss();
} }
}); });
((AlertDialog) Objects.requireNonNull(getDialog())).getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener(v -> { ((AlertDialog) Objects.requireNonNull(getDialog())).getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener(v -> {
playlistEditorViewModel.deletePlaylist(); playlistEditorViewModel.deletePlaylist();
Objects.requireNonNull(getDialog()).dismiss(); dialogDismiss();
}); });
} }
@@ -102,7 +108,9 @@ public class PlaylistEditorDialog extends DialogFragment {
playlistDialogSongHorizontalAdapter = new PlaylistDialogSongHorizontalAdapter(); playlistDialogSongHorizontalAdapter = new PlaylistDialogSongHorizontalAdapter();
bind.playlistSongRecyclerView.setAdapter(playlistDialogSongHorizontalAdapter); bind.playlistSongRecyclerView.setAdapter(playlistDialogSongHorizontalAdapter);
playlistEditorViewModel.getPlaylistSongLiveList().observe(requireActivity(), songs -> playlistDialogSongHorizontalAdapter.setItems(songs)); playlistEditorViewModel.getPlaylistSongLiveList().observe(requireActivity(), songs -> {
if (songs != null) playlistDialogSongHorizontalAdapter.setItems(songs);
});
new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.LEFT) { new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.LEFT) {
int originalPosition = -1; int originalPosition = -1;
@@ -157,4 +165,9 @@ public class PlaylistEditorDialog extends DialogFragment {
return true; return true;
} }
private void dialogDismiss() {
Objects.requireNonNull(getDialog()).dismiss();
playlistCallback.onDismiss();
}
} }

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

@@ -3,10 +3,14 @@ package com.cappielloantonio.tempo.ui.fragment;
import android.content.ComponentName; import android.content.ComponentName;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.view.ViewCompat; import androidx.core.view.ViewCompat;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
@@ -19,14 +23,21 @@ import androidx.recyclerview.widget.LinearLayoutManager;
import com.cappielloantonio.tempo.R; import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentDirectoryBinding; import com.cappielloantonio.tempo.databinding.FragmentDirectoryBinding;
import com.cappielloantonio.tempo.interfaces.ClickCallback; import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.model.Download;
import com.cappielloantonio.tempo.service.MediaManager; import com.cappielloantonio.tempo.service.MediaManager;
import com.cappielloantonio.tempo.service.MediaService; import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.subsonic.models.Child;
import com.cappielloantonio.tempo.ui.activity.MainActivity; import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.MusicDirectoryAdapter; import com.cappielloantonio.tempo.ui.adapter.MusicDirectoryAdapter;
import com.cappielloantonio.tempo.util.Constants; import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.DownloadUtil;
import com.cappielloantonio.tempo.util.MappingUtil;
import com.cappielloantonio.tempo.viewmodel.DirectoryViewModel; import com.cappielloantonio.tempo.viewmodel.DirectoryViewModel;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import java.util.List;
import java.util.stream.Collectors;
@UnstableApi @UnstableApi
public class DirectoryFragment extends Fragment implements ClickCallback { public class DirectoryFragment extends Fragment implements ClickCallback {
private static final String TAG = "DirectoryFragment"; private static final String TAG = "DirectoryFragment";
@@ -39,6 +50,18 @@ public class DirectoryFragment extends Fragment implements ClickCallback {
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture; private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.directory_page_menu, menu);
}
@Override @Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
activity = (MainActivity) getActivity(); activity = (MainActivity) getActivity();
@@ -71,6 +94,24 @@ public class DirectoryFragment extends Fragment implements ClickCallback {
bind = null; bind = null;
} }
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == R.id.action_download_directory) {
directoryViewModel.loadMusicDirectory(getArguments().getString(Constants.MUSIC_DIRECTORY_ID)).observe(getViewLifecycleOwner(), directory -> {
if (isVisible() && getActivity() != null) {
List<Child> songs = directory.getChildren().stream().filter(child -> !child.isDir()).collect(Collectors.toList());
DownloadUtil.getDownloadTracker(requireContext()).download(
MappingUtil.mapDownloads(songs),
songs.stream().map(Download::new).collect(Collectors.toList())
);
}
});
return true;
}
return false;
}
private void initAppBar() { private void initAppBar() {
activity.setSupportActionBar(bind.toolbar); activity.setSupportActionBar(bind.toolbar);

View File

@@ -5,7 +5,9 @@ import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.PopupMenu;
import androidx.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;
@@ -19,19 +21,25 @@ import androidx.recyclerview.widget.LinearLayoutManager;
import com.cappielloantonio.tempo.R; import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentDownloadBinding; import com.cappielloantonio.tempo.databinding.FragmentDownloadBinding;
import com.cappielloantonio.tempo.interfaces.ClickCallback; import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.model.DownloadStack;
import com.cappielloantonio.tempo.service.MediaManager; import com.cappielloantonio.tempo.service.MediaManager;
import com.cappielloantonio.tempo.service.MediaService; import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.subsonic.models.Child;
import com.cappielloantonio.tempo.ui.activity.MainActivity; import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.DownloadHorizontalAdapter; import com.cappielloantonio.tempo.ui.adapter.DownloadHorizontalAdapter;
import com.cappielloantonio.tempo.util.Constants; import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.Preferences;
import com.cappielloantonio.tempo.viewmodel.DownloadViewModel; import com.cappielloantonio.tempo.viewmodel.DownloadViewModel;
import com.google.android.material.appbar.MaterialToolbar; import com.google.android.material.appbar.MaterialToolbar;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import java.util.List;
import java.util.Objects; import java.util.Objects;
@UnstableApi @UnstableApi
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;
@@ -59,7 +67,7 @@ public class DownloadFragment extends Fragment implements ClickCallback {
super.onViewCreated(view, savedInstanceState); super.onViewCreated(view, savedInstanceState);
initAppBar(); initAppBar();
initDownloadedSongView(); initDownloadedView();
} }
@Override @Override
@@ -90,11 +98,12 @@ public class DownloadFragment extends Fragment implements ClickCallback {
Objects.requireNonNull(materialToolbar.getOverflowIcon()).setTint(requireContext().getResources().getColor(R.color.titleTextColor, null)); Objects.requireNonNull(materialToolbar.getOverflowIcon()).setTint(requireContext().getResources().getColor(R.color.titleTextColor, null));
} }
private void initDownloadedSongView() { private void initDownloadedView() {
bind.downloadedTracksRecyclerView.setHasFixedSize(true); bind.downloadedRecyclerView.setHasFixedSize(true);
downloadHorizontalAdapter = new DownloadHorizontalAdapter(this); downloadHorizontalAdapter = new DownloadHorizontalAdapter(this);
bind.downloadedTracksRecyclerView.setAdapter(downloadHorizontalAdapter); bind.downloadedRecyclerView.setAdapter(downloadHorizontalAdapter);
downloadViewModel.getDownloadedTracks(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), songs -> { downloadViewModel.getDownloadedTracks(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), songs -> {
if (songs != null) { if (songs != null) {
if (songs.isEmpty()) { if (songs.isEmpty()) {
@@ -102,26 +111,109 @@ public class DownloadFragment extends Fragment implements ClickCallback {
bind.emptyDownloadLayout.setVisibility(View.VISIBLE); bind.emptyDownloadLayout.setVisibility(View.VISIBLE);
bind.fragmentDownloadNestedScrollView.setVisibility(View.GONE); bind.fragmentDownloadNestedScrollView.setVisibility(View.GONE);
bind.downloadDownloadedTracksPlaceholder.placeholder.setVisibility(View.VISIBLE); bind.downloadDownloadedPlaceholder.placeholder.setVisibility(View.VISIBLE);
bind.downloadDownloadedTracksSector.setVisibility(View.GONE); bind.downloadDownloadedSector.setVisibility(View.GONE);
bind.downloadedGroupByImageView.setVisibility(View.GONE);
} }
} else { } else {
if (bind != null) { if (bind != null) {
bind.emptyDownloadLayout.setVisibility(View.GONE); bind.emptyDownloadLayout.setVisibility(View.GONE);
bind.fragmentDownloadNestedScrollView.setVisibility(View.VISIBLE); bind.fragmentDownloadNestedScrollView.setVisibility(View.VISIBLE);
bind.downloadDownloadedTracksPlaceholder.placeholder.setVisibility(View.GONE); bind.downloadDownloadedPlaceholder.placeholder.setVisibility(View.GONE);
bind.downloadDownloadedTracksSector.setVisibility(View.VISIBLE); bind.downloadDownloadedSector.setVisibility(View.VISIBLE);
bind.downloadedTracksRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext())); bind.downloadedGroupByImageView.setVisibility(View.VISIBLE);
downloadHorizontalAdapter.setItems(songs); finishDownloadView(songs);
} }
} }
if (bind != null) bind.loadingProgressBar.setVisibility(View.GONE); if (bind != null) bind.loadingProgressBar.setVisibility(View.GONE);
} }
}); });
bind.downloadedGroupByImageView.setOnClickListener(view -> showPopupMenu(view, R.menu.download_popup_menu));
bind.downloadedGoBackImageView.setOnClickListener(view -> downloadViewModel.popViewStack());
}
private void finishDownloadView(List<Child> songs) {
downloadViewModel.getViewStack().observe(getViewLifecycleOwner(), stack -> {
bind.downloadedRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
DownloadStack lastLevel = stack.get(stack.size() - 1);
switch (lastLevel.getId()) {
case Constants.DOWNLOAD_TYPE_TRACK:
downloadHorizontalAdapter.setItems(Constants.DOWNLOAD_TYPE_TRACK, lastLevel.getId(), lastLevel.getView(), songs);
break;
case Constants.DOWNLOAD_TYPE_ALBUM:
downloadHorizontalAdapter.setItems(Constants.DOWNLOAD_TYPE_TRACK, lastLevel.getId(), lastLevel.getView(), songs);
break;
case Constants.DOWNLOAD_TYPE_ARTIST:
downloadHorizontalAdapter.setItems(Constants.DOWNLOAD_TYPE_ALBUM, lastLevel.getId(), lastLevel.getView(), songs);
break;
case Constants.DOWNLOAD_TYPE_GENRE:
downloadHorizontalAdapter.setItems(Constants.DOWNLOAD_TYPE_TRACK, lastLevel.getId(), lastLevel.getView(), songs);
break;
case Constants.DOWNLOAD_TYPE_YEAR:
downloadHorizontalAdapter.setItems(Constants.DOWNLOAD_TYPE_TRACK, lastLevel.getId(), lastLevel.getView(), songs);
break;
}
bind.downloadedGoBackImageView.setVisibility(stack.size() > 1 ? View.VISIBLE : View.GONE);
setupBackPressing(stack.size());
});
}
private void setupBackPressing(int stackSize) {
requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(), new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
if (stackSize > 1) {
downloadViewModel.popViewStack();
} else {
activity.navController.navigateUp();
}
remove();
}
});
}
private void showPopupMenu(View view, int menuResource) {
PopupMenu popup = new PopupMenu(requireContext(), view);
popup.getMenuInflater().inflate(menuResource, popup.getMenu());
popup.setOnMenuItemClickListener(menuItem -> {
if (menuItem.getItemId() == R.id.menu_download_group_by_track) {
downloadViewModel.initViewStack(new DownloadStack(Constants.DOWNLOAD_TYPE_TRACK, null));
Preferences.setDefaultDownloadViewType(Constants.DOWNLOAD_TYPE_TRACK);
return true;
} else if (menuItem.getItemId() == R.id.menu_download_group_by_album) {
downloadViewModel.initViewStack(new DownloadStack(Constants.DOWNLOAD_TYPE_ALBUM, null));
Preferences.setDefaultDownloadViewType(Constants.DOWNLOAD_TYPE_ALBUM);
return true;
} else if (menuItem.getItemId() == R.id.menu_download_group_by_artist) {
downloadViewModel.initViewStack(new DownloadStack(Constants.DOWNLOAD_TYPE_ARTIST, null));
Preferences.setDefaultDownloadViewType(Constants.DOWNLOAD_TYPE_ARTIST);
return true;
} else if (menuItem.getItemId() == R.id.menu_download_group_by_genre) {
downloadViewModel.initViewStack(new DownloadStack(Constants.DOWNLOAD_TYPE_GENRE, null));
Preferences.setDefaultDownloadViewType(Constants.DOWNLOAD_TYPE_GENRE);
return true;
} else if (menuItem.getItemId() == R.id.menu_download_group_by_year) {
downloadViewModel.initViewStack(new DownloadStack(Constants.DOWNLOAD_TYPE_YEAR, null));
Preferences.setDefaultDownloadViewType(Constants.DOWNLOAD_TYPE_YEAR);
return true;
}
return false;
});
popup.show();
} }
private void initializeMediaBrowser() { private void initializeMediaBrowser() {
@@ -132,6 +224,26 @@ public class DownloadFragment extends Fragment implements ClickCallback {
MediaBrowser.releaseFuture(mediaBrowserListenableFuture); MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
} }
@Override
public void onYearClick(Bundle bundle) {
downloadViewModel.pushViewStack(new DownloadStack(Constants.DOWNLOAD_TYPE_YEAR, bundle.getString(Constants.DOWNLOAD_TYPE_YEAR)));
}
@Override
public void onGenreClick(Bundle bundle) {
downloadViewModel.pushViewStack(new DownloadStack(Constants.DOWNLOAD_TYPE_GENRE, bundle.getString(Constants.DOWNLOAD_TYPE_GENRE)));
}
@Override
public void onArtistClick(Bundle bundle) {
downloadViewModel.pushViewStack(new DownloadStack(Constants.DOWNLOAD_TYPE_ARTIST, bundle.getString(Constants.DOWNLOAD_TYPE_ARTIST)));
}
@Override
public void onAlbumClick(Bundle bundle) {
downloadViewModel.pushViewStack(new DownloadStack(Constants.DOWNLOAD_TYPE_ALBUM, bundle.getString(Constants.DOWNLOAD_TYPE_ALBUM)));
}
@Override @Override
public void onMediaClick(Bundle bundle) { public void onMediaClick(Bundle bundle) {
MediaManager.startQueue(mediaBrowserListenableFuture, bundle.getParcelableArrayList(Constants.TRACKS_OBJECT), bundle.getInt(Constants.ITEM_POSITION)); MediaManager.startQueue(mediaBrowserListenableFuture, bundle.getParcelableArrayList(Constants.TRACKS_OBJECT), bundle.getInt(Constants.ITEM_POSITION));
@@ -142,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

@@ -5,7 +5,6 @@ import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@@ -290,11 +289,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 +313,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 +338,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 +363,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 +390,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 +409,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 +448,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 +483,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 +520,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 +556,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 +581,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 +607,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 +632,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);
} }
@@ -619,25 +664,6 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
}); });
} }
public void reorder() {
if (bind != null) {
// bind.homeLinearLayoutContainer.removeAllViews();
// bind.homeLinearLayoutContainer.addView(bind.homeDiscoverSector);
// bind.homeLinearLayoutContainer.addView(bind.homeSimilarTracksSector);
// bind.homeLinearLayoutContainer.addView(bind.homeRadioArtistSector);
// bind.homeLinearLayoutContainer.addView(bind.homeGridTracksSector);
// bind.homeLinearLayoutContainer.addView(bind.starredTracksSector);
// bind.homeLinearLayoutContainer.addView(bind.starredAlbumsSector);
// bind.homeLinearLayoutContainer.addView(bind.starredArtistsSector);
// bind.homeLinearLayoutContainer.addView(bind.homeRecentlyAddedAlbumsSector);
// bind.homeLinearLayoutContainer.addView(bind.homeFlashbackSector);
// bind.homeLinearLayoutContainer.addView(bind.homeMostPlayedAlbumsSector);
// bind.homeLinearLayoutContainer.addView(bind.homeRecentlyPlayedAlbumsSector);
}
}
private void initializeMediaBrowser() { 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();
} }

View File

@@ -85,17 +85,22 @@ public class IndexFragment extends Fragment implements ClickCallback {
} }
private void initDirectoryListView() { private void initDirectoryListView() {
MusicFolder musicFolder = getArguments().getParcelable(Constants.MUSIC_FOLDER_OBJECT);
bind.indexRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext())); bind.indexRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
bind.indexRecyclerView.setHasFixedSize(true); bind.indexRecyclerView.setHasFixedSize(true);
musicIndexAdapter = new MusicIndexAdapter(this); musicIndexAdapter = new MusicIndexAdapter(this);
bind.indexRecyclerView.setAdapter(musicIndexAdapter); bind.indexRecyclerView.setAdapter(musicIndexAdapter);
indexViewModel.getIndexes().observe(getViewLifecycleOwner(), indexes -> { indexViewModel.getIndexes(musicFolder != null ? musicFolder.getId() : null).observe(getViewLifecycleOwner(), indexes -> {
if (indexes != null) { if (indexes != null) {
musicIndexAdapter.setItems(IndexUtil.getArtist(indexes)); musicIndexAdapter.setItems(IndexUtil.getArtist(indexes));
} }
}); });
bind.fastScrollbar.setRecyclerView(bind.indexRecyclerView);
bind.fastScrollbar.setViewsToUse(R.layout.layout_fast_scrollbar, R.id.fastscroller_bubble, R.id.fastscroller_handle);
} }
@Override @Override

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

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

@@ -19,6 +19,7 @@ import androidx.media3.session.SessionToken;
import androidx.navigation.Navigation; import androidx.navigation.Navigation;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import com.bumptech.glide.load.resource.bitmap.GranularRoundedCorners;
import com.cappielloantonio.tempo.R; import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentPlaylistPageBinding; import com.cappielloantonio.tempo.databinding.FragmentPlaylistPageBinding;
import com.cappielloantonio.tempo.glide.CustomGlideRequest; import com.cappielloantonio.tempo.glide.CustomGlideRequest;
@@ -146,13 +147,13 @@ public class PlaylistPageFragment extends Fragment implements ClickCallback {
playlistPageViewModel.getPlaylistSongLiveList().observe(getViewLifecycleOwner(), songs -> { playlistPageViewModel.getPlaylistSongLiveList().observe(getViewLifecycleOwner(), songs -> {
if (bind != null) { if (bind != null) {
bind.playlistPagePlayButton.setOnClickListener(v -> { bind.playlistPagePlayButton.setOnClickListener(v -> {
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0); MediaManager.startQueue(mediaBrowserListenableFuture, songs.subList(0, Math.min(100, songs.size())), 0);
activity.setBottomSheetInPeek(true); activity.setBottomSheetInPeek(true);
}); });
bind.playlistPageShuffleButton.setOnClickListener(v -> { bind.playlistPageShuffleButton.setOnClickListener(v -> {
Collections.shuffle(songs); Collections.shuffle(songs);
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0); MediaManager.startQueue(mediaBrowserListenableFuture, songs.subList(0, Math.min(100, songs.size())), 0);
activity.setBottomSheetInPeek(true); activity.setBottomSheetInPeek(true);
}); });
} }
@@ -160,17 +161,46 @@ public class PlaylistPageFragment extends Fragment implements ClickCallback {
} }
private void initBackCover() { private void initBackCover() {
CustomGlideRequest.Builder playlistPageViewModel.getPlaylistSongLiveList().observe(requireActivity(), songs -> {
.from(requireContext(), playlistPageViewModel.getPlaylist().getCoverArtId()) if (bind != null && songs != null && !songs.isEmpty()) {
.build() Collections.shuffle(songs);
.into(bind.playlistCoverImageView);
// Pic top-left
CustomGlideRequest.Builder
.from(requireContext(), songs.size() > 0 ? songs.get(0).getCoverArtId() : playlistPageViewModel.getPlaylist().getCoverArtId(), CustomGlideRequest.ResourceType.Song)
.build()
.transform(new GranularRoundedCorners(CustomGlideRequest.CORNER_RADIUS, 0, 0, 0))
.into(bind.playlistCoverImageViewTopLeft);
// Pic top-right
CustomGlideRequest.Builder
.from(requireContext(), songs.size() > 1 ? songs.get(1).getCoverArtId() : playlistPageViewModel.getPlaylist().getCoverArtId(), CustomGlideRequest.ResourceType.Song)
.build()
.transform(new GranularRoundedCorners(0, CustomGlideRequest.CORNER_RADIUS, 0, 0))
.into(bind.playlistCoverImageViewTopRight);
// Pic bottom-left
CustomGlideRequest.Builder
.from(requireContext(), songs.size() > 2 ? songs.get(2).getCoverArtId() : playlistPageViewModel.getPlaylist().getCoverArtId(), CustomGlideRequest.ResourceType.Song)
.build()
.transform(new GranularRoundedCorners(0, 0, 0, CustomGlideRequest.CORNER_RADIUS))
.into(bind.playlistCoverImageViewBottomLeft);
// Pic bottom-right
CustomGlideRequest.Builder
.from(requireContext(), songs.size() > 3 ? songs.get(3).getCoverArtId() : playlistPageViewModel.getPlaylist().getCoverArtId(), CustomGlideRequest.ResourceType.Song)
.build()
.transform(new GranularRoundedCorners(0, 0, CustomGlideRequest.CORNER_RADIUS, 0))
.into(bind.playlistCoverImageViewBottomRight);
}
});
} }
private void initSongsView() { private void initSongsView() {
bind.songRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext())); bind.songRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
bind.songRecyclerView.setHasFixedSize(true); bind.songRecyclerView.setHasFixedSize(true);
songHorizontalAdapter = new SongHorizontalAdapter(this, true); songHorizontalAdapter = new SongHorizontalAdapter(this, true, false);
bind.songRecyclerView.setAdapter(songHorizontalAdapter); bind.songRecyclerView.setAdapter(songHorizontalAdapter);
playlistPageViewModel.getPlaylistSongLiveList().observe(getViewLifecycleOwner(), songs -> songHorizontalAdapter.setItems(songs)); playlistPageViewModel.getPlaylistSongLiveList().observe(getViewLifecycleOwner(), songs -> songHorizontalAdapter.setItems(songs));

View File

@@ -5,6 +5,7 @@ import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.PopupMenu;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
@@ -17,7 +18,6 @@ import androidx.recyclerview.widget.LinearLayoutManager;
import com.cappielloantonio.tempo.R; import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentPodcastChannelPageBinding; import com.cappielloantonio.tempo.databinding.FragmentPodcastChannelPageBinding;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.interfaces.ClickCallback; import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.service.MediaManager; import com.cappielloantonio.tempo.service.MediaManager;
import com.cappielloantonio.tempo.service.MediaService; import com.cappielloantonio.tempo.service.MediaService;
@@ -28,11 +28,10 @@ import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil; import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.util.UIUtil; import com.cappielloantonio.tempo.util.UIUtil;
import com.cappielloantonio.tempo.viewmodel.PodcastChannelPageViewModel; import com.cappielloantonio.tempo.viewmodel.PodcastChannelPageViewModel;
import com.google.android.material.snackbar.Snackbar;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
@UnstableApi @UnstableApi
public class PodcastChannelPageFragment extends Fragment implements ClickCallback { public class PodcastChannelPageFragment extends Fragment implements ClickCallback {
@@ -84,28 +83,26 @@ public class PodcastChannelPageFragment extends Fragment implements ClickCallbac
} }
private void initAppBar() { private void initAppBar() {
activity.setSupportActionBar(bind.animToolbar); activity.setSupportActionBar(bind.toolbar);
if (activity.getSupportActionBar() != null)
activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
bind.collapsingToolbar.setTitle(MusicUtil.getReadableString(podcastChannelPageViewModel.getPodcastChannel().getTitle())); if (activity.getSupportActionBar() != null) {
bind.animToolbar.setNavigationOnClickListener(v -> activity.navController.navigateUp()); activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
bind.collapsingToolbar.setExpandedTitleColor(getResources().getColor(R.color.white, null)); activity.getSupportActionBar().setDisplayShowHomeEnabled(true);
}
bind.toolbar.setTitle(MusicUtil.getReadableString(podcastChannelPageViewModel.getPodcastChannel().getTitle()));
bind.toolbar.setNavigationOnClickListener(v -> activity.navController.navigateUp());
bind.toolbar.setTitle(MusicUtil.getReadableString(podcastChannelPageViewModel.getPodcastChannel().getTitle()));
} }
private void initPodcastChannelInfo() { private void initPodcastChannelInfo() {
String normalizePodcastChannelDescription = MusicUtil.forceReadableString(podcastChannelPageViewModel.getPodcastChannel().getDescription()); String normalizePodcastChannelDescription = MusicUtil.forceReadableString(podcastChannelPageViewModel.getPodcastChannel().getDescription());
if (bind != null) if (bind != null) {
bind.podcastChannelDescriptionTextView.setVisibility(!normalizePodcastChannelDescription.trim().isEmpty() ? View.VISIBLE : View.GONE); bind.podcastChannelDescriptionTextView.setVisibility(!normalizePodcastChannelDescription.trim().isEmpty() ? View.VISIBLE : View.GONE);
if (getContext() != null && bind != null) CustomGlideRequest.Builder
.from(requireContext(), podcastChannelPageViewModel.getPodcastChannel().getCoverArtId())
.build()
.into(bind.podcastChannelBackdropImageView);
if (bind != null)
bind.podcastChannelDescriptionTextView.setText(normalizePodcastChannelDescription); bind.podcastChannelDescriptionTextView.setText(normalizePodcastChannelDescription);
bind.podcastEpisodesFilterImageView.setOnClickListener(view -> showPopupMenu(view, R.menu.filter_podcast_episode_popup_menu));
}
} }
private void initPodcastChannelEpisodesView() { private void initPodcastChannelEpisodesView() {
@@ -116,22 +113,21 @@ public class PodcastChannelPageFragment extends Fragment implements ClickCallbac
bind.podcastEpisodesRecyclerView.setAdapter(podcastEpisodeAdapter); bind.podcastEpisodesRecyclerView.setAdapter(podcastEpisodeAdapter);
podcastChannelPageViewModel.getPodcastChannelEpisodes().observe(getViewLifecycleOwner(), channels -> { podcastChannelPageViewModel.getPodcastChannelEpisodes().observe(getViewLifecycleOwner(), channels -> {
if (channels == null) { if (channels == null) {
if (bind != null) if (bind != null) {
bind.podcastChannelPageEpisodesPlaceholder.placeholder.setVisibility(View.VISIBLE); bind.podcastEpisodesRecyclerView.setVisibility(View.GONE);
if (bind != null) bind.podcastChannelPageEpisodesSector.setVisibility(View.GONE); }
} else { } else {
if (bind != null) if (bind != null) {
bind.podcastChannelPageEpisodesPlaceholder.placeholder.setVisibility(View.GONE); bind.podcastEpisodesRecyclerView.setVisibility(View.VISIBLE);
}
if (!channels.isEmpty() && channels.get(0) != null && channels.get(0).getEpisodes() != null) { if (!channels.isEmpty() && channels.get(0) != null && channels.get(0).getEpisodes() != null) {
List<PodcastEpisode> availableEpisode = channels.get(0).getEpisodes().stream().filter(podcastEpisode -> Objects.equals(podcastEpisode.getStatus(), "completed")).collect(Collectors.toList()); List<PodcastEpisode> availableEpisode = channels.get(0).getEpisodes();
if (bind != null) { if (bind != null && availableEpisode != null) {
bind.podcastEpisodesRecyclerView.setVisibility(availableEpisode.isEmpty() ? View.GONE : View.VISIBLE); bind.podcastEpisodesRecyclerView.setVisibility(availableEpisode.isEmpty() ? View.GONE : View.VISIBLE);
bind.podcastEpisodesAvailabilityTextView.setVisibility(availableEpisode.isEmpty() ? View.VISIBLE : View.GONE); podcastEpisodeAdapter.setItems(availableEpisode);
} }
podcastEpisodeAdapter.setItems(availableEpisode);
} }
} }
}); });
@@ -145,6 +141,25 @@ public class PodcastChannelPageFragment extends Fragment implements ClickCallbac
MediaBrowser.releaseFuture(mediaBrowserListenableFuture); MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
} }
private void showPopupMenu(View view, int menuResource) {
PopupMenu popup = new PopupMenu(requireContext(), view);
popup.getMenuInflater().inflate(menuResource, popup.getMenu());
popup.setOnMenuItemClickListener(menuItem -> {
if (menuItem.getItemId() == R.id.menu_podcast_filter_download) {
podcastEpisodeAdapter.sort(Constants.PODCAST_FILTER_BY_DOWNLOAD);
return true;
} else if (menuItem.getItemId() == R.id.menu_podcast_filter_all) {
podcastEpisodeAdapter.sort(Constants.PODCAST_FILTER_BY_ALL);
return true;
}
return false;
});
popup.show();
}
@Override @Override
public void onPodcastEpisodeClick(Bundle bundle) { public void onPodcastEpisodeClick(Bundle bundle) {
MediaManager.startPodcast(mediaBrowserListenableFuture, bundle.getParcelable(Constants.PODCAST_OBJECT)); MediaManager.startPodcast(mediaBrowserListenableFuture, bundle.getParcelable(Constants.PODCAST_OBJECT));
@@ -155,4 +170,14 @@ public class PodcastChannelPageFragment extends Fragment implements ClickCallbac
public void onPodcastEpisodeLongClick(Bundle bundle) { public void onPodcastEpisodeLongClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.podcastEpisodeBottomSheetDialog, bundle); Navigation.findNavController(requireView()).navigate(R.id.podcastEpisodeBottomSheetDialog, bundle);
} }
@Override
public void onPodcastEpisodeAltClick(Bundle bundle) {
PodcastEpisode episode = bundle.getParcelable(Constants.PODCAST_OBJECT);
podcastChannelPageViewModel.requestPodcastEpisodeDownload(episode);
Snackbar.make(requireView(), R.string.podcast_episode_download_request_snackbar, Snackbar.LENGTH_SHORT)
.setAnchorView(activity.bind.bottomNavigation)
.show();
}
} }

View File

@@ -4,6 +4,7 @@ import android.content.ComponentName;
import android.os.Bundle; import android.os.Bundle;
import android.text.Editable; import android.text.Editable;
import android.text.TextWatcher; import android.text.TextWatcher;
import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@@ -111,7 +112,7 @@ public class SearchFragment extends Fragment implements ClickCallback {
bind.searchResultTracksRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext())); bind.searchResultTracksRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
bind.searchResultTracksRecyclerView.setHasFixedSize(true); bind.searchResultTracksRecyclerView.setHasFixedSize(true);
songHorizontalAdapter = new SongHorizontalAdapter(this, true); songHorizontalAdapter = new SongHorizontalAdapter(this, true, false);
bind.searchResultTracksRecyclerView.setAdapter(songHorizontalAdapter); bind.searchResultTracksRecyclerView.setAdapter(songHorizontalAdapter);
} }
@@ -121,17 +122,11 @@ public class SearchFragment extends Fragment implements ClickCallback {
bind.searchView bind.searchView
.getEditText() .getEditText()
.setOnEditorActionListener((textView, actionId, keyEvent) -> { .setOnEditorActionListener((textView, actionId, keyEvent) -> {
String query = bind.searchView.getText().toString(); String query = bind.searchView.getText().toString();
if (actionId == EditorInfo.IME_ACTION_DONE) { if (isQueryValid(query)) {
if (isQueryValid(query)) { search(query);
search(bind.searchView.getText().toString()); return true;
return true;
} else {
Toast.makeText(requireContext(), getString(R.string.search_info_minimum_characters), Toast.LENGTH_SHORT).show();
return false;
}
} }
return false; return false;
@@ -147,7 +142,7 @@ public class SearchFragment extends Fragment implements ClickCallback {
@Override @Override
public void onTextChanged(CharSequence charSequence, int start, int before, int count) { public void onTextChanged(CharSequence charSequence, int start, int before, int count) {
if (count > 1) { if (start + count > 1) {
setSearchSuggestions(charSequence.toString()); setSearchSuggestions(charSequence.toString());
} else { } else {
setRecentSuggestions(); setRecentSuggestions();

View File

@@ -12,20 +12,30 @@ import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.OptIn; import androidx.annotation.OptIn;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.core.os.LocaleListCompat;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.preference.ListPreference; import androidx.preference.ListPreference;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat; import androidx.preference.PreferenceFragmentCompat;
import com.cappielloantonio.tempo.BuildConfig; import com.cappielloantonio.tempo.BuildConfig;
import com.cappielloantonio.tempo.R; import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.helper.ThemeHelper; import com.cappielloantonio.tempo.helper.ThemeHelper;
import com.cappielloantonio.tempo.interfaces.DialogClickCallback;
import com.cappielloantonio.tempo.interfaces.ScanCallback; import com.cappielloantonio.tempo.interfaces.ScanCallback;
import com.cappielloantonio.tempo.ui.activity.MainActivity; import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.dialog.DeleteDownloadStorageDialog;
import com.cappielloantonio.tempo.ui.dialog.DownloadStorageDialog;
import com.cappielloantonio.tempo.ui.dialog.StarredSyncDialog; import com.cappielloantonio.tempo.ui.dialog.StarredSyncDialog;
import com.cappielloantonio.tempo.util.Preferences; import com.cappielloantonio.tempo.util.Preferences;
import com.cappielloantonio.tempo.util.UIUtil;
import com.cappielloantonio.tempo.viewmodel.SettingViewModel; import com.cappielloantonio.tempo.viewmodel.SettingViewModel;
import java.util.Locale;
import java.util.Map;
@OptIn(markerClass = UnstableApi.class) @OptIn(markerClass = UnstableApi.class)
public class SettingsFragment extends PreferenceFragmentCompat { public class SettingsFragment extends PreferenceFragmentCompat {
private static final String TAG = "SettingsFragment"; private static final String TAG = "SettingsFragment";
@@ -70,44 +80,17 @@ public class SettingsFragment extends PreferenceFragmentCompat {
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
findPreference("version").setSummary(BuildConfig.VERSION_NAME); checkEqualizer();
checkStorage();
findPreference("logout").setOnPreferenceClickListener(preference -> { setAppLanguage();
activity.quit(); setVersion();
return true;
});
findPreference("scan_library").setOnPreferenceClickListener(preference -> { actionLogout();
settingViewModel.launchScan(new ScanCallback() { actionScan();
@Override actionSyncStarredTracks();
public void onError(Exception exception) { actionChangeDownloadStorage();
findPreference("scan_library").setSummary(exception.getMessage()); actionDeleteDownloadStorage();
}
@Override
public void onSuccess(boolean isScanning, long count) {
getScanStatus();
}
});
return true;
});
findPreference("equalizer").setOnPreferenceClickListener(preference -> {
Intent intent = new Intent(AudioEffect.ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL);
someActivityResultLauncher.launch(intent);
return true;
});
findPreference("sync_starred_tracks_for_offline_use").setOnPreferenceChangeListener((preference, newValue) -> {
if (newValue instanceof Boolean) {
if ((Boolean) newValue) {
StarredSyncDialog dialog = new StarredSyncDialog();
dialog.show(activity.getSupportFragmentManager(), null);
}
}
return true;
});
} }
@Override @Override
@@ -130,6 +113,127 @@ public class SettingsFragment extends PreferenceFragmentCompat {
} }
} }
private void checkEqualizer() {
Preference equalizer = findPreference("equalizer");
if (equalizer == null) return;
Intent intent = new Intent(AudioEffect.ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL);
if ((intent.resolveActivity(requireActivity().getPackageManager()) != null)) {
equalizer.setOnPreferenceClickListener(preference -> {
someActivityResultLauncher.launch(intent);
return true;
});
} else {
equalizer.setVisible(false);
}
}
private void checkStorage() {
Preference storage = findPreference("download_storage");
if (storage == null) return;
try {
if (requireContext().getExternalFilesDirs(null)[1] == null) {
storage.setVisible(false);
} else {
storage.setSummary(Preferences.getDownloadStoragePreference() == 0 ? R.string.download_storage_internal_dialog_negative_button : R.string.download_storage_external_dialog_positive_button);
}
} catch (Exception exception) {
storage.setVisible(false);
}
}
private void setAppLanguage() {
ListPreference localePref = (ListPreference) findPreference("language");
Map<String, String> locales = UIUtil.getLangPreferenceDropdownEntries(requireContext());
CharSequence[] entries = locales.keySet().toArray(new CharSequence[locales.size()]);
CharSequence[] entryValues = locales.values().toArray(new CharSequence[locales.size()]);
localePref.setEntries(entries);
localePref.setEntryValues(entryValues);
localePref.setDefaultValue(entryValues[0]);
localePref.setSummary(Locale.forLanguageTag(localePref.getValue()).getDisplayLanguage());
localePref.setOnPreferenceChangeListener((preference, newValue) -> {
LocaleListCompat appLocale = LocaleListCompat.forLanguageTags((String) newValue);
AppCompatDelegate.setApplicationLocales(appLocale);
return true;
});
}
private void setVersion() {
findPreference("version").setSummary(BuildConfig.VERSION_NAME);
}
private void actionLogout() {
findPreference("logout").setOnPreferenceClickListener(preference -> {
activity.quit();
return true;
});
}
private void actionScan() {
findPreference("scan_library").setOnPreferenceClickListener(preference -> {
settingViewModel.launchScan(new ScanCallback() {
@Override
public void onError(Exception exception) {
findPreference("scan_library").setSummary(exception.getMessage());
}
@Override
public void onSuccess(boolean isScanning, long count) {
getScanStatus();
}
});
return true;
});
}
private void actionSyncStarredTracks() {
findPreference("sync_starred_tracks_for_offline_use").setOnPreferenceChangeListener((preference, newValue) -> {
if (newValue instanceof Boolean) {
if ((Boolean) newValue) {
StarredSyncDialog dialog = new StarredSyncDialog();
dialog.show(activity.getSupportFragmentManager(), null);
}
}
return true;
});
}
private void actionChangeDownloadStorage() {
findPreference("download_storage").setOnPreferenceClickListener(preference -> {
DownloadStorageDialog dialog = new DownloadStorageDialog(new DialogClickCallback() {
@Override
public void onPositiveClick() {
findPreference("download_storage").setSummary(R.string.download_storage_external_dialog_positive_button);
}
@Override
public void onNegativeClick() {
findPreference("download_storage").setSummary(R.string.download_storage_internal_dialog_negative_button);
}
});
dialog.show(activity.getSupportFragmentManager(), null);
return true;
});
}
private void actionDeleteDownloadStorage() {
findPreference("delete_download_storage").setOnPreferenceClickListener(preference -> {
DeleteDownloadStorageDialog dialog = new DeleteDownloadStorageDialog();
dialog.show(activity.getSupportFragmentManager(), null);
return true;
});
}
private void getScanStatus() { private void getScanStatus() {
settingViewModel.getScanStatus(new ScanCallback() { settingViewModel.getScanStatus(new ScanCallback() {
@Override @Override

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

@@ -79,7 +79,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);
@@ -91,10 +91,9 @@ public class AlbumBottomSheetDialog extends BottomSheetDialogFragment implements
artistAlbum.setText(MusicUtil.getReadableString(albumBottomSheetViewModel.getAlbum().getArtist())); artistAlbum.setText(MusicUtil.getReadableString(albumBottomSheetViewModel.getAlbum().getArtist()));
ToggleButton favoriteToggle = view.findViewById(R.id.button_favorite); ToggleButton favoriteToggle = view.findViewById(R.id.button_favorite);
favoriteToggle.setChecked(Boolean.TRUE.equals(albumBottomSheetViewModel.getAlbum().getStarred())); favoriteToggle.setChecked(albumBottomSheetViewModel.getAlbum().getStarred() != null);
favoriteToggle.setOnClickListener(v -> { favoriteToggle.setOnClickListener(v -> {
albumBottomSheetViewModel.setFavorite(); albumBottomSheetViewModel.setFavorite();
dismissBottomSheet();
}); });
TextView playRadio = view.findViewById(R.id.play_radio_text_view); TextView playRadio = view.findViewById(R.id.play_radio_text_view);
@@ -148,8 +147,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());
@@ -158,17 +155,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) {
@@ -192,6 +193,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();
} }

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);
@@ -79,10 +79,9 @@ public class ArtistBottomSheetDialog extends BottomSheetDialogFragment implement
nameArtist.setSelected(true); nameArtist.setSelected(true);
ToggleButton favoriteToggle = view.findViewById(R.id.button_favorite); ToggleButton favoriteToggle = view.findViewById(R.id.button_favorite);
favoriteToggle.setChecked(Boolean.TRUE.equals(artistBottomSheetViewModel.getArtist().getStarred())); favoriteToggle.setChecked(artistBottomSheetViewModel.getArtist().getStarred() != null);
favoriteToggle.setOnClickListener(v -> { favoriteToggle.setOnClickListener(v -> {
artistBottomSheetViewModel.setFavorite(); artistBottomSheetViewModel.setFavorite();
dismissBottomSheet();
}); });
TextView playRadio = view.findViewById(R.id.play_radio_text_view); TextView playRadio = view.findViewById(R.id.play_radio_text_view);

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

@@ -74,7 +74,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);
@@ -87,10 +87,9 @@ public class SongBottomSheetDialog extends BottomSheetDialogFragment implements
artistSong.setText(MusicUtil.getReadableString(songBottomSheetViewModel.getSong().getArtist())); artistSong.setText(MusicUtil.getReadableString(songBottomSheetViewModel.getSong().getArtist()));
ToggleButton favoriteToggle = view.findViewById(R.id.button_favorite); ToggleButton favoriteToggle = view.findViewById(R.id.button_favorite);
favoriteToggle.setChecked(Boolean.TRUE.equals(songBottomSheetViewModel.getSong().getStarred())); favoriteToggle.setChecked(songBottomSheetViewModel.getSong().getStarred() != null);
favoriteToggle.setOnClickListener(v -> { favoriteToggle.setOnClickListener(v -> {
songBottomSheetViewModel.setFavorite(requireContext()); songBottomSheetViewModel.setFavorite(requireContext());
dismissBottomSheet();
}); });
favoriteToggle.setOnLongClickListener(v -> { favoriteToggle.setOnLongClickListener(v -> {
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
@@ -216,7 +215,6 @@ public class SongBottomSheetDialog extends BottomSheetDialogFragment implements
private void initDownloadUI(TextView download, TextView remove) { private void initDownloadUI(TextView download, TextView remove) {
if (DownloadUtil.getDownloadTracker(requireContext()).isDownloaded(song.getId())) { if (DownloadUtil.getDownloadTracker(requireContext()).isDownloaded(song.getId())) {
download.setVisibility(View.GONE);
remove.setVisibility(View.VISIBLE); remove.setVisibility(View.VISIBLE);
} else { } else {
download.setVisibility(View.VISIBLE); download.setVisibility(View.VISIBLE);

View File

@@ -44,6 +44,9 @@ object Constants {
const val PLAYLIST_ORDER_BY_NAME = "ORDER_BY_NAME" const val PLAYLIST_ORDER_BY_NAME = "ORDER_BY_NAME"
const val PLAYLIST_ORDER_BY_RANDOM = "ORDER_BY_RANDOM" const val PLAYLIST_ORDER_BY_RANDOM = "ORDER_BY_RANDOM"
const val PODCAST_FILTER_BY_DOWNLOAD = "PODCAST_FILTER_BY_DOWNLOAD"
const val PODCAST_FILTER_BY_ALL = "PODCAST_FILTER_BY_ALL"
const val MEDIA_TYPE_MUSIC = "music" const val MEDIA_TYPE_MUSIC = "music"
const val MEDIA_TYPE_PODCAST = "podcast" const val MEDIA_TYPE_PODCAST = "podcast"
const val MEDIA_TYPE_AUDIOBOOK = "audiobook" const val MEDIA_TYPE_AUDIOBOOK = "audiobook"
@@ -73,6 +76,16 @@ object Constants {
const val DOWNLOAD_URI = "rest/download" const val DOWNLOAD_URI = "rest/download"
const val DOWNLOAD_TYPE_TRACK = "download_type_track"
const val DOWNLOAD_TYPE_ALBUM = "download_type_album"
const val DOWNLOAD_TYPE_ARTIST = "download_type_artist"
const val DOWNLOAD_TYPE_GENRE = "download_type_genre"
const val DOWNLOAD_TYPE_YEAR = "download_type_year"
const val DOWNLOAD_GROUP = "download_group"
const val DOWNLOAD_GROUP_TITLE = "download_group_title"
const val DOWNLOAD_GROUP_SUBTITLE = "download_group_subtitle"
const val PLAYABLE_MEDIA_LIMIT = 100 const val 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";
@@ -127,9 +132,19 @@ public final class DownloadUtil {
private static synchronized File getDownloadDirectory(Context context) { private static synchronized File getDownloadDirectory(Context context) {
if (downloadDirectory == null) { if (downloadDirectory == null) {
downloadDirectory = context.getExternalFilesDir(null); if (Preferences.getDownloadStoragePreference() == 0) {
if (downloadDirectory == null) { downloadDirectory = context.getExternalFilesDirs(null)[0];
downloadDirectory = context.getFilesDir(); if (downloadDirectory == null) {
downloadDirectory = context.getFilesDir();
}
} else {
try {
downloadDirectory = context.getExternalFilesDirs(null)[1];
} catch (Exception exception) {
downloadDirectory = context.getExternalFilesDirs(null)[0];
Preferences.setDownloadStoragePreference(0);
}
} }
} }
@@ -143,4 +158,41 @@ public final class DownloadUtil {
.setCacheWriteDataSinkFactory(null) .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,9 @@ import androidx.media3.common.MimeTypes;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import com.cappielloantonio.tempo.App; import com.cappielloantonio.tempo.App;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.model.Download;
import com.cappielloantonio.tempo.repository.DownloadRepository;
import com.cappielloantonio.tempo.subsonic.models.Child; import com.cappielloantonio.tempo.subsonic.models.Child;
import com.cappielloantonio.tempo.subsonic.models.InternetRadioStation; import com.cappielloantonio.tempo.subsonic.models.InternetRadioStation;
import com.cappielloantonio.tempo.subsonic.models.PodcastEpisode; import com.cappielloantonio.tempo.subsonic.models.PodcastEpisode;
@@ -31,6 +34,7 @@ public class MappingUtil {
public static MediaItem mapMediaItem(Child media) { public static MediaItem mapMediaItem(Child media) {
Uri uri = getUri(media); Uri uri = getUri(media);
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(media.getCoverArtId(), Preferences.getImageSize()));
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
bundle.putString("id", media.getId()); bundle.putString("id", media.getId());
@@ -54,8 +58,8 @@ public class MappingUtil {
bundle.putBoolean("isVideo", media.isVideo()); bundle.putBoolean("isVideo", media.isVideo());
bundle.putInt("userRating", media.getUserRating() != null ? media.getUserRating() : 0); bundle.putInt("userRating", media.getUserRating() != null ? media.getUserRating() : 0);
bundle.putDouble("averageRating", media.getAverageRating() != null ? media.getAverageRating() : 0); bundle.putDouble("averageRating", media.getAverageRating() != null ? media.getAverageRating() : 0);
bundle.putLong("playCount", media.getPlayCount() != null ? media.getTrack() : 0); bundle.putLong("playCount", media.getPlayCount() != null ? media.getPlayCount() : 0);
bundle.putInt("discNumber", media.getDiscNumber() != null ? media.getTrack() : 0); bundle.putInt("discNumber", media.getDiscNumber() != null ? media.getDiscNumber() : 0);
bundle.putLong("created", media.getCreated() != null ? media.getCreated().getTime() : 0); bundle.putLong("created", media.getCreated() != null ? media.getCreated().getTime() : 0);
bundle.putLong("starred", media.getStarred() != null ? media.getStarred().getTime() : 0); bundle.putLong("starred", media.getStarred() != null ? media.getStarred().getTime() : 0);
bundle.putString("albumId", media.getAlbumId()); bundle.putString("albumId", media.getAlbumId());
@@ -71,11 +75,12 @@ public class MappingUtil {
.setMediaMetadata( .setMediaMetadata(
new MediaMetadata.Builder() new MediaMetadata.Builder()
.setTitle(MusicUtil.getReadableString(media.getTitle())) .setTitle(MusicUtil.getReadableString(media.getTitle()))
.setTrackNumber(media.getTrack()) .setTrackNumber(media.getTrack() != null ? media.getTrack() : 0)
.setDiscNumber(media.getDiscNumber()) .setDiscNumber(media.getDiscNumber() != null ? media.getDiscNumber() : 0)
.setReleaseYear(media.getYear()) .setReleaseYear(media.getYear() != null ? media.getYear() : 0)
.setAlbumTitle(MusicUtil.getReadableString(media.getAlbum())) .setAlbumTitle(MusicUtil.getReadableString(media.getAlbum()))
.setArtist(MusicUtil.getReadableString(media.getArtist())) .setArtist(MusicUtil.getReadableString(media.getArtist()))
.setArtworkUri(artworkUri)
.setExtras(bundle) .setExtras(bundle)
.build() .build()
) )
@@ -106,20 +111,20 @@ public class MappingUtil {
.setMediaMetadata( .setMediaMetadata(
new MediaMetadata.Builder() new MediaMetadata.Builder()
.setTitle(MusicUtil.getReadableString(media.getTitle())) .setTitle(MusicUtil.getReadableString(media.getTitle()))
.setTrackNumber(media.getTrack()) .setTrackNumber(media.getTrack() != null ? media.getTrack() : 0)
.setDiscNumber(media.getDiscNumber()) .setDiscNumber(media.getDiscNumber() != null ? media.getDiscNumber() : 0)
.setReleaseYear(media.getYear()) .setReleaseYear(media.getYear() != null ? media.getYear() : 0)
.setAlbumTitle(MusicUtil.getReadableString(media.getAlbum())) .setAlbumTitle(MusicUtil.getReadableString(media.getAlbum()))
.setArtist(MusicUtil.getReadableString(media.getArtist())) .setArtist(MusicUtil.getReadableString(media.getArtist()))
.build() .build()
) )
.setRequestMetadata( .setRequestMetadata(
new MediaItem.RequestMetadata.Builder() new MediaItem.RequestMetadata.Builder()
.setMediaUri(MusicUtil.getDownloadUri(media.getId())) .setMediaUri(Preferences.preferTranscodedDownload() ? MusicUtil.getTranscodedDownloadUri(media.getId()) : MusicUtil.getDownloadUri(media.getId()))
.build() .build()
) )
.setMimeType(MimeTypes.BASE_TYPE_AUDIO) .setMimeType(MimeTypes.BASE_TYPE_AUDIO)
.setUri(MusicUtil.getDownloadUri(media.getId())) .setUri(Preferences.preferTranscodedDownload() ? MusicUtil.getTranscodedDownloadUri(media.getId()) : MusicUtil.getDownloadUri(media.getId()))
.build(); .build();
} }
@@ -155,6 +160,7 @@ public class MappingUtil {
public static MediaItem mapMediaItem(PodcastEpisode podcastEpisode) { public static MediaItem mapMediaItem(PodcastEpisode podcastEpisode) {
Uri uri = getUri(podcastEpisode); Uri uri = getUri(podcastEpisode);
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(podcastEpisode.getCoverArtId(), Preferences.getImageSize()));
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
bundle.putString("id", podcastEpisode.getId()); bundle.putString("id", podcastEpisode.getId());
@@ -178,8 +184,8 @@ public class MappingUtil {
bundle.putBoolean("isVideo", podcastEpisode.isVideo()); bundle.putBoolean("isVideo", podcastEpisode.isVideo());
bundle.putInt("userRating", podcastEpisode.getUserRating() != null ? podcastEpisode.getUserRating() : 0); bundle.putInt("userRating", podcastEpisode.getUserRating() != null ? podcastEpisode.getUserRating() : 0);
bundle.putDouble("averageRating", podcastEpisode.getAverageRating() != null ? podcastEpisode.getAverageRating() : 0); bundle.putDouble("averageRating", podcastEpisode.getAverageRating() != null ? podcastEpisode.getAverageRating() : 0);
bundle.putLong("playCount", podcastEpisode.getPlayCount() != null ? podcastEpisode.getTrack() : 0); bundle.putLong("playCount", podcastEpisode.getPlayCount() != null ? podcastEpisode.getPlayCount() : 0);
bundle.putInt("discNumber", podcastEpisode.getDiscNumber() != null ? podcastEpisode.getTrack() : 0); bundle.putInt("discNumber", podcastEpisode.getDiscNumber() != null ? podcastEpisode.getDiscNumber() : 0);
bundle.putLong("created", podcastEpisode.getCreated() != null ? podcastEpisode.getCreated().getTime() : 0); bundle.putLong("created", podcastEpisode.getCreated() != null ? podcastEpisode.getCreated().getTime() : 0);
bundle.putLong("starred", podcastEpisode.getStarred() != null ? podcastEpisode.getStarred().getTime() : 0); bundle.putLong("starred", podcastEpisode.getStarred() != null ? podcastEpisode.getStarred().getTime() : 0);
bundle.putString("albumId", podcastEpisode.getAlbumId()); bundle.putString("albumId", podcastEpisode.getAlbumId());
@@ -190,16 +196,17 @@ 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()
.setTitle(MusicUtil.getReadableString(podcastEpisode.getTitle())) .setTitle(MusicUtil.getReadableString(podcastEpisode.getTitle()))
.setTrackNumber(podcastEpisode.getTrack()) .setTrackNumber(podcastEpisode.getTrack() != null ? podcastEpisode.getTrack() : 0)
.setDiscNumber(podcastEpisode.getDiscNumber()) .setDiscNumber(podcastEpisode.getDiscNumber() != null ? podcastEpisode.getDiscNumber() : 0)
.setReleaseYear(podcastEpisode.getYear()) .setReleaseYear(podcastEpisode.getYear() != null ? podcastEpisode.getYear() : 0)
.setAlbumTitle(MusicUtil.getReadableString(podcastEpisode.getAlbum())) .setAlbumTitle(MusicUtil.getReadableString(podcastEpisode.getAlbum()))
.setArtist(MusicUtil.getReadableString(podcastEpisode.getArtist())) .setArtist(MusicUtil.getReadableString(podcastEpisode.getArtist()))
.setArtworkUri(artworkUri)
.setExtras(bundle) .setExtras(bundle)
.build() .build()
) )
@@ -209,20 +216,33 @@ 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) {
return DownloadUtil.getDownloadTracker(App.getContext()).isDownloaded(media.getId()) return DownloadUtil.getDownloadTracker(App.getContext()).isDownloaded(media.getId())
? MusicUtil.getDownloadUri(media.getId()) ? getDownloadUri(media.getId())
: MusicUtil.getStreamUri(media.getId()); : MusicUtil.getStreamUri(media.getId());
} }
private static Uri getUri(PodcastEpisode podcastEpisode) { private static Uri getUri(PodcastEpisode podcastEpisode) {
return DownloadUtil.getDownloadTracker(App.getContext()).isDownloaded(podcastEpisode.getId()) return DownloadUtil.getDownloadTracker(App.getContext()).isDownloaded(podcastEpisode.getStreamId())
? MusicUtil.getDownloadUri(podcastEpisode.getId()) ? getDownloadUri(podcastEpisode.getStreamId())
: MusicUtil.getStreamUri(podcastEpisode.getId()); : MusicUtil.getStreamUri(podcastEpisode.getStreamId());
}
private static Uri getDownloadUri(String id) {
Download download = new DownloadRepository().getDownload(id);
return download != null && !download.getDownloadUri().isEmpty() ? Uri.parse(download.getDownloadUri()) : MusicUtil.getDownloadUri(id);
} }
} }

View File

@@ -9,8 +9,13 @@ import android.text.Html;
import android.util.Log; import android.util.Log;
import com.cappielloantonio.tempo.App; import com.cappielloantonio.tempo.App;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.model.Download;
import com.cappielloantonio.tempo.repository.DownloadRepository;
import com.cappielloantonio.tempo.subsonic.models.Child; import com.cappielloantonio.tempo.subsonic.models.Child;
import java.text.CharacterIterator;
import java.text.StringCharacterIterator;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
@@ -29,7 +34,7 @@ public class MusicUtil {
uri.append("stream"); uri.append("stream");
if (params.containsKey("u") && params.get("u") != null) if (params.containsKey("u") && params.get("u") != null)
uri.append("?u=").append(params.get("u")); uri.append("?u=").append(Util.encode(params.get("u")));
if (params.containsKey("p") && params.get("p") != null) if (params.containsKey("p") && params.get("p") != null)
uri.append("&p=").append(params.get("p")); uri.append("&p=").append(params.get("p"));
if (params.containsKey("s") && params.get("s") != null) if (params.containsKey("s") && params.get("s") != null)
@@ -41,8 +46,11 @@ public class MusicUtil {
if (params.containsKey("c") && params.get("c") != null) if (params.containsKey("c") && params.get("c") != null)
uri.append("&c=").append(params.get("c")); uri.append("&c=").append(params.get("c"));
uri.append("&maxBitRate=").append(getBitratePreference()); if (!Preferences.isServerPrioritized())
uri.append("&format=").append(getTranscodingFormatPreference()); uri.append("&maxBitRate=").append(getBitratePreference());
if (!Preferences.isServerPrioritized())
uri.append("&format=").append(getTranscodingFormatPreference());
uri.append("&id=").append(id); uri.append("&id=").append(id);
Log.d(TAG, "getStreamUri: " + uri); Log.d(TAG, "getStreamUri: " + uri);
@@ -51,15 +59,49 @@ public class MusicUtil {
} }
public static Uri getDownloadUri(String id) { public static Uri getDownloadUri(String id) {
StringBuilder uri = new StringBuilder();
Download download = new DownloadRepository().getDownload(id);
if (download == null || download.getDownloadUri().isEmpty()) {
Map<String, String> params = App.getSubsonicClientInstance(false).getParams();
uri.append(App.getSubsonicClientInstance(false).getUrl());
uri.append("download");
if (params.containsKey("u") && params.get("u") != null)
uri.append("?u=").append(Util.encode(params.get("u")));
if (params.containsKey("p") && params.get("p") != null)
uri.append("&p=").append(params.get("p"));
if (params.containsKey("s") && params.get("s") != null)
uri.append("&s=").append(params.get("s"));
if (params.containsKey("t") && params.get("t") != null)
uri.append("&t=").append(params.get("t"));
if (params.containsKey("v") && params.get("v") != null)
uri.append("&v=").append(params.get("v"));
if (params.containsKey("c") && params.get("c") != null)
uri.append("&c=").append(params.get("c"));
uri.append("&id=").append(id);
} else {
uri.append(download.getDownloadUri());
}
Log.d(TAG, "getDownloadUri: " + uri);
return Uri.parse(uri.toString());
}
public static Uri getTranscodedDownloadUri(String id) {
Map<String, String> params = App.getSubsonicClientInstance(false).getParams(); Map<String, String> params = App.getSubsonicClientInstance(false).getParams();
StringBuilder uri = new StringBuilder(); StringBuilder uri = new StringBuilder();
uri.append(App.getSubsonicClientInstance(false).getUrl()); uri.append(App.getSubsonicClientInstance(false).getUrl());
uri.append("download"); 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)
@@ -71,13 +113,19 @@ public class MusicUtil {
if (params.containsKey("c") && params.get("c") != null) if (params.containsKey("c") && params.get("c") != null)
uri.append("&c=").append(params.get("c")); uri.append("&c=").append(params.get("c"));
if (!Preferences.isServerPrioritizedInTranscodedDownload())
uri.append("&maxBitRate=").append(getBitratePreferenceForDownload());
if (!Preferences.isServerPrioritizedInTranscodedDownload())
uri.append("&format=").append(getTranscodingFormatPreferenceForDownload());
uri.append("&id=").append(id); uri.append("&id=").append(id);
Log.d(TAG, "getDownloadUri: " + uri); Log.d(TAG, "getTranscodedDownloadUri: " + uri);
return Uri.parse(uri.toString()); return Uri.parse(uri.toString());
} }
public static String getReadableDurationString(long duration, boolean millis) { public static String getReadableDurationString(long duration, boolean millis) {
long minutes; long minutes;
long seconds; long seconds;
@@ -119,6 +167,14 @@ public class MusicUtil {
return ""; return "";
} }
public static String getReadableTrackNumber(Context context, Integer trackNumber) {
if (trackNumber != null) {
return String.valueOf(trackNumber);
}
return context.getString(R.string.label_placeholder);
}
public static String forceReadableString(String string) { public static String forceReadableString(String string) {
if (string != null) { if (string != null) {
return getReadableString(string) return getReadableString(string)
@@ -157,6 +213,27 @@ public class MusicUtil {
return readableStrings; return readableStrings;
} }
public static String getReadableByteCount(long bytes) {
long absB = bytes == Long.MIN_VALUE ? Long.MAX_VALUE : Math.abs(bytes);
if (absB < 1024) {
return bytes + " B";
}
long value = absB;
CharacterIterator ci = new StringCharacterIterator("KMGTPE");
for (int i = 40; i >= 0 && absB > 0xfffccccccccccccL >> i; i -= 10) {
value >>= 10;
ci.next();
}
value *= Long.signum(bytes);
return String.format("%.1f %ciB", value / 1024.0, ci.current());
}
public static String passwordHexEncoding(String plainPassword) { public static String passwordHexEncoding(String plainPassword) {
return "enc:" + plainPassword.chars().mapToObj(Integer::toHexString).collect(Collectors.joining()); return "enc:" + plainPassword.chars().mapToObj(Integer::toHexString).collect(Collectors.joining());
} }
@@ -193,6 +270,19 @@ public class MusicUtil {
} }
} }
public static String getBitratePreferenceForDownload() {
String audioTranscodeFormat = getTranscodingFormatPreferenceForDownload();
if (audioTranscodeFormat.equals("raw"))
return "0";
return Preferences.getBitrateTranscodedDownload();
}
public static String getTranscodingFormatPreferenceForDownload() {
return Preferences.getAudioTranscodeFormatTranscodedDownload();
}
public static List<Child> limitPlayableMedia(List<Child> toLimit, int position) { public static List<Child> limitPlayableMedia(List<Child> toLimit, int position) {
if (!toLimit.isEmpty() && toLimit.size() > Constants.PLAYABLE_MEDIA_LIMIT) { if (!toLimit.isEmpty() && toLimit.size() > Constants.PLAYABLE_MEDIA_LIMIT) {
int from = position < Constants.PRE_PLAYABLE_MEDIA ? 0 : position - Constants.PRE_PLAYABLE_MEDIA; int from = position < Constants.PRE_PLAYABLE_MEDIA ? 0 : position - Constants.PRE_PLAYABLE_MEDIA;
@@ -204,8 +294,12 @@ public class MusicUtil {
return toLimit; return toLimit;
} }
public static int getPlayableMediaPosition(int initialPosition) { public static int getPlayableMediaPosition(List<Child> toLimit, int position) {
return initialPosition > Constants.PLAYABLE_MEDIA_LIMIT ? Constants.PRE_PLAYABLE_MEDIA : initialPosition; if (!toLimit.isEmpty() && toLimit.size() > Constants.PLAYABLE_MEDIA_LIMIT) {
return Math.min(position, Constants.PRE_PLAYABLE_MEDIA);
}
return position;
} }
private static ConnectivityManager getConnectivityManager() { private static ConnectivityManager getConnectivityManager() {

View File

@@ -0,0 +1,21 @@
package com.cappielloantonio.tempo.util;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import com.cappielloantonio.tempo.App;
public class NetworkUtil {
public static boolean isOffline() {
ConnectivityManager connectivityManager = (ConnectivityManager) App.getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
if (connectivityManager != null) {
NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
return networkInfo == null || !networkInfo.isConnected();
}
return true;
}
}

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