288 Commits
3.3.2 ... 3.6.0

Author SHA1 Message Date
antonio
112e468c7d gradle: bump up code version 2024-01-03 12:47:08 +01:00
antonio
0ccc601f1b gradle: add db migrations 2024-01-03 12:46:45 +01:00
CappielloAntonio
ae82bcd7bf Update github_release.yml 2024-01-03 12:40:21 +01:00
antonio
68512b7e12 feat: Implemented search functionality for Android Auto, "Made for You" section, starred songs, albums, artists, podcasts, and radio 2024-01-03 12:23:46 +01:00
antonio
d6cc4fc028 feat: test: Implemented initial functional version with Android Auto support 2024-01-03 00:45:22 +01:00
antonio
e8c7c065e2 gradle: dependencies update 2023-12-31 15:35:15 +01:00
CappielloAntonio
427394a105 Merge pull request #121 from DelightLane/localization
feat: add korean localization
2023-12-21 09:55:09 +01:00
CappielloAntonio
75fc2cbafa Merge pull request #118 from rkowalsk/french-localization-updates
fix: update French localization and fix previous errors
2023-12-21 09:53:54 +01:00
antonio
b9a3393b39 style: code cleanup 2023-12-21 09:52:10 +01:00
antonio
ed60677608 feat: cleaned up MediaService class, added support for Android Auto repository 2023-12-21 09:52:00 +01:00
antonio
1a407341ec feat: cleaned up MediaService class, added support for Android Auto repository 2023-12-21 09:51:48 +01:00
antonio
567350771c feat: implemented an embryonic version of Android Auto support 2023-12-21 09:49:39 +01:00
antonio
47bef77723 feat: declared Android Auto support 2023-12-21 09:47:39 +01:00
DelightLane
77d0b4182e feat: add korean localization 2023-12-21 11:36:57 +09:00
Romain Kowalski
caf64e0fa4 feat: Update French localization and fix previous errors 2023-12-18 13:23:07 +01:00
antonio
ac90b89099 fix: network security configuration 2023-12-12 21:57:53 +01:00
antonio
4523bb8e49 feat: implemented horizontal layout for music player 2023-12-12 21:28:49 +01:00
antonio
06f4898892 refactor/fix: renamed method name to be more descriptive and manually collapse bottomSheet on device state change 2023-12-12 21:28:02 +01:00
antonio
5bbab10485 fix: null checking 2023-12-12 21:24:52 +01:00
antonio
8fe66d058e gradle: build:gradle update 2023-12-10 17:32:03 +01:00
antonio
6a893ac424 Merge remote-tracking branch 'origin/main' 2023-11-29 19:22:41 +01:00
antonio
f184ace301 gradle: dependencies update 2023-11-29 19:22:25 +01:00
antonio
121c2b33da feat: added filter for songs that don't meet a user defined rating threshold 2023-11-29 19:21:42 +01:00
CappielloAntonio
215f1021d6 Merge pull request #108 from ixff/main
feat: add Simplified Chinese localization
2023-11-29 11:39:16 +01:00
ixff
ce6159ad93 feat: add Simplified Chinese localization 2023-11-27 23:08:31 +08:00
ixff
746ea93dbe chore: fix some typos 2023-11-27 22:54:24 +08:00
antonio
612c05fabc fix: increased button's tap area to facilitate easier clicking 2023-11-26 19:28:21 +01:00
antonio
dfc246d079 feat: increased buffering multiplier limits 2023-11-26 19:14:42 +01:00
antonio
0a5983e32b feat: implemented streaming buffering strategy 2023-11-26 19:10:21 +01:00
antonio
9fde629233 fix: network security configuration with cleartext traffic opt-out 2023-11-26 17:36:38 +01:00
antonio
e089c5e466 gradle: bump up code version 2023-11-26 17:27:55 +01:00
antonio
94b0572958 feat: network security configuration 2023-11-26 17:22:38 +01:00
antonio
133b5e4794 feat: implemented settings for enabling or disabling music scrobbling 2023-11-26 16:39:05 +01:00
antonio
6b0ba573de feat: improved live transcoding UI 2023-11-26 16:20:07 +01:00
antonio
6ae7fc2172 gradle: dependencies update 2023-11-26 16:18:47 +01:00
antonio
9cabfb0e2c style: PR code cleanup 2023-11-05 17:10:13 +01:00
antonio
17f05cb3d8 Merge remote-tracking branch 'origin/main' 2023-11-05 16:44:23 +01:00
CappielloAntonio
f2839f4ff9 Merge pull request #100 from kingbluezback/main
style: updated dialog graphics to comply with Material You guidelines
2023-11-05 16:44:05 +01:00
antonio
9aadcb91fb fix: null checking 2023-11-05 16:43:13 +01:00
antonio
5d4dfe1ac7 gradle: gradle and dependencies update 2023-11-05 16:07:12 +01:00
kingbluezback
d3d76dd435 Change Dialogs
Update the dialogs to use material you theme.
2023-10-22 20:42:40 -07:00
kingbluezback
0c3086d68b Update activity_main.xml
Update the apps background color for material you.
2023-10-22 20:35:41 -07:00
kingbluezback
9b3b9771e6 Update strings.xml
When editing a playlist the title is "create playlist" instead of "edit playlist"
2023-10-22 20:33:51 -07:00
antonio
a92403bda4 style: sort translation strings 2023-10-14 16:09:19 +02:00
antonio
67905affd3 feat: added podcast description to the right page of the player 2023-10-12 22:57:38 +02:00
antonio
c20cadff94 clean: cleaned up the podcast listening UI 2023-10-12 22:36:48 +02:00
antonio
0cc45b2b12 clean: code cleanup 2023-10-12 22:36:03 +02:00
antonio
b6063f6b20 fix: forced media type during playback 2023-10-12 22:35:34 +02:00
antonio
678c61e569 clean: removed unused properties of podcast episodes 2023-10-12 22:34:48 +02:00
antonio
e88d6d7844 fix: music player now stays closed on startup if there are no songs in the queue 2023-10-12 21:31:46 +02:00
antonio
bae43cbfe9 gradle: dependencies update 2023-10-12 21:11:24 +02:00
antonio
68c77cefff feat: added songBottomSheet to track files within directories 2023-10-11 22:49:26 +02:00
antonio
75980ee18b feat: show download button only if there are music files in the current directory fragment 2023-10-11 22:20:50 +02:00
antonio
ed30198c8d feat: implemented playlist deletion starting from current track 2023-10-11 21:48:04 +02:00
antonio
185a671f82 fix: resolved an issue with podcast cover image display 2023-10-11 20:55:33 +02:00
antonio
42905819a7 Merge remote-tracking branch 'origin/main' 2023-10-07 16:01:40 +02:00
CappielloAntonio
93d6faa392 github: update crash-report.md 2023-10-07 15:52:28 +02:00
CappielloAntonio
64337775d6 Merge pull request #97 from rkowalsk/main
feat: added french localization
2023-10-07 15:47:29 +02:00
Romain Kowalski
d690cf37fb fix: shortened Wifi transcoding string that was overflowing 2023-10-05 16:56:14 +02:00
Romain Kowalski
32c8ef3b25 Add French localization 2023-10-05 16:23:17 +02:00
antonio
607c68967d gradle: dependencies update 2023-09-29 11:11:30 +02:00
antonio
7fb9f63d1f Merge remote-tracking branch 'origin/main' 2023-09-17 22:33:39 +02:00
antonio
a04a2072f7 feat: implemented a second grid layout for album display on the artist page 2023-09-17 22:33:25 +02:00
antonio
72bd71dea2 fix: error handling 2023-09-17 22:04:07 +02:00
CappielloAntonio
f87328f874 Merge pull request #84 from GallowsDove/templates
style: use proper markdown for issue templates
2023-09-17 21:16:54 +02:00
GallowsDove
4d9b71d48e fix: fix indent in templates 2023-09-17 20:02:43 +02:00
GallowsDove
06f8bc771a feat: Use markdown for github templates 2023-09-17 19:59:27 +02:00
antonio
24747207b8 gradle: bump up code version 2023-09-17 19:15:58 +02:00
antonio
e762c91ff3 feat: added setting to request estimated duration of transcoded file 2023-09-17 19:12:45 +02:00
antonio
0fe4636fe1 feat: parameterized track sharing feature 2023-09-17 18:51:22 +02:00
antonio
8ab011781e fix: made expiration date TextInputEditText unselectable 2023-09-17 18:27:16 +02:00
antonio
7f820bd5a6 feat: implemented playlist sharing 2023-09-17 18:22:57 +02:00
antonio
26b8f3f65c feat: implemented album and song sharing 2023-09-17 16:46:21 +02:00
antonio
f172a00fb7 feat: implemented updating shared items through dialog 2023-09-17 16:43:09 +02:00
antonio
077d22167c feat: added bottomSheet for shared items on the home screen 2023-09-17 16:42:07 +02:00
antonio
ebd1582d1c feat: added shared items list on the homepage 2023-09-17 16:40:41 +02:00
antonio
5e486d4794 feat: implemented client, service, and repository for sharing APIs 2023-09-17 16:38:08 +02:00
antonio
537d0c6d8f gradle: bump up code version 2023-09-15 22:13:13 +02:00
antonio
4fc75f5c46 refactor: code cleanup 2023-09-15 22:02:31 +02:00
CappielloAntonio
baf1bc48b0 Merge pull request #82 from GallowsDove/notifications
feat: add notification groups
2023-09-15 21:50:08 +02:00
antonio
f26a123646 style: added icon for indexes 2023-09-15 21:42:24 +02:00
antonio
7160e3f4b9 style: modified placeholder icon background colors and added folder icon 2023-09-15 21:34:44 +02:00
antonio
24b3161a8e style: hide top-listened until server response is confirmed 2023-09-15 21:33:39 +02:00
GallowsDove
383fbd1c49 feat: add notification groups 2023-09-15 13:09:48 +02:00
antonio
c0f5abdfae Merge remote-tracking branch 'origin/main' 2023-09-13 17:27:49 +02:00
antonio
a50e50d797 fix: set no replay gain if array is null or empty 2023-09-13 17:27:43 +02:00
CappielloAntonio
1f9dac9e3a github: add crash issue template 2023-09-13 17:01:51 +02:00
CappielloAntonio
f57ac6d624 github: add feature request issue template 2023-09-13 16:56:09 +02:00
CappielloAntonio
0b96fe5605 github: add bug issue template 2023-09-13 16:51:05 +02:00
antonio
16561be854 fix: null checking 2023-09-13 16:19:50 +02:00
antonio
570ad1c984 gradle: dependencies update 2023-09-13 16:09:02 +02:00
antonio
736bcdb994 style: uniformed button size for "more buttons" to make them consistent and easier to click 2023-09-09 18:43:18 +02:00
antonio
e71472f498 style: pull request refactoring 2023-09-08 15:56:50 +02:00
CappielloAntonio
fb328f26b8 Merge pull request #70 from ivan-avalos/main
feat: improved item placeholders
2023-09-08 15:38:17 +02:00
antonio
c26aba8b2d style: pull request refactoring 2023-09-08 15:30:05 +02:00
antonio
a17de1de8d fix: null checking 2023-09-08 14:41:32 +02:00
antonio
7ea159cb10 Merge remote-tracking branch 'origin/main' 2023-09-08 14:20:15 +02:00
CappielloAntonio
8ed98bbedc Merge pull request #61 from GallowsDove/main
feat: add bottom sheet for grouped views in download tab
2023-09-08 14:17:49 +02:00
antonio
43a1b5b93a fix: null checking 2023-09-08 14:12:35 +02:00
antonio
977754f02c clean: code cleanup 2023-09-08 10:54:43 +02:00
antonio
10aae5fa15 test: implemented and temporarily set aside the implementation of a customLoadController 2023-09-08 10:52:21 +02:00
antonio
dee60e5e8f fix: fixed application logic of Replay Gain 2023-09-08 10:51:09 +02:00
Iván Ávalos
0c05b77849 feat: improved item placeholders 2023-09-07 19:48:03 -06:00
GallowsDove
223954e9ca Merge branch 'main' into main 2023-09-07 10:39:03 +02:00
antonio
fdc8c50d25 clean: code cleanup 2023-09-06 22:56:54 +02:00
antonio
aa7872d030 fix: fixed lag when opening AlbumBottomSheet caused by disappearing download removal button 2023-09-06 22:33:20 +02:00
antonio
9ed7744421 fix: updated download section navigation to match the system's correct stack-based navigation 2023-09-06 21:49:23 +02:00
antonio
c19db362d9 gradle: bump up code version 2023-09-06 18:31:04 +02:00
antonio
6a505eea4e feat: implemented a dialog box for displaying details of the currently playing track 2023-09-06 18:28:32 +02:00
antonio
99be2764d0 feat: added the ability to shuffle the playback queue 2023-09-06 15:03:28 +02:00
antonio
b656ad9e7f feat: increased visibility of gestures within the music player 2023-09-06 15:02:32 +02:00
GallowsDove
75e5756d7e fix: Fix performance issues with DownloadedBottomSheet with large number of grouped entries 2023-09-06 00:53:59 +02:00
antonio
13d8bbf877 feat: made tap on the track cover more prominent to discover quick action commands 2023-09-05 21:33:31 +02:00
GallowsDove
f43d7fb394 feat: Add bottom sheet for grouped views in Download tab 2023-09-05 18:54:48 +02:00
antonio
68527b2c91 style: adjusted padding of elements for improved consistency 2023-08-31 16:21:36 +02:00
antonio
347c074368 style: fixed visibility state of playback icons 2023-08-31 16:20:55 +02:00
antonio
2a74f51f8d fix: corrected folder navigation, now displaying correct subfolders 2023-08-31 16:20:05 +02:00
antonio
1a75369591 fix: null checking 2023-08-31 15:58:00 +02:00
antonio
8f6e775ca9 gradle: bump up code version 2023-08-30 17:53:44 +02:00
antonio
bad0fa6c23 fix: fixed a bug in the paginated song lists 2023-08-30 17:52:44 +02:00
antonio
d4caa6f209 build: dependencies update 2023-08-29 08:03:47 +02:00
antonio
723bdf9771 clean: code cleanup 2023-08-28 12:26:04 +02:00
antonio
49fbd85bb4 clean: code cleanup 2023-08-28 12:25:36 +02:00
CappielloAntonio
f040fbf0cf Merge pull request #50 from GallowsDove/main
feat: show albums instead of the artist in artist's top songs
2023-08-28 12:17:54 +02:00
antonio
efb2213ab7 fix: files without embedded cover art now display the image in the music player notification 2023-08-28 12:15:40 +02:00
GallowsDove
742ac6b17d feat: show albums instead of the artist in artist's top songs. 2023-08-28 11:28:59 +02:00
antonio
ae7761cb96 fix: null checking 2023-08-28 10:51:16 +02:00
antonio
c977982d64 gradle: bump up code version 2023-08-25 15:38:05 +02:00
antonio
28fc3dca36 fix: set initial visibility of the grid to "Gone" and make it appear once the elements are successfully loaded 2023-08-25 15:36:55 +02:00
antonio
f1cf65a371 fix: redefined proguard rules to make Retrofit work for everyone 2023-08-25 15:28:47 +02:00
antonio
beb1d29e8f fix: delayed grid display until server connectivity is confirmed 2023-08-25 15:27:39 +02:00
antonio
1eda7cef9e fix: fix a crash when clicking the dot menu of an empty playlist 2023-08-25 12:32:24 +02:00
antonio
1af92ad949 fix: now refreshing playlist view every time library is visited 2023-08-25 12:23:26 +02:00
antonio
3fc9b35fe4 fix: a callback on playlist editor dialog closing tells me when to refresh the playlist view 2023-08-25 12:22:43 +02:00
antonio
56b48dbd4d fix: fixed race condition issue in playlist update 2023-08-25 12:20:49 +02:00
antonio
1cb371dc5a gradle: dependencies update 2023-08-25 08:50:52 +02:00
antonio
499001a269 fix: podcast playback issue resolved 2023-08-24 10:04:12 +02:00
antonio
4c9e47379d style: code cleanup 2023-08-22 14:35:21 +02:00
CappielloAntonio
a0dbb5c81f clean: readme cleanup 2023-08-22 14:34:49 +02:00
CappielloAntonio
dab53c6bbf Merge pull request #42 from dnno/fix/null-pointer-genre-scrolling
fix: check if children exist before adding
2023-08-22 14:30:35 +02:00
antonio
c0a665c00a fix: fix click on mediaitem 2023-08-22 14:22:20 +02:00
Reinhard Prechtl
41b5c57240 Check if children exist before adding 2023-08-22 13:09:30 +02:00
antonio
1d65a79c20 Fix: manually collapse bottomSheet on device state change 2023-08-21 16:40:01 +02:00
antonio
efb6e72636 Merge remote-tracking branch 'origin/main' 2023-08-21 16:15:26 +02:00
antonio
af83ffd608 fix: manually encoded the username when creating the uri for streaming and fetching coverArt 2023-08-21 16:15:08 +02:00
antonio
0201077bc4 gradle: dependencies update 2023-08-21 15:49:26 +02:00
CappielloAntonio
91d91d3024 Merge pull request #39 from dnno/main
Add new localized strings for german language
2023-08-20 15:25:54 +02:00
Reinhard Prechtl
9784a2b6c5 Add new localized strings for german language 2023-08-20 14:24:55 +02:00
antonio
87a3301912 feat: implemented hard deletion of downloaded files upon storage change or explicit user request 2023-08-19 12:33:39 +02:00
antonio
295795edc9 clean: AndroidManifest cleaning 2023-08-19 11:57:05 +02:00
antonio
6120ab66ba style: implemented string conversion method to PascalCase 2023-08-17 14:09:56 +02:00
antonio
7bea180c58 feat: implemented language picker in app settings, dynamically populating the list of available languages programmatically. 2023-08-17 14:09:17 +02:00
antonio
a29cee488e build: started implementing language picker via appcompat instead of automatically generating language configurations to maintain compatibility with Android versions below 13 2023-08-17 14:07:56 +02:00
antonio
74b4b04693 feat: enabled automatic per-app language support 2023-08-16 23:54:47 +02:00
antonio
b8b9c80bdc fix: null checking 2023-08-16 23:48:14 +02:00
antonio
a50fc74117 build: update workflow 2023-08-16 23:31:33 +02:00
antonio
c1af438a3a build: update workflow 2023-08-16 23:27:55 +02:00
antonio
80b251cddc gradle: bump up gradle version 2023-08-16 22:53:02 +02:00
antonio
7d9a48818e gradle: installing jetbrains runtime 17.0.8 2023-08-16 22:47:54 +02:00
antonio
ca3da0839b Merge remote-tracking branch 'origin/main' 2023-08-16 22:42:37 +02:00
CappielloAntonio
cf463d8fa1 Update github_release.yml 2023-08-16 22:41:44 +02:00
antonio
635fdc4c5c gradle: started working on Gradle upgrade, for testing purposes 2023-08-16 22:33:52 +02:00
antonio
3e913931c4 gradle: bump up code version 2023-08-15 19:16:32 +02:00
antonio
46420da038 chore: uploaded Android Room migration file 2023-08-15 19:08:10 +02:00
antonio
d5b7619dd1 style: aligned surface colors with recommended Material You colors 2023-08-15 19:06:31 +02:00
antonio
89b39123da fix: updated status bar and music player background colors 2023-08-14 19:24:38 +02:00
antonio
5a43137984 fix: fixed issue with dynamic color not being correctly applied 2023-08-14 19:02:26 +02:00
CappielloAntonio
761b07450f Merge pull request #36 from ThePBone/fix-filter
fix: filter array of songs in setItems (DownloadHorizontalAdapter)
2023-08-14 18:10:49 +02:00
Tim Schneeberger
a0b67a06f4 refactor: put grouped into bundle instead of songs 2023-08-14 18:08:11 +02:00
Tim Schneeberger
59d7adb66d fix: filter array of songs in setItems() 2023-08-14 17:43:33 +02:00
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
antonio
259c8b3599 refactor: added search2 method as per Subsonic API specifications 2023-07-12 09:52:57 +02:00
antonio
ba1295440b feat: added ReplayGain functionality in main flavor 2023-07-12 09:27:58 +02:00
antonio
9e6178037c feat: added ReplayGain functionality in main flavor 2023-07-03 09:27:35 +02:00
antonio
47302815c1 clean: code cleanup 2023-07-03 09:26:31 +02:00
antonio
c81bfe1457 fix: deleted an irrelevant minus sign 2023-07-03 09:24:20 +02:00
antonio
f413b5d498 feat: added initial version ReplayGain functionality, still in development 2023-07-02 23:38:33 +02:00
antonio
feae1b8bdd feat: added settings to enable and disable ReplayGain 2023-07-02 23:36:52 +02:00
antonio
57bb51fb8d feat: version number is now taken dynamically from the VersionName parameter 2023-07-02 19:59:03 +02:00
antonio
0f1364502a fix: modified list scrolling behavior 2023-07-02 19:55:05 +02:00
antonio
a508ec0af6 feat: make music directory section optional 2023-07-02 19:36:39 +02:00
antonio
a9b264004c fix: fixed directory navigation 2023-07-02 19:23:08 +02:00
antonio
803b61430f build: removed unused dependencies 2023-07-02 12:32:08 +02:00
antonio
25367512c0 build: removed unused dependency 2023-07-02 12:13:50 +02:00
antonio
e3720f4a50 build: disabled jetifier 2023-07-02 12:11:36 +02:00
antonio
1168cc58f4 clean: removed unused repositories 2023-07-02 12:09:37 +02:00
antonio
c711b387bb refactor: extracted common toolbar to better handle two flavors and minimized file duplication 2023-07-01 18:11:44 +02:00
antonio
a3fe0de233 feat: initial groundwork for creating the flavor 2023-07-01 11:37:36 +02:00
antonio
922153837d fix: tap the player bottom sheet header to bring player up 2023-06-30 18:34:33 +02:00
antonio
960dd6c76a fix: shuffle all action 2023-06-30 18:23:12 +02:00
antonio
fcb7135c3d fix: changed the translation label 2023-06-25 22:12:45 +02:00
antonio
3102bff729 gradle: bump up code version 2023-06-24 15:29:40 +02:00
antonio
992b16cc1d clean: explain user why to disable manufacturer optimizations and troubleshooting steps 2023-06-24 15:28:02 +02:00
antonio
2046872d79 gradle: bump up code version 2023-06-24 15:02:52 +02:00
antonio
7312ae20d1 fix: improved error handling during scanning phase 2023-06-24 15:02:13 +02:00
antonio
e8f2385928 gradle: bump up code version 2023-06-24 13:40:55 +02:00
antonio
204f9ff4c7 fix: addConverterFactory to retrofit client 2023-06-24 13:40:06 +02:00
antonio
17e64f3dd0 fix: cull checking 2023-06-24 13:38:45 +02:00
antonio
d09e7e7981 fix: collapse player bottom sheet on logout 2023-06-24 13:38:21 +02:00
antonio
95e9d06e47 gradle: bump up code version 2023-06-24 12:13:02 +02:00
antonio
f147c762d5 fix: reduced the frequency of the optimization prompt dialog and made it possible to disable it 2023-06-24 12:12:14 +02:00
antonio
200ce6555b Gradle: removed old search bar implementation and updated code version in preparation for the tag 2023-06-24 11:17:23 +02:00
antonio
b5e5537691 feat: Removed limit of 5 search suggestions 2023-06-24 11:16:05 +02:00
antonio
3b80725673 feat: replace old searchbar implementation with official material3 2023-06-24 11:15:04 +02:00
antonio
d7c5be834e Merge remote-tracking branch 'origin/main' 2023-06-23 18:28:31 +02:00
antonio
4a5672fc77 build: bump up code version 2023-06-23 18:28:16 +02:00
antonio
34fa2f456e fix: resolved potential issues related to missing or invalid prefixes 2023-06-23 18:23:44 +02:00
antonio
663285611b build: dependencies update 2023-06-23 09:29:10 +02:00
CappielloAntonio
a62cc78e18 Update README.md 2023-06-21 14:50:22 +02:00
291 changed files with 20040 additions and 1802 deletions

35
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,35 @@
---
name: Bug report
about: Create a report to help us improve
title: "[BUG] - "
labels: ''
assignees: ''
---
## Describe the bug
<!-- A clear and concise description of what the bug is. -->
## Expected behavior
<!-- A clear and concise description of what you expected to happen. -->
## To Reproduce
<!--
Outline the steps required to reproduce the bug, including any specific actions, inputs, or conditions:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
-->
## Environment
- Android device: [Device Model]
- Android OS version: [Android Version]
- App version: [App Version]
- Other relevant details: [e.g., specific network conditions, external dependencies]
## Logs or Screenshots
<!-- If applicable, add screenshots to help explain your problem. -->
## Additional context
<!-- Add any other context about the problem here. -->

41
.github/ISSUE_TEMPLATE/crash-report.md vendored Normal file
View File

@@ -0,0 +1,41 @@
---
name: Crash report
about: Tell us how to replicate the crash
title: "[CRASH] - "
labels: ''
assignees: ''
---
## Description
<!-- Provide a clear and concise description of the crash you encountered. -->
## Steps to Reproduce
<!--
Please provide the steps to reproduce the crash:
1. Go to '...'
2. Click on '...'
3. Scroll down to '...'
4. See the crash
-->
## Environment
- Android device: [Device Model]
- Android OS version: [Android Version]
- App version: [App Version]
- Other relevant details: [e.g., specific network conditions, external dependencies]
## Crash Logs/Stack trace
<!-- If available, please provide the crash log or stack trace related to the crash. Include it inside a code block (surround with triple backticks ```). Please use the unsigned apk (app-tempo-debug.apk), as the logs would be illegible and therefore useless for this purpose. -->
## Screenshots
<!-- If applicable, add screenshots to help explain the problem. -->
## Additional Context
<!-- Add any other context about the problem here. -->
## Reproducibility
<!-- Mention the frequency of the crash occurrence (e.g., always, sometimes, occasionally). -->
## Additional Notes
<!-- Include any other notes or details that could be helpful for troubleshooting the crash. -->

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]+'
jobs:
setup:
runs-on: ubuntu-latest
steps:
- name: Setup JDK 8
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: 8
build:
needs: setup
runs-on: ubuntu-latest
steps:
- name: Setup JDK 17
uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: '17'
- uses: actions/checkout@v3
- name: Cache Gradle and wrapper
@@ -34,13 +30,13 @@ jobs:
- name: Build APK
id: build
run: bash ./gradlew assembleRelease
run: bash ./gradlew assembleTempoRelease
- name: Sign APK
id: sign_apk
uses: r0adkll/sign-android-release@v1
with:
releaseDirectory: app/build/outputs/apk/release
releaseDirectory: app/build/outputs/apk/tempo/release
signingKeyBase64: ${{ secrets.KEYSTORE_BASE64 }}
alias: ${{ secrets.KEY_ALIAS_GITHUB }}
keyStorePassword: ${{ secrets.KEYSTORE_PASSWORD }}
@@ -98,7 +94,7 @@ jobs:
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ${{steps.sign_apk.outputs.signedReleaseFile}}
asset_name: app-release.apk
asset_name: app-tempo-release.apk
asset_content_type: application/zip
# - name: Upload AAB

2
.idea/gradle.xml generated
View File

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

2
.idea/misc.xml generated
View File

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

View File

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

View File

@@ -3,15 +3,12 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlin-parcelize'
android {
compileSdkVersion 34
buildToolsVersion '33.0.0'
compileSdk = 34
buildToolsVersion = "34.0.0"
defaultConfig {
applicationId 'com.cappielloantonio.tempo'
minSdkVersion 24
targetSdkVersion 34
versionCode 5
versionName '3.3.0'
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
@@ -25,6 +22,24 @@ android {
}
}
flavorDimensions += "default"
productFlavors {
tempo {
dimension = "default"
applicationId 'com.cappielloantonio.tempo'
versionCode 23
versionName '3.6.0'
}
notquitemy {
dimension = "default"
applicationId "com.cappielloantonio.notquitemy.tempo"
versionCode 1
versionName "1.0.0"
}
}
buildTypes {
release {
shrinkResources true
@@ -54,51 +69,35 @@ dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
// AndroidX
implementation 'androidx.core:core-ktx:1.10.1'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.2.0'
implementation 'androidx.preference:preference-ktx:1.2.0'
implementation 'androidx.navigation:navigation-fragment-ktx:2.6.0'
implementation 'androidx.navigation:navigation-ui-ktx:2.6.0'
implementation 'androidx.recyclerview:recyclerview:1.3.0'
implementation 'androidx.room:room-runtime:2.5.1'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.preference:preference-ktx:1.2.1'
implementation 'androidx.navigation:navigation-fragment-ktx:2.7.6'
implementation 'androidx.navigation:navigation-ui-ktx:2.7.6'
implementation 'androidx.recyclerview:recyclerview:1.3.2'
implementation 'androidx.room:room-runtime:2.6.1'
implementation 'androidx.core:core-splashscreen:1.0.1'
// Google GMS
implementation 'com.google.android.gms:play-services-cast-framework:21.3.0'
implementation "androidx.appcompat:appcompat:1.6.1"
// Android Material
implementation 'com.google.android.material:material:1.9.0'
// SearchBar
implementation 'com.paulrybitskyi.persistentsearchview:persistentsearchview:1.1.4'
implementation 'com.arthurivanets.adapster:adapster:1.0.13'
implementation 'com.google.android.material:material:1.11.0'
// Glide
implementation 'com.github.bumptech.glide:glide:4.15.1'
implementation 'com.github.bumptech.glide:annotations:4.15.1'
implementation 'com.github.bumptech.glide:glide:4.16.0'
implementation 'com.github.bumptech.glide:annotations:4.16.0'
// Media3
implementation 'androidx.media3:media3-session:1.0.2'
implementation 'androidx.media3:media3-common:1.0.2'
implementation 'androidx.media3:media3-exoplayer:1.0.2'
implementation 'androidx.media3:media3-ui:1.0.2'
implementation 'androidx.media3:media3-cast:1.0.2'
implementation 'androidx.media3:media3-session:1.2.0'
implementation 'androidx.media3:media3-common:1.2.0'
implementation 'androidx.media3:media3-exoplayer:1.2.0'
implementation 'androidx.media3:media3-ui:1.2.0'
tempoImplementation 'androidx.media3:media3-cast:1.2.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.15.1'
annotationProcessor 'androidx.room:room-compiler:2.5.1'
annotationProcessor 'com.github.bumptech.glide:compiler:4.16.0'
annotationProcessor 'androidx.room:room-compiler:2.6.1'
// Retrofit
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.6'
implementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.12'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
// Crash Report
// debugImplementation 'com.balsikandar.android:crashreporter:1.1.0'
// DB debug
// debugImplementation 'com.amitshekhar.android:debug-db:1.0.6'
}

View File

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

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

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@@ -1,5 +1,6 @@
package com.cappielloantonio.tempo.database;
import androidx.room.AutoMigration;
import androidx.room.Database;
import androidx.room.Room;
import androidx.room.RoomDatabase;
@@ -9,19 +10,23 @@ import com.cappielloantonio.tempo.App;
import com.cappielloantonio.tempo.database.converter.DateConverters;
import com.cappielloantonio.tempo.database.dao.ChronologyDao;
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.RecentSearchDao;
import com.cappielloantonio.tempo.database.dao.ServerDao;
import com.cappielloantonio.tempo.database.dao.SessionMediaItemDao;
import com.cappielloantonio.tempo.model.Chronology;
import com.cappielloantonio.tempo.model.Download;
import com.cappielloantonio.tempo.model.Favorite;
import com.cappielloantonio.tempo.model.Queue;
import com.cappielloantonio.tempo.model.RecentSearch;
import com.cappielloantonio.tempo.model.Server;
import com.cappielloantonio.tempo.model.SessionMediaItem;
@Database(
version = 1,
entities = {Queue.class, Server.class, RecentSearch.class, Download.class, Chronology.class}
// autoMigrations = {@AutoMigration(from = 61, to = 62)}
version = 8,
entities = {Queue.class, Server.class, RecentSearch.class, Download.class, Chronology.class, Favorite.class, SessionMediaItem.class},
autoMigrations = {@AutoMigration(from = 7, to = 8)}
)
@TypeConverters({DateConverters.class})
public abstract class AppDatabase extends RoomDatabase {
@@ -47,4 +52,8 @@ public abstract class AppDatabase extends RoomDatabase {
public abstract DownloadDao downloadDao();
public abstract ChronologyDao chronologyDao();
public abstract FavoriteDao favoriteDao();
public abstract SessionMediaItemDao sessionMediaItemDao();
}

View File

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

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,8 +12,8 @@ import java.util.List;
@Dao
public interface RecentSearchDao {
@Query("SELECT * FROM recent_search GROUP BY search ORDER BY search DESC LIMIT :limit")
List<String> getRecent(int limit);
@Query("SELECT * FROM recent_search ORDER BY search DESC")
List<String> getRecent();
@Insert(onConflict = OnConflictStrategy.REPLACE)
void insert(RecentSearch search);

View File

@@ -0,0 +1,29 @@
package com.cappielloantonio.tempo.database.dao;
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.OnConflictStrategy;
import androidx.room.Query;
import com.cappielloantonio.tempo.model.Queue;
import com.cappielloantonio.tempo.model.SessionMediaItem;
import java.util.List;
@Dao
public interface SessionMediaItemDao {
@Query("SELECT * FROM session_media_item WHERE id = :id")
SessionMediaItem get(String id);
@Query("SELECT * FROM session_media_item WHERE timestamp = :timestamp")
List<SessionMediaItem> get(long timestamp);
@Insert(onConflict = OnConflictStrategy.IGNORE)
void insert(SessionMediaItem sessionMediaItem);
@Insert(onConflict = OnConflictStrategy.IGNORE)
void insertAll(List<SessionMediaItem> sessionMediaItems);
@Query("DELETE FROM session_media_item")
void deleteAll();
}

View File

@@ -5,6 +5,9 @@ import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.util.Log;
import androidx.annotation.Nullable;
import androidx.appcompat.content.res.AppCompatResources;
import com.bumptech.glide.Glide;
import com.bumptech.glide.RequestBuilder;
import com.bumptech.glide.RequestManager;
@@ -15,7 +18,9 @@ import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions;
import com.bumptech.glide.request.RequestOptions;
import com.bumptech.glide.signature.ObjectKey;
import com.cappielloantonio.tempo.App;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.util.Preferences;
import com.cappielloantonio.tempo.util.Util;
import com.google.android.material.elevation.SurfaceColors;
import java.util.Map;
@@ -27,16 +32,53 @@ public class CustomGlideRequest {
public static final DiskCacheStrategy DEFAULT_DISK_CACHE_STRATEGY = DiskCacheStrategy.ALL;
public static RequestOptions createRequestOptions(Context context, String item) {
public enum ResourceType {
Unknown,
Album,
Artist,
Folder,
Directory,
Playlist,
Podcast,
Radio,
Song,
}
public static RequestOptions createRequestOptions(Context context, String item, ResourceType type) {
return new RequestOptions()
.placeholder(new ColorDrawable(SurfaceColors.SURFACE_5.getColor(context)))
.fallback(new ColorDrawable(SurfaceColors.SURFACE_5.getColor(context)))
.error(new ColorDrawable(SurfaceColors.SURFACE_5.getColor(context)))
.fallback(getPlaceholder(context, type))
.error(getPlaceholder(context, type))
.diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY)
.signature(new ObjectKey(item != null ? item : 0))
.transform(new CenterCrop(), new RoundedCorners(CustomGlideRequest.CORNER_RADIUS));
}
@Nullable
private static Drawable getPlaceholder(Context context, ResourceType type) {
switch (type) {
case Album:
return AppCompatResources.getDrawable(context, R.drawable.ic_placeholder_album);
case Artist:
return AppCompatResources.getDrawable(context, R.drawable.ic_placeholder_artist);
case Folder:
return AppCompatResources.getDrawable(context, R.drawable.ic_placeholder_folder);
case Directory:
return AppCompatResources.getDrawable(context, R.drawable.ic_placeholder_directory);
case Playlist:
return AppCompatResources.getDrawable(context, R.drawable.ic_placeholder_playlist);
case Podcast:
return AppCompatResources.getDrawable(context, R.drawable.ic_placeholder_podcast);
case Radio:
return AppCompatResources.getDrawable(context, R.drawable.ic_placeholder_radio);
case Song:
return AppCompatResources.getDrawable(context, R.drawable.ic_placeholder_song);
default:
case Unknown:
return new ColorDrawable(SurfaceColors.SURFACE_5.getColor(context));
}
}
public static String createUrl(String item, int size) {
Map<String, String> params = App.getSubsonicClientInstance(false).getParams();
@@ -46,7 +88,7 @@ public class CustomGlideRequest {
uri.append("getCoverArt");
if (params.containsKey("u") && params.get("u") != null)
uri.append("?u=").append(params.get("u"));
uri.append("?u=").append(Util.encode(params.get("u")));
if (params.containsKey("p") && params.get("p") != null)
uri.append("&p=").append(params.get("p"));
if (params.containsKey("s") && params.get("s") != null)
@@ -71,18 +113,18 @@ public class CustomGlideRequest {
private final RequestManager requestManager;
private Object item;
private Builder(Context context, String item) {
private Builder(Context context, String item, ResourceType type) {
this.requestManager = Glide.with(context);
if (item != null && !Preferences.isDataSavingMode()) {
this.item = createUrl(item, Preferences.getImageSize());
}
requestManager.applyDefaultRequestOptions(createRequestOptions(context, item));
requestManager.applyDefaultRequestOptions(createRequestOptions(context, item, type));
}
public static Builder from(Context context, String item) {
return new Builder(context, item);
public static Builder from(Context context, String item, ResourceType type) {
return new Builder(context, item, type);
}
public RequestBuilder<Drawable> build() {

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 onServerLongClick(Bundle bundle) {}
default void onPodcastEpisodeClick(Bundle bundle) {}
default void onPodcastEpisodeAltClick(Bundle bundle) {}
default void onPodcastEpisodeLongClick(Bundle bundle) {}
default void onPodcastChannelClick(Bundle bundle) {}
default void onPodcastChannelLongClick(Bundle bundle) {}
@@ -28,4 +29,7 @@ public interface ClickCallback {
default void onMusicFolderClick(Bundle bundle) {}
default void onMusicDirectoryClick(Bundle bundle) {}
default void onMusicIndexClick(Bundle bundle) {}
default void onDownloadGroupLongClick(Bundle bundle) {}
default void onShareClick(Bundle bundle) {}
default void onShareLongClick(Bundle bundle) {}
}

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")
var downloadState: Int = 0
@ColumnInfo(name = "download_uri", defaultValue = "")
var downloadUri: String? = null
constructor(child: Child) : this(child.id) {
parentId = child.parentId
isDir = child.isDir
@@ -52,4 +55,10 @@ class Download(@PrimaryKey override val id: String) : Child(id) {
originalWidth = child.originalWidth
originalHeight = child.originalHeight
}
}
}
@Keep
data class DownloadStack(
var id: String,
var view: String?
)

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

@@ -0,0 +1,9 @@
package com.cappielloantonio.tempo.model
import androidx.annotation.Keep
@Keep
data class ReplayGain(
var trackGain: Float = 0f,
var albumGain: Float = 0f,
)

View File

@@ -0,0 +1,281 @@
package com.cappielloantonio.tempo.model
import android.net.Uri
import android.os.Bundle
import androidx.annotation.Keep
import androidx.media3.common.MediaItem
import androidx.media3.common.MediaItem.RequestMetadata
import androidx.media3.common.MediaMetadata
import androidx.media3.common.MimeTypes
import androidx.media3.common.util.UnstableApi
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import com.cappielloantonio.tempo.glide.CustomGlideRequest
import com.cappielloantonio.tempo.subsonic.models.Child
import com.cappielloantonio.tempo.subsonic.models.InternetRadioStation
import com.cappielloantonio.tempo.subsonic.models.PodcastEpisode
import com.cappielloantonio.tempo.util.Constants
import com.cappielloantonio.tempo.util.MusicUtil
import com.cappielloantonio.tempo.util.Preferences.getImageSize
import java.util.Date
@UnstableApi
@Keep
@Entity(tableName = "session_media_item")
class SessionMediaItem() {
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "index")
var index: Int = 0
@ColumnInfo(name = "id")
var id: String? = null
@ColumnInfo(name = "parent_id")
var parentId: String? = null
@ColumnInfo(name = "is_dir")
var isDir: Boolean = false
@ColumnInfo
var title: String? = null
@ColumnInfo
var album: String? = null
@ColumnInfo
var artist: String? = null
@ColumnInfo
var track: Int? = null
@ColumnInfo
var year: Int? = null
@ColumnInfo
var genre: String? = null
@ColumnInfo(name = "cover_art_id")
var coverArtId: String? = null
@ColumnInfo
var size: Long? = null
@ColumnInfo(name = "content_type")
var contentType: String? = null
@ColumnInfo
var suffix: String? = null
@ColumnInfo("transcoding_content_type")
var transcodedContentType: String? = null
@ColumnInfo(name = "transcoded_suffix")
var transcodedSuffix: String? = null
@ColumnInfo
var duration: Int? = null
@ColumnInfo("bitrate")
var bitrate: Int? = null
@ColumnInfo
var path: String? = null
@ColumnInfo(name = "is_video")
var isVideo: Boolean = false
@ColumnInfo(name = "user_rating")
var userRating: Int? = null
@ColumnInfo(name = "average_rating")
var averageRating: Double? = null
@ColumnInfo(name = "play_count")
var playCount: Long? = null
@ColumnInfo(name = "disc_number")
var discNumber: Int? = null
@ColumnInfo
var created: Date? = null
@ColumnInfo
var starred: Date? = null
@ColumnInfo(name = "album_id")
var albumId: String? = null
@ColumnInfo(name = "artist_id")
var artistId: String? = null
@ColumnInfo
var type: String? = null
@ColumnInfo(name = "bookmark_position")
var bookmarkPosition: Long? = null
@ColumnInfo(name = "original_width")
var originalWidth: Int? = null
@ColumnInfo(name = "original_height")
var originalHeight: Int? = null
@ColumnInfo(name = "stream_id")
var streamId: String? = null
@ColumnInfo(name = "stream_url")
var streamUrl: String? = null
@ColumnInfo(name = "timestamp")
var timestamp: Long? = null
constructor(child: Child) : this() {
id = child.id
parentId = child.parentId
isDir = child.isDir
title = child.title
album = child.album
artist = child.artist
track = child.track
year = child.year
genre = child.genre
coverArtId = child.coverArtId
size = child.size
contentType = child.contentType
suffix = child.suffix
transcodedContentType = child.transcodedContentType
transcodedSuffix = child.transcodedSuffix
duration = child.duration
bitrate = child.bitrate
path = child.path
isVideo = child.isVideo
userRating = child.userRating
averageRating = child.averageRating
playCount = child.playCount
discNumber = child.discNumber
created = child.created
starred = child.starred
albumId = child.albumId
artistId = child.artistId
type = Constants.MEDIA_TYPE_MUSIC
bookmarkPosition = child.bookmarkPosition
originalWidth = child.originalWidth
originalHeight = child.originalHeight
}
constructor(podcastEpisode: PodcastEpisode) : this() {
id = podcastEpisode.id
parentId = podcastEpisode.parentId
isDir = podcastEpisode.isDir
title = podcastEpisode.title
album = podcastEpisode.album
artist = podcastEpisode.artist
year = podcastEpisode.year
genre = podcastEpisode.genre
coverArtId = podcastEpisode.coverArtId
size = podcastEpisode.size
contentType = podcastEpisode.contentType
suffix = podcastEpisode.suffix
duration = podcastEpisode.duration
bitrate = podcastEpisode.bitrate
path = podcastEpisode.path
isVideo = podcastEpisode.isVideo
created = podcastEpisode.created
artistId = podcastEpisode.artistId
streamId = podcastEpisode.streamId
type = Constants.MEDIA_TYPE_PODCAST
}
constructor(internetRadioStation: InternetRadioStation) : this() {
id = internetRadioStation.id
title = internetRadioStation.name
streamUrl = internetRadioStation.streamUrl
type = Constants.MEDIA_TYPE_RADIO
}
fun getMediaItem(): MediaItem {
val uri: Uri = getStreamUri()
val artworkUri = Uri.parse(CustomGlideRequest.createUrl(coverArtId, getImageSize()))
val bundle = Bundle()
bundle.putString("id", id)
bundle.putString("parentId", parentId)
bundle.putBoolean("isDir", isDir)
bundle.putString("title", title)
bundle.putString("album", album)
bundle.putString("artist", artist)
bundle.putInt("track", track ?: 0)
bundle.putInt("year", year ?: 0)
bundle.putString("genre", genre)
bundle.putString("coverArtId", coverArtId)
bundle.putLong("size", size ?: 0)
bundle.putString("contentType", contentType)
bundle.putString("suffix", suffix)
bundle.putString("transcodedContentType", transcodedContentType)
bundle.putString("transcodedSuffix", transcodedSuffix)
bundle.putInt("duration", duration ?: 0)
bundle.putInt("bitrate", bitrate ?: 0)
bundle.putString("path", path)
bundle.putBoolean("isVideo", isVideo)
bundle.putInt("userRating", userRating ?: 0)
bundle.putDouble("averageRating", averageRating ?: .0)
bundle.putLong("playCount", playCount ?: 0)
bundle.putInt("discNumber", discNumber ?: 0)
bundle.putLong("created", created?.time ?: 0)
bundle.putLong("starred", starred?.time ?: 0)
bundle.putString("albumId", albumId)
bundle.putString("artistId", artistId)
bundle.putString("type", Constants.MEDIA_TYPE_MUSIC)
bundle.putLong("bookmarkPosition", bookmarkPosition ?: 0)
bundle.putInt("originalWidth", originalWidth ?: 0)
bundle.putInt("originalHeight", originalHeight ?: 0)
bundle.putString("uri", uri.toString())
return MediaItem.Builder()
.setMediaId(id!!)
.setMediaMetadata(
MediaMetadata.Builder()
.setTitle(MusicUtil.getReadableString(title))
.setTrackNumber(track ?: 0)
.setDiscNumber(discNumber ?: 0)
.setReleaseYear(year ?: 0)
.setAlbumTitle(MusicUtil.getReadableString(album))
.setArtist(MusicUtil.getReadableString(artist))
.setArtworkUri(artworkUri)
.setExtras(bundle)
.setIsBrowsable(false)
.setIsPlayable(true)
.build()
)
.setRequestMetadata(
RequestMetadata.Builder()
.setMediaUri(uri)
.setExtras(bundle)
.build()
)
.setMimeType(MimeTypes.BASE_TYPE_AUDIO)
.setUri(uri)
.build()
}
private fun getStreamUri(): Uri {
return when (type) {
Constants.MEDIA_TYPE_MUSIC -> {
MusicUtil.getStreamUri(id)
}
Constants.MEDIA_TYPE_PODCAST -> {
MusicUtil.getStreamUri(streamId)
}
Constants.MEDIA_TYPE_RADIO -> {
Uri.parse(streamUrl)
}
else -> {
MusicUtil.getStreamUri(id)
}
}
}
}

View File

@@ -21,8 +21,6 @@ import retrofit2.Callback;
import retrofit2.Response;
public class AlbumRepository {
private static final String TAG = "AlbumRepository";
public MutableLiveData<List<AlbumID3>> getAlbums(String type, int size, Integer fromYear, Integer toYear) {
MutableLiveData<List<AlbumID3>> listLiveAlbums = new MutableLiveData<>(new ArrayList<>());
@@ -78,40 +76,6 @@ public class AlbumRepository {
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) {
App.getSubsonicClientInstance(false)
.getMediaAnnotationClient()
@@ -141,7 +105,9 @@ public class AlbumRepository {
List<Child> tracks = new ArrayList<>();
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getAlbum() != null) {
tracks.addAll(response.body().getSubsonicResponse().getAlbum().getSongs());
if (response.body().getSubsonicResponse().getAlbum().getSongs() != null) {
tracks.addAll(response.body().getSubsonicResponse().getAlbum().getSongs());
}
}
albumTracks.setValue(tracks);
@@ -283,7 +249,7 @@ public class AlbumRepository {
.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getAlbumList2() != null) {
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getAlbumList2() != null && response.body().getSubsonicResponse().getAlbumList2().getAlbums() != null) {
if (response.body().getSubsonicResponse().getAlbumList2().getAlbums().size() > 0 && !response.body().getSubsonicResponse().getAlbumList2().getAlbums().isEmpty()) {
callback.onLoadYear(response.body().getSubsonicResponse().getAlbumList2().getAlbums().get(0).getYear());
} else {

View File

@@ -63,8 +63,12 @@ public class ArtistRepository {
if (response.isSuccessful() && response.body() != null) {
List<ArtistID3> artists = new ArrayList<>();
for (IndexID3 index : response.body().getSubsonicResponse().getArtists().getIndices()) {
artists.addAll(index.getArtists());
if(response.body().getSubsonicResponse().getArtists() != null && response.body().getSubsonicResponse().getArtists().getIndices() != null) {
for (IndexID3 index : response.body().getSubsonicResponse().getArtists().getIndices()) {
if(index != null && index.getArtists() != null) {
artists.addAll(index.getArtists());
}
}
}
if (random) {
@@ -135,9 +139,6 @@ public class ArtistRepository {
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) {
MutableLiveData<ArtistInfo2> artistFullInfo = new MutableLiveData<>(null);
@@ -161,40 +162,6 @@ public class ArtistRepository {
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) {
App.getSubsonicClientInstance(false)
.getMediaAnnotationClient()

View File

@@ -0,0 +1,957 @@
package com.cappielloantonio.tempo.repository;
import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.annotation.OptIn;
import androidx.media3.common.MediaItem;
import androidx.media3.common.MediaMetadata;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.session.LibraryResult;
import com.cappielloantonio.tempo.App;
import com.cappielloantonio.tempo.database.AppDatabase;
import com.cappielloantonio.tempo.database.dao.SessionMediaItemDao;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.model.SessionMediaItem;
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
import com.cappielloantonio.tempo.subsonic.models.Artist;
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
import com.cappielloantonio.tempo.subsonic.models.Child;
import com.cappielloantonio.tempo.subsonic.models.Directory;
import com.cappielloantonio.tempo.subsonic.models.Index;
import com.cappielloantonio.tempo.subsonic.models.InternetRadioStation;
import com.cappielloantonio.tempo.subsonic.models.MusicFolder;
import com.cappielloantonio.tempo.subsonic.models.Playlist;
import com.cappielloantonio.tempo.subsonic.models.PodcastEpisode;
import com.cappielloantonio.tempo.util.MappingUtil;
import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.util.Preferences;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class AutomotiveRepository {
private final SessionMediaItemDao sessionMediaItemDao = AppDatabase.getInstance().sessionMediaItemDao();
public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> getAlbums(String prefix, String type, int size) {
final SettableFuture<LibraryResult<ImmutableList<MediaItem>>> listenableFuture = SettableFuture.create();
App.getSubsonicClientInstance(false)
.getAlbumSongListClient()
.getAlbumList2(type, size, 0, null, null)
.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getAlbumList2() != null && response.body().getSubsonicResponse().getAlbumList2().getAlbums() != null) {
List<AlbumID3> albums = response.body().getSubsonicResponse().getAlbumList2().getAlbums();
List<MediaItem> mediaItems = new ArrayList<>();
for (AlbumID3 album : albums) {
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(album.getCoverArtId(), Preferences.getImageSize()));
MediaMetadata mediaMetadata = new MediaMetadata.Builder()
.setTitle(album.getName())
.setAlbumTitle(album.getName())
.setArtist(album.getArtist())
.setGenre(album.getGenre())
.setIsBrowsable(true)
.setIsPlayable(false)
.setMediaType(MediaMetadata.MEDIA_TYPE_ALBUM)
.setArtworkUri(artworkUri)
.build();
MediaItem mediaItem = new MediaItem.Builder()
.setMediaId(prefix + album.getId())
.setMediaMetadata(mediaMetadata)
.setUri("")
.build();
mediaItems.add(mediaItem);
}
LibraryResult<ImmutableList<MediaItem>> libraryResult = LibraryResult.ofItemList(ImmutableList.copyOf(mediaItems), null);
listenableFuture.set(libraryResult);
} else {
listenableFuture.set(LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE));
}
}
@Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
listenableFuture.setException(t);
}
});
return listenableFuture;
}
public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> getStarredSongs() {
final SettableFuture<LibraryResult<ImmutableList<MediaItem>>> listenableFuture = SettableFuture.create();
App.getSubsonicClientInstance(false)
.getAlbumSongListClient()
.getStarred2()
.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getStarred2() != null && response.body().getSubsonicResponse().getStarred2().getSongs() != null) {
List<Child> songs = response.body().getSubsonicResponse().getStarred2().getSongs();
setChildrenMetadata(songs);
List<MediaItem> mediaItems = MappingUtil.mapMediaItems(songs);
LibraryResult<ImmutableList<MediaItem>> libraryResult = LibraryResult.ofItemList(ImmutableList.copyOf(mediaItems), null);
listenableFuture.set(libraryResult);
} else {
listenableFuture.set(LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE));
}
}
@Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
listenableFuture.setException(t);
}
});
return listenableFuture;
}
public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> getStarredAlbums(String prefix) {
final SettableFuture<LibraryResult<ImmutableList<MediaItem>>> listenableFuture = SettableFuture.create();
App.getSubsonicClientInstance(false)
.getAlbumSongListClient()
.getStarred2()
.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getStarred2() != null && response.body().getSubsonicResponse().getStarred2().getAlbums() != null) {
List<AlbumID3> albums = response.body().getSubsonicResponse().getStarred2().getAlbums();
List<MediaItem> mediaItems = new ArrayList<>();
for (AlbumID3 album : albums) {
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(album.getCoverArtId(), Preferences.getImageSize()));
MediaMetadata mediaMetadata = new MediaMetadata.Builder()
.setTitle(album.getName())
.setArtist(album.getArtist())
.setGenre(album.getGenre())
.setIsBrowsable(true)
.setIsPlayable(false)
.setMediaType(MediaMetadata.MEDIA_TYPE_ALBUM)
.setArtworkUri(artworkUri)
.build();
MediaItem mediaItem = new MediaItem.Builder()
.setMediaId(prefix + album.getId())
.setMediaMetadata(mediaMetadata)
.setUri("")
.build();
mediaItems.add(mediaItem);
}
LibraryResult<ImmutableList<MediaItem>> libraryResult = LibraryResult.ofItemList(ImmutableList.copyOf(mediaItems), null);
listenableFuture.set(libraryResult);
} else {
listenableFuture.set(LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE));
}
}
@Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
}
});
return listenableFuture;
}
public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> getStarredArtists(String prefix) {
final SettableFuture<LibraryResult<ImmutableList<MediaItem>>> listenableFuture = SettableFuture.create();
App.getSubsonicClientInstance(false)
.getAlbumSongListClient()
.getStarred2()
.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getStarred2() != null && response.body().getSubsonicResponse().getStarred2().getArtists() != null) {
List<ArtistID3> artists = response.body().getSubsonicResponse().getStarred2().getArtists();
Collections.shuffle(artists);
List<MediaItem> mediaItems = new ArrayList<>();
for (ArtistID3 artist : artists) {
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(artist.getCoverArtId(), Preferences.getImageSize()));
MediaMetadata mediaMetadata = new MediaMetadata.Builder()
.setTitle(artist.getName())
.setIsBrowsable(true)
.setIsPlayable(false)
.setMediaType(MediaMetadata.MEDIA_TYPE_PLAYLIST)
.setArtworkUri(artworkUri)
.build();
MediaItem mediaItem = new MediaItem.Builder()
.setMediaId(prefix + artist.getId())
.setMediaMetadata(mediaMetadata)
.setUri("")
.build();
mediaItems.add(mediaItem);
}
LibraryResult<ImmutableList<MediaItem>> libraryResult = LibraryResult.ofItemList(ImmutableList.copyOf(mediaItems), null);
listenableFuture.set(libraryResult);
} else {
listenableFuture.set(LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE));
}
}
@Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
listenableFuture.setException(t);
}
});
return listenableFuture;
}
public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> getMusicFolders(String prefix) {
final SettableFuture<LibraryResult<ImmutableList<MediaItem>>> listenableFuture = SettableFuture.create();
App.getSubsonicClientInstance(false)
.getBrowsingClient()
.getMusicFolders()
.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getMusicFolders() != null && response.body().getSubsonicResponse().getMusicFolders().getMusicFolders() != null) {
List<MusicFolder> musicFolders = response.body().getSubsonicResponse().getMusicFolders().getMusicFolders();
List<MediaItem> mediaItems = new ArrayList<>();
for (MusicFolder musicFolder : musicFolders) {
MediaMetadata mediaMetadata = new MediaMetadata.Builder()
.setTitle(musicFolder.getName())
.setIsBrowsable(true)
.setIsPlayable(false)
.setMediaType(MediaMetadata.MEDIA_TYPE_FOLDER_MIXED)
.build();
MediaItem mediaItem = new MediaItem.Builder()
.setMediaId(prefix + musicFolder.getId())
.setMediaMetadata(mediaMetadata)
.setUri("")
.build();
mediaItems.add(mediaItem);
}
LibraryResult<ImmutableList<MediaItem>> libraryResult = LibraryResult.ofItemList(ImmutableList.copyOf(mediaItems), null);
listenableFuture.set(libraryResult);
} else {
listenableFuture.set(LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE));
}
}
@Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
listenableFuture.setException(t);
}
});
return listenableFuture;
}
public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> getIndexes(String prefix, String id) {
final SettableFuture<LibraryResult<ImmutableList<MediaItem>>> listenableFuture = SettableFuture.create();
App.getSubsonicClientInstance(false)
.getBrowsingClient()
.getIndexes(id, null)
.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getIndexes() != null) {
List<MediaItem> mediaItems = new ArrayList<>();
if (response.body().getSubsonicResponse().getIndexes().getIndices() != null) {
List<Index> indices = response.body().getSubsonicResponse().getIndexes().getIndices();
for (Index index : indices) {
if (index.getArtists() != null) {
for (Artist artist : index.getArtists()) {
MediaMetadata mediaMetadata = new MediaMetadata.Builder()
.setTitle(artist.getName())
.setIsBrowsable(true)
.setIsPlayable(false)
.setMediaType(MediaMetadata.MEDIA_TYPE_ARTIST)
.build();
MediaItem mediaItem = new MediaItem.Builder()
.setMediaId(prefix + artist.getId())
.setMediaMetadata(mediaMetadata)
.setUri("")
.build();
mediaItems.add(mediaItem);
}
}
}
}
if (response.body().getSubsonicResponse().getIndexes().getChildren() != null) {
List<Child> children = response.body().getSubsonicResponse().getIndexes().getChildren();
for (Child song : children) {
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(song.getCoverArtId(), Preferences.getImageSize()));
MediaMetadata mediaMetadata = new MediaMetadata.Builder()
.setTitle(song.getTitle())
.setAlbumTitle(song.getAlbum())
.setArtist(song.getArtist())
.setIsBrowsable(false)
.setIsPlayable(true)
.setMediaType(MediaMetadata.MEDIA_TYPE_MUSIC)
.setArtworkUri(artworkUri)
.build();
MediaItem mediaItem = new MediaItem.Builder()
.setMediaId(prefix + song.getId())
.setMediaMetadata(mediaMetadata)
.setUri(MusicUtil.getStreamUri(song.getId()))
.build();
mediaItems.add(mediaItem);
}
setChildrenMetadata(children);
}
LibraryResult<ImmutableList<MediaItem>> libraryResult = LibraryResult.ofItemList(ImmutableList.copyOf(mediaItems), null);
listenableFuture.set(libraryResult);
}
}
@Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
listenableFuture.setException(t);
}
});
return listenableFuture;
}
public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> getDirectories(String prefix, String id) {
final SettableFuture<LibraryResult<ImmutableList<MediaItem>>> listenableFuture = SettableFuture.create();
App.getSubsonicClientInstance(false)
.getBrowsingClient()
.getMusicDirectory(id)
.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getDirectory() != null && response.body().getSubsonicResponse().getDirectory().getChildren() != null) {
Directory directory = response.body().getSubsonicResponse().getDirectory();
List<MediaItem> mediaItems = new ArrayList<>();
for (Child child : directory.getChildren()) {
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(child.getCoverArtId(), Preferences.getImageSize()));
MediaMetadata mediaMetadata = new MediaMetadata.Builder()
.setTitle(child.getTitle())
.setIsBrowsable(child.isDir())
.setIsPlayable(!child.isDir())
.setMediaType(MediaMetadata.MEDIA_TYPE_FOLDER_MIXED)
.setArtworkUri(artworkUri)
.build();
MediaItem mediaItem = new MediaItem.Builder()
.setMediaId(child.isDir() ? prefix + child.getId() : child.getId())
.setMediaMetadata(mediaMetadata)
.setUri(!child.isDir() ? MusicUtil.getStreamUri(child.getId()) : Uri.parse(""))
.build();
mediaItems.add(mediaItem);
}
setChildrenMetadata(directory.getChildren().stream().filter(child -> !child.isDir()).collect(Collectors.toList()));
LibraryResult<ImmutableList<MediaItem>> libraryResult = LibraryResult.ofItemList(ImmutableList.copyOf(mediaItems), null);
listenableFuture.set(libraryResult);
}
}
@Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
listenableFuture.setException(t);
}
});
return listenableFuture;
}
public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> getPlaylists(String prefix) {
final SettableFuture<LibraryResult<ImmutableList<MediaItem>>> listenableFuture = SettableFuture.create();
App.getSubsonicClientInstance(false)
.getPlaylistClient()
.getPlaylists()
.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getPlaylists() != null && response.body().getSubsonicResponse().getPlaylists().getPlaylists() != null) {
List<Playlist> playlists = response.body().getSubsonicResponse().getPlaylists().getPlaylists();
List<MediaItem> mediaItems = new ArrayList<>();
for (Playlist playlist : playlists) {
MediaMetadata mediaMetadata = new MediaMetadata.Builder()
.setTitle(playlist.getName())
.setIsBrowsable(true)
.setIsPlayable(false)
.setMediaType(MediaMetadata.MEDIA_TYPE_PLAYLIST)
.build();
MediaItem mediaItem = new MediaItem.Builder()
.setMediaId(prefix + playlist.getId())
.setMediaMetadata(mediaMetadata)
.setUri("")
.build();
mediaItems.add(mediaItem);
}
LibraryResult<ImmutableList<MediaItem>> libraryResult = LibraryResult.ofItemList(ImmutableList.copyOf(mediaItems), null);
listenableFuture.set(libraryResult);
} else {
listenableFuture.set(LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE));
}
}
@Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
listenableFuture.setException(t);
}
});
return listenableFuture;
}
public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> getNewestPodcastEpisodes(int count) {
final SettableFuture<LibraryResult<ImmutableList<MediaItem>>> listenableFuture = SettableFuture.create();
App.getSubsonicClientInstance(false)
.getPodcastClient()
.getNewestPodcasts(count)
.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getNewestPodcasts() != null && response.body().getSubsonicResponse().getNewestPodcasts().getEpisodes() != null) {
List<PodcastEpisode> episodes = response.body().getSubsonicResponse().getNewestPodcasts().getEpisodes();
List<MediaItem> mediaItems = new ArrayList<>();
for (PodcastEpisode episode : episodes) {
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(episode.getCoverArtId(), Preferences.getImageSize()));
MediaMetadata mediaMetadata = new MediaMetadata.Builder()
.setTitle(episode.getTitle())
.setIsBrowsable(false)
.setIsPlayable(true)
.setMediaType(MediaMetadata.MEDIA_TYPE_PODCAST_EPISODE)
.setArtworkUri(artworkUri)
.build();
MediaItem mediaItem = new MediaItem.Builder()
.setMediaId(episode.getId())
.setMediaMetadata(mediaMetadata)
.setUri(MusicUtil.getStreamUri(episode.getStreamId()))
.build();
mediaItems.add(mediaItem);
}
setPodcastEpisodesMetadata(episodes);
LibraryResult<ImmutableList<MediaItem>> libraryResult = LibraryResult.ofItemList(ImmutableList.copyOf(mediaItems), null);
listenableFuture.set(libraryResult);
} else {
listenableFuture.set(LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE));
}
}
@Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
listenableFuture.setException(t);
}
});
return listenableFuture;
}
public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> getInternetRadioStations() {
final SettableFuture<LibraryResult<ImmutableList<MediaItem>>> listenableFuture = SettableFuture.create();
App.getSubsonicClientInstance(false)
.getInternetRadioClient()
.getInternetRadioStations()
.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getInternetRadioStations() != null && response.body().getSubsonicResponse().getInternetRadioStations().getInternetRadioStations() != null) {
List<InternetRadioStation> radioStations = response.body().getSubsonicResponse().getInternetRadioStations().getInternetRadioStations();
List<MediaItem> mediaItems = new ArrayList<>();
for (InternetRadioStation radioStation : radioStations) {
MediaMetadata mediaMetadata = new MediaMetadata.Builder()
.setTitle(radioStation.getName())
.setIsBrowsable(false)
.setIsPlayable(true)
.setMediaType(MediaMetadata.MEDIA_TYPE_RADIO_STATION)
.build();
MediaItem mediaItem = new MediaItem.Builder()
.setMediaId(radioStation.getId())
.setMediaMetadata(mediaMetadata)
.setUri(radioStation.getStreamUrl())
.build();
mediaItems.add(mediaItem);
}
setInternetRadioStationsMetadata(radioStations);
LibraryResult<ImmutableList<MediaItem>> libraryResult = LibraryResult.ofItemList(ImmutableList.copyOf(mediaItems), null);
listenableFuture.set(libraryResult);
} else {
listenableFuture.set(LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE));
}
}
@Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
listenableFuture.setException(t);
}
});
return listenableFuture;
}
public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> getAlbumTracks(String id) {
final SettableFuture<LibraryResult<ImmutableList<MediaItem>>> listenableFuture = SettableFuture.create();
App.getSubsonicClientInstance(false)
.getBrowsingClient()
.getAlbum(id)
.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getAlbum() != null && response.body().getSubsonicResponse().getAlbum().getSongs() != null) {
List<Child> tracks = response.body().getSubsonicResponse().getAlbum().getSongs();
setChildrenMetadata(tracks);
List<MediaItem> mediaItems = MappingUtil.mapMediaItems(tracks);
LibraryResult<ImmutableList<MediaItem>> libraryResult = LibraryResult.ofItemList(ImmutableList.copyOf(mediaItems), null);
listenableFuture.set(libraryResult);
} else {
listenableFuture.set(LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE));
}
}
@Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
listenableFuture.setException(t);
}
});
return listenableFuture;
}
public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> getArtistAlbum(String prefix, String id) {
final SettableFuture<LibraryResult<ImmutableList<MediaItem>>> listenableFuture = SettableFuture.create();
App.getSubsonicClientInstance(false)
.getBrowsingClient()
.getArtist(id)
.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getArtist() != null && response.body().getSubsonicResponse().getArtist().getAlbums() != null) {
List<AlbumID3> albums = response.body().getSubsonicResponse().getArtist().getAlbums();
List<MediaItem> mediaItems = new ArrayList<>();
for (AlbumID3 album : albums) {
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(album.getCoverArtId(), Preferences.getImageSize()));
MediaMetadata mediaMetadata = new MediaMetadata.Builder()
.setTitle(album.getName())
.setAlbumTitle(album.getName())
.setArtist(album.getArtist())
.setGenre(album.getGenre())
.setIsBrowsable(true)
.setIsPlayable(false)
.setMediaType(MediaMetadata.MEDIA_TYPE_ALBUM)
.setArtworkUri(artworkUri)
.build();
MediaItem mediaItem = new MediaItem.Builder()
.setMediaId(prefix + album.getId())
.setMediaMetadata(mediaMetadata)
.setUri("")
.build();
mediaItems.add(mediaItem);
}
LibraryResult<ImmutableList<MediaItem>> libraryResult = LibraryResult.ofItemList(ImmutableList.copyOf(mediaItems), null);
listenableFuture.set(libraryResult);
}
}
@Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
listenableFuture.setException(t);
}
});
return listenableFuture;
}
public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> getPlaylistSongs(String id) {
final SettableFuture<LibraryResult<ImmutableList<MediaItem>>> listenableFuture = SettableFuture.create();
App.getSubsonicClientInstance(false)
.getPlaylistClient()
.getPlaylist(id)
.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getPlaylist() != null && response.body().getSubsonicResponse().getPlaylist().getEntries() != null) {
List<Child> tracks = response.body().getSubsonicResponse().getPlaylist().getEntries();
setChildrenMetadata(tracks);
List<MediaItem> mediaItems = MappingUtil.mapMediaItems(tracks);
LibraryResult<ImmutableList<MediaItem>> libraryResult = LibraryResult.ofItemList(ImmutableList.copyOf(mediaItems), null);
listenableFuture.set(libraryResult);
}
}
@Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
listenableFuture.setException(t);
}
});
return listenableFuture;
}
public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> getMadeForYou(String id, int count) {
final SettableFuture<LibraryResult<ImmutableList<MediaItem>>> listenableFuture = SettableFuture.create();
App.getSubsonicClientInstance(false)
.getBrowsingClient()
.getSimilarSongs2(id, count)
.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getSimilarSongs2() != null && response.body().getSubsonicResponse().getSimilarSongs2().getSongs() != null) {
List<Child> tracks = response.body().getSubsonicResponse().getSimilarSongs2().getSongs();
setChildrenMetadata(tracks);
List<MediaItem> mediaItems = MappingUtil.mapMediaItems(tracks);
LibraryResult<ImmutableList<MediaItem>> libraryResult = LibraryResult.ofItemList(ImmutableList.copyOf(mediaItems), null);
listenableFuture.set(libraryResult);
}
}
@Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
listenableFuture.setException(t);
}
});
return listenableFuture;
}
public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> search(String query, String albumPrefix, String artistPrefix) {
final SettableFuture<LibraryResult<ImmutableList<MediaItem>>> listenableFuture = SettableFuture.create();
App.getSubsonicClientInstance(false)
.getSearchingClient()
.search3(query, 20, 20, 20)
.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getSearchResult3() != null) {
List<MediaItem> mediaItems = new ArrayList<>();
if (response.body().getSubsonicResponse().getSearchResult3().getArtists() != null) {
for (ArtistID3 artist : response.body().getSubsonicResponse().getSearchResult3().getArtists()) {
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(artist.getCoverArtId(), Preferences.getImageSize()));
MediaMetadata mediaMetadata = new MediaMetadata.Builder()
.setTitle(artist.getName())
.setIsBrowsable(true)
.setIsPlayable(false)
.setMediaType(MediaMetadata.MEDIA_TYPE_PLAYLIST)
.setArtworkUri(artworkUri)
.build();
MediaItem mediaItem = new MediaItem.Builder()
.setMediaId(artistPrefix + artist.getId())
.setMediaMetadata(mediaMetadata)
.setUri("")
.build();
mediaItems.add(mediaItem);
}
}
if (response.body().getSubsonicResponse().getSearchResult3().getAlbums() != null) {
for (AlbumID3 album : response.body().getSubsonicResponse().getSearchResult3().getAlbums()) {
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(album.getCoverArtId(), Preferences.getImageSize()));
MediaMetadata mediaMetadata = new MediaMetadata.Builder()
.setTitle(album.getName())
.setAlbumTitle(album.getName())
.setArtist(album.getArtist())
.setGenre(album.getGenre())
.setIsBrowsable(true)
.setIsPlayable(false)
.setMediaType(MediaMetadata.MEDIA_TYPE_ALBUM)
.setArtworkUri(artworkUri)
.build();
MediaItem mediaItem = new MediaItem.Builder()
.setMediaId(albumPrefix + album.getId())
.setMediaMetadata(mediaMetadata)
.setUri("")
.build();
mediaItems.add(mediaItem);
}
}
if (response.body().getSubsonicResponse().getSearchResult3().getSongs() != null) {
List<Child> tracks = response.body().getSubsonicResponse().getSearchResult3().getSongs();
setChildrenMetadata(tracks);
mediaItems.addAll(MappingUtil.mapMediaItems(tracks));
}
LibraryResult<ImmutableList<MediaItem>> libraryResult = LibraryResult.ofItemList(ImmutableList.copyOf(mediaItems), null);
listenableFuture.set(libraryResult);
}
}
@Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
listenableFuture.setException(t);
}
});
return listenableFuture;
}
@OptIn(markerClass = UnstableApi.class)
public void setChildrenMetadata(List<Child> children) {
long timestamp = System.currentTimeMillis();
ArrayList<SessionMediaItem> sessionMediaItems = new ArrayList<>();
for (Child child : children) {
SessionMediaItem sessionMediaItem = new SessionMediaItem(child);
sessionMediaItem.setTimestamp(timestamp);
sessionMediaItems.add(sessionMediaItem);
}
InsertAllThreadSafe insertAll = new InsertAllThreadSafe(sessionMediaItemDao, sessionMediaItems);
Thread thread = new Thread(insertAll);
thread.start();
}
@OptIn(markerClass = UnstableApi.class)
public void setPodcastEpisodesMetadata(List<PodcastEpisode> podcastEpisodes) {
long timestamp = System.currentTimeMillis();
ArrayList<SessionMediaItem> sessionMediaItems = new ArrayList<>();
for (PodcastEpisode podcastEpisode : podcastEpisodes) {
SessionMediaItem sessionMediaItem = new SessionMediaItem(podcastEpisode);
sessionMediaItem.setTimestamp(timestamp);
sessionMediaItems.add(sessionMediaItem);
}
InsertAllThreadSafe insertAll = new InsertAllThreadSafe(sessionMediaItemDao, sessionMediaItems);
Thread thread = new Thread(insertAll);
thread.start();
}
@OptIn(markerClass = UnstableApi.class)
public void setInternetRadioStationsMetadata(List<InternetRadioStation> internetRadioStations) {
long timestamp = System.currentTimeMillis();
ArrayList<SessionMediaItem> sessionMediaItems = new ArrayList<>();
for (InternetRadioStation internetRadioStation : internetRadioStations) {
SessionMediaItem sessionMediaItem = new SessionMediaItem(internetRadioStation);
sessionMediaItem.setTimestamp(timestamp);
sessionMediaItems.add(sessionMediaItem);
}
InsertAllThreadSafe insertAll = new InsertAllThreadSafe(sessionMediaItemDao, sessionMediaItems);
Thread thread = new Thread(insertAll);
thread.start();
}
public SessionMediaItem getSessionMediaItem(String id) {
SessionMediaItem sessionMediaItem = null;
GetMediaItemThreadSafe getMediaItemThreadSafe = new GetMediaItemThreadSafe(sessionMediaItemDao, id);
Thread thread = new Thread(getMediaItemThreadSafe);
thread.start();
try {
thread.join();
sessionMediaItem = getMediaItemThreadSafe.getSessionMediaItem();
} catch (InterruptedException e) {
e.printStackTrace();
}
return sessionMediaItem;
}
public List<MediaItem> getMetadatas(long timestamp) {
List<MediaItem> mediaItems = Collections.emptyList();
GetMediaItemsThreadSafe getMediaItemsThreadSafe = new GetMediaItemsThreadSafe(sessionMediaItemDao, timestamp);
Thread thread = new Thread(getMediaItemsThreadSafe);
thread.start();
try {
thread.join();
mediaItems = getMediaItemsThreadSafe.getMediaItems();
} catch (InterruptedException e) {
e.printStackTrace();
}
return mediaItems;
}
public void deleteMetadata() {
DeleteAllThreadSafe delete = new DeleteAllThreadSafe(sessionMediaItemDao);
Thread thread = new Thread(delete);
thread.start();
}
private static class GetMediaItemThreadSafe implements Runnable {
private final SessionMediaItemDao sessionMediaItemDao;
private final String id;
private SessionMediaItem sessionMediaItem;
public GetMediaItemThreadSafe(SessionMediaItemDao sessionMediaItemDao, String id) {
this.sessionMediaItemDao = sessionMediaItemDao;
this.id = id;
}
@Override
public void run() {
sessionMediaItem = sessionMediaItemDao.get(id);
}
public SessionMediaItem getSessionMediaItem() {
return sessionMediaItem;
}
}
@OptIn(markerClass = UnstableApi.class)
private static class GetMediaItemsThreadSafe implements Runnable {
private final SessionMediaItemDao sessionMediaItemDao;
private final Long timestamp;
private final List<MediaItem> mediaItems = new ArrayList<>();
public GetMediaItemsThreadSafe(SessionMediaItemDao sessionMediaItemDao, Long timestamp) {
this.sessionMediaItemDao = sessionMediaItemDao;
this.timestamp = timestamp;
}
@Override
public void run() {
List<SessionMediaItem> sessionMediaItems = sessionMediaItemDao.get(timestamp);
sessionMediaItems.forEach(sessionMediaItem -> mediaItems.add(sessionMediaItem.getMediaItem()));
}
public List<MediaItem> getMediaItems() {
return mediaItems;
}
}
private static class InsertAllThreadSafe implements Runnable {
private final SessionMediaItemDao sessionMediaItemDao;
private final List<SessionMediaItem> sessionMediaItems;
public InsertAllThreadSafe(SessionMediaItemDao sessionMediaItemDao, List<SessionMediaItem> sessionMediaItems) {
this.sessionMediaItemDao = sessionMediaItemDao;
this.sessionMediaItems = sessionMediaItems;
}
@Override
public void run() {
sessionMediaItemDao.insertAll(sessionMediaItems);
}
}
private static class DeleteAllThreadSafe implements Runnable {
private final SessionMediaItemDao sessionMediaItemDao;
public DeleteAllThreadSafe(SessionMediaItemDao sessionMediaItemDao) {
this.sessionMediaItemDao = sessionMediaItemDao;
}
@Override
public void run() {
sessionMediaItemDao.deleteAll();
}
}
}

View File

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

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>() {
@Override
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();
if (genreList == null || genreList.isEmpty()) {
genres.setValue(Collections.emptyList());
return;
}
if (random) {
Collections.shuffle(genreList);
}

View File

@@ -1,7 +1,5 @@
package com.cappielloantonio.tempo.repository;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.lifecycle.MutableLiveData;
@@ -95,12 +93,29 @@ public class PlaylistRepository {
.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
Log.d("PLAYLIST", response.toString());
}
@Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
Log.d("PLAYLIST", t.toString());
}
});
}
public void updatePlaylist(String playlistId, String name, ArrayList<String> songsId) {
App.getSubsonicClientInstance(false)
.getPlaylistClient()
.deletePlaylist(playlistId)
.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
createPlaylist(null, name, songsId);
}
@Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
}
});
}

View File

@@ -59,7 +59,7 @@ public class PodcastRepository {
@Override
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

@@ -17,8 +17,12 @@ public class ScanRepository {
.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull retrofit2.Response<ApiResponse> response) {
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getScanStatus() != null) {
callback.onSuccess(response.body().getSubsonicResponse().getScanStatus().isScanning(), response.body().getSubsonicResponse().getScanStatus().getCount());
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse() != null) {
if (response.body().getSubsonicResponse().getError() != null) {
callback.onError(new Exception(response.body().getSubsonicResponse().getError().getMessage()));
} else if (response.body().getSubsonicResponse().getScanStatus() != null) {
callback.onSuccess(response.body().getSubsonicResponse().getScanStatus().isScanning(), response.body().getSubsonicResponse().getScanStatus().getCount());
}
}
}
@@ -36,8 +40,12 @@ public class ScanRepository {
.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull retrofit2.Response<ApiResponse> response) {
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getScanStatus() != null) {
callback.onSuccess(response.body().getSubsonicResponse().getScanStatus().isScanning(), response.body().getSubsonicResponse().getScanStatus().getCount());
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse() != null) {
if (response.body().getSubsonicResponse().getError() != null) {
callback.onError(new Exception(response.body().getSubsonicResponse().getError().getMessage()));
} else if (response.body().getSubsonicResponse().getScanStatus() != null) {
callback.onSuccess(response.body().getSubsonicResponse().getScanStatus().isScanning(), response.body().getSubsonicResponse().getScanStatus().getCount());
}
}
}

View File

@@ -11,6 +11,7 @@ import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
import com.cappielloantonio.tempo.subsonic.models.Child;
import com.cappielloantonio.tempo.subsonic.models.SearchResult2;
import com.cappielloantonio.tempo.subsonic.models.SearchResult3;
import java.util.ArrayList;
@@ -24,7 +25,30 @@ import retrofit2.Response;
public class SearchingRepository {
private final RecentSearchDao recentSearchDao = AppDatabase.getInstance().recentSearchDao();
public MutableLiveData<SearchResult3> search(String query) {
public MutableLiveData<SearchResult2> search2(String query) {
MutableLiveData<SearchResult2> result = new MutableLiveData<>();
App.getSubsonicClientInstance(false)
.getSearchingClient()
.search3(query, 20, 20, 20)
.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
if (response.isSuccessful() && response.body() != null) {
result.setValue(response.body().getSubsonicResponse().getSearchResult2());
}
}
@Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
}
});
return result;
}
public MutableLiveData<SearchResult3> search3(String query) {
MutableLiveData<SearchResult3> result = new MutableLiveData<>();
App.getSubsonicClientInstance(false)
@@ -33,7 +57,9 @@ public class SearchingRepository {
.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
result.setValue(response.body().getSubsonicResponse().getSearchResult3());
if (response.isSuccessful() && response.body() != null) {
result.setValue(response.body().getSubsonicResponse().getSearchResult3());
}
}
@Override
@@ -56,7 +82,7 @@ public class SearchingRepository {
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
List<String> newSuggestions = new ArrayList();
if (response.isSuccessful() && response.body() != null) {
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getSearchResult3() != null) {
if (response.body().getSubsonicResponse().getSearchResult3().getArtists() != null) {
for (ArtistID3 artistID3 : response.body().getSubsonicResponse().getSearchResult3().getArtists()) {
newSuggestions.add(artistID3.getName());
@@ -103,10 +129,10 @@ public class SearchingRepository {
thread.start();
}
public List<String> getRecentSearchSuggestion(int limit) {
public List<String> getRecentSearchSuggestion() {
List<String> recent = new ArrayList<>();
RecentThreadSafe suggestionsThread = new RecentThreadSafe(recentSearchDao, limit);
RecentThreadSafe suggestionsThread = new RecentThreadSafe(recentSearchDao);
Thread thread = new Thread(suggestionsThread);
thread.start();
@@ -152,17 +178,15 @@ public class SearchingRepository {
private static class RecentThreadSafe implements Runnable {
private final RecentSearchDao recentSearchDao;
private final int limit;
private List<String> recent = new ArrayList<>();
public RecentThreadSafe(RecentSearchDao recentSearchDao, int limit) {
public RecentThreadSafe(RecentSearchDao recentSearchDao) {
this.recentSearchDao = recentSearchDao;
this.limit = limit;
}
@Override
public void run() {
recent = recentSearchDao.getRecent(limit);
recent = recentSearchDao.getRecent();
}
public List<String> getRecent() {

View File

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

View File

@@ -6,6 +6,8 @@ import androidx.annotation.NonNull;
import androidx.lifecycle.MutableLiveData;
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.models.Child;
@@ -86,7 +88,7 @@ public class SongRepository {
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
List<Child> songs = new ArrayList<>();
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getRandomSongs() != null) {
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getRandomSongs() != null && response.body().getSubsonicResponse().getRandomSongs().getSongs() != null) {
songs.addAll(response.body().getSubsonicResponse().getRandomSongs().getSongs());
}
@@ -95,7 +97,7 @@ public class SongRepository {
@Override
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) {
App.getSubsonicClientInstance(false)
.getMediaAnnotationClient()
@@ -173,8 +141,6 @@ public class SongRepository {
public MutableLiveData<List<Child>> getSongsByGenre(String id, int page) {
MutableLiveData<List<Child>> songsByGenre = new MutableLiveData<>();
Log.d(TAG, "onScrolled PAGE: " + page);
App.getSubsonicClientInstance(false)
.getAlbumSongListClient()
.getSongsByGenre(id, 100, 100 * page)
@@ -239,7 +205,7 @@ public class SongRepository {
@Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
Log.d(TAG, "onFailure: ");
}
});

View File

@@ -22,7 +22,7 @@ public class SystemRepository {
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull retrofit2.Response<ApiResponse> response) {
if (response.body() != null) {
if (response.body().getSubsonicResponse().getStatus().equals(ResponseStatus.FAILED)) {
callback.onError(new Exception(response.body().getSubsonicResponse().getError().getCode().getValue() + " - " + response.body().getSubsonicResponse().getError().getMessage()));
callback.onError(new Exception(response.body().getSubsonicResponse().getError().getCode() + " - " + response.body().getSubsonicResponse().getError().getMessage()));
} else if (response.body().getSubsonicResponse().getStatus().equals(ResponseStatus.OK)) {
String password = response.raw().request().url().queryParameter("p");
String token = response.raw().request().url().queryParameter("t");

View File

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

View File

@@ -7,7 +7,6 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.media3.common.util.NotificationUtil;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import androidx.media3.exoplayer.offline.Download;
import androidx.media3.exoplayer.offline.DownloadManager;
import androidx.media3.exoplayer.offline.DownloadNotificationHelper;
@@ -55,12 +54,37 @@ public class DownloaderService extends androidx.media3.exoplayer.offline.Downloa
private final Context context;
private final DownloadNotificationHelper notificationHelper;
private final Notification successfulDownloadGroupNotification;
private final Notification failedDownloadGroupNotification;
private final int successfulDownloadGroupNotificationId;
private final int failedDownloadGroupNotificationId;
private int nextNotificationId;
public TerminalStateNotificationHelper(Context context, DownloadNotificationHelper notificationHelper, int firstNotificationId) {
this.context = context.getApplicationContext();
this.notificationHelper = notificationHelper;
nextNotificationId = firstNotificationId;
successfulDownloadGroupNotification = DownloadUtil.buildGroupSummaryNotification(
this.context,
DownloadUtil.DOWNLOAD_NOTIFICATION_CHANNEL_ID,
DownloadUtil.DOWNLOAD_NOTIFICATION_SUCCESSFUL_GROUP,
R.drawable.ic_check_circle,
"Downloads completed"
);
failedDownloadGroupNotification = DownloadUtil.buildGroupSummaryNotification(
this.context,
DownloadUtil.DOWNLOAD_NOTIFICATION_CHANNEL_ID,
DownloadUtil.DOWNLOAD_NOTIFICATION_FAILED_GROUP,
R.drawable.ic_error,
"Downloads failed"
);
successfulDownloadGroupNotificationId = nextNotificationId++;
failedDownloadGroupNotificationId = nextNotificationId++;
}
@Override
@@ -68,10 +92,14 @@ public class DownloaderService extends androidx.media3.exoplayer.offline.Downloa
Notification notification;
if (download.state == Download.STATE_COMPLETED) {
notification = notificationHelper.buildDownloadCompletedNotification(context, R.drawable.ic_check_circle, null, Util.fromUtf8Bytes(download.request.data));
notification = notificationHelper.buildDownloadCompletedNotification(context, R.drawable.ic_check_circle, null, DownloaderManager.getDownloadNotificationMessage(download.request.id));
notification = Notification.Builder.recoverBuilder(context, notification).setGroup(DownloadUtil.DOWNLOAD_NOTIFICATION_SUCCESSFUL_GROUP).build();
NotificationUtil.setNotification(this.context, successfulDownloadGroupNotificationId, successfulDownloadGroupNotification);
DownloaderManager.updateDatabase(download.request.id);
} else if (download.state == Download.STATE_FAILED) {
notification = notificationHelper.buildDownloadFailedNotification(context, R.drawable.ic_error, null, Util.fromUtf8Bytes(download.request.data));
notification = notificationHelper.buildDownloadFailedNotification(context, R.drawable.ic_error, null, DownloaderManager.getDownloadNotificationMessage(download.request.id));
notification = Notification.Builder.recoverBuilder(context, notification).setGroup(DownloadUtil.DOWNLOAD_NOTIFICATION_FAILED_GROUP).build();
NotificationUtil.setNotification(this.context, failedDownloadGroupNotificationId, failedDownloadGroupNotification);
} else {
return;
}

View File

@@ -12,6 +12,7 @@ import com.cappielloantonio.tempo.subsonic.models.Child;
import com.cappielloantonio.tempo.subsonic.models.InternetRadioStation;
import com.cappielloantonio.tempo.subsonic.models.PodcastEpisode;
import com.cappielloantonio.tempo.util.MappingUtil;
import com.cappielloantonio.tempo.util.Preferences;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
@@ -204,6 +205,22 @@ public class MediaManager {
}
}
public static void shuffle(ListenableFuture<MediaBrowser> mediaBrowserListenableFuture, List<Child> media, int startIndex, int endIndex) {
if (mediaBrowserListenableFuture != null) {
mediaBrowserListenableFuture.addListener(() -> {
try {
if (mediaBrowserListenableFuture.isDone()) {
mediaBrowserListenableFuture.get().removeMediaItems(startIndex, endIndex + 1);
mediaBrowserListenableFuture.get().addMediaItems(MappingUtil.mapMediaItems(media).subList(startIndex, endIndex + 1));
swapDatabase(media);
}
} catch (ExecutionException | InterruptedException e) {
e.printStackTrace();
}
}, MoreExecutors.directExecutor());
}
}
public static void swap(ListenableFuture<MediaBrowser> mediaBrowserListenableFuture, List<Child> media, int from, int to) {
if (mediaBrowserListenableFuture != null) {
mediaBrowserListenableFuture.addListener(() -> {
@@ -238,6 +255,21 @@ public class MediaManager {
}
}
public static void removeRange(ListenableFuture<MediaBrowser> mediaBrowserListenableFuture, List<Child> media, int fromItem, int toItem) {
if (mediaBrowserListenableFuture != null) {
mediaBrowserListenableFuture.addListener(() -> {
try {
if (mediaBrowserListenableFuture.isDone()) {
mediaBrowserListenableFuture.get().removeMediaItems(fromItem, toItem);
removeRangeDatabase(media, fromItem, toItem);
}
} catch (ExecutionException | InterruptedException e) {
e.printStackTrace();
}
}, MoreExecutors.directExecutor());
}
}
public static void getCurrentIndex(ListenableFuture<MediaBrowser> mediaBrowserListenableFuture, MediaIndexCallback callback) {
if (mediaBrowserListenableFuture != null) {
mediaBrowserListenableFuture.addListener(() -> {
@@ -262,7 +294,7 @@ public class MediaManager {
}
public static void scrobble(MediaItem mediaItem) {
if (mediaItem != null) {
if (mediaItem != null && Preferences.isScrobblingEnabled()) {
getSongRepository().scrobble(mediaItem.mediaMetadata.extras.getString("id"));
}
}
@@ -304,6 +336,14 @@ public class MediaManager {
}
}
private static void removeRangeDatabase(List<Child> media, int fromItem, int toItem) {
List<Child> toRemove = media.subList(fromItem, toItem);
media.removeAll(toRemove);
getQueueRepository().insertAll(media, true, 0);
}
public static void clearDatabase() {
getQueueRepository().deleteAll();
}

View File

@@ -16,6 +16,7 @@ class RetrofitClient(subsonic: Subsonic) {
init {
retrofit = Retrofit.Builder()
.baseUrl(subsonic.url)
.addConverterFactory(GsonConverterFactory.create(GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ss").create()))
.addConverterFactory(GsonConverterFactory.create(GsonBuilder().setLenient().create()))
.client(getOkHttpClient())
.build()

View File

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

View File

@@ -9,7 +9,7 @@ import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
import retrofit2.Call;
public class MediaLibraryScanningClient {
private static final String TAG = "SystemClient";
private static final String TAG = "MediaLibraryScanningClient";
private final Subsonic subsonic;
private final MediaLibraryScanningService mediaLibraryScanningService;

View File

@@ -9,7 +9,7 @@ import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
import retrofit2.Call;
public class PodcastClient {
private static final String TAG = "SystemClient";
private static final String TAG = "PodcastClient";
private final Subsonic subsonic;
private final PodcastService podcastService;
@@ -48,4 +48,9 @@ public class PodcastClient {
Log.d(TAG, "deletePodcastEpisode()");
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")
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

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

View File

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

View File

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

View File

@@ -4,6 +4,6 @@ import androidx.annotation.Keep
@Keep
class Error {
var code: ErrorCode? = null
var code: Int? = null
var message: String? = null
}

View File

@@ -14,6 +14,7 @@ class PodcastChannel : Parcelable {
var url: String? = null
var title: String? = null
var description: String? = null
@SerializedName("coverArt")
var coverArtId: String? = null
var originalImageUrl: String? = null
var status: String? = null

View File

@@ -2,7 +2,6 @@ package com.cappielloantonio.tempo.subsonic.models
import android.os.Parcelable
import androidx.annotation.Keep
import androidx.room.ColumnInfo
import com.google.gson.annotations.SerializedName
import kotlinx.parcelize.Parcelize
import java.util.*
@@ -13,12 +12,10 @@ class PodcastEpisode : Parcelable {
var id: String? = null
@SerializedName("parent")
var parentId: String? = null
@SerializedName("isDir")
var isDir = false
var title: String? = null
var album: String? = null
var artist: String? = null
var track: Int? = null
var year: Int? = null
var genre: String? = null
@SerializedName("coverArt")
@@ -26,28 +23,14 @@ class PodcastEpisode : Parcelable {
var size: Long? = null
var contentType: String? = null
var suffix: String? = null
var transcodedContentType: String? = null
var transcodedSuffix: String? = null
var duration: Int? = null
@ColumnInfo("bitrate")
@SerializedName("bitRate")
var bitrate: Int? = null
var path: String? = null
@ColumnInfo(name = "is_video")
@SerializedName("isVideo")
var isVideo: Boolean = false
var userRating: Int? = null
var averageRating: Double? = null
var playCount: Long? = null
var discNumber: Int? = null
var created: Date? = null
var starred: Date? = null
var albumId: String? = null
var artistId: String? = null
var type: String? = null
var bookmarkPosition: Long? = null
var originalWidth: Int? = null
var originalHeight: Int? = null
var streamId: String? = null
var channelId: String? = null
var description: String? = null

View File

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

View File

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

View File

@@ -3,6 +3,7 @@ package com.cappielloantonio.tempo.ui.activity;
import android.content.Context;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
@@ -30,6 +31,7 @@ import com.cappielloantonio.tempo.util.Preferences;
import com.cappielloantonio.tempo.viewmodel.MainViewModel;
import com.google.android.material.bottomnavigation.BottomNavigationView;
import com.google.android.material.bottomsheet.BottomSheetBehavior;
import com.google.android.material.color.DynamicColors;
import com.google.common.util.concurrent.MoreExecutors;
import java.util.Objects;
@@ -50,10 +52,10 @@ public class MainActivity extends BaseActivity {
ConnectivityStatusBroadcastReceiver connectivityStatusBroadcastReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
SplashScreen.installSplashScreen(this);
DynamicColors.applyToActivityIfAvailable(this);
super.onCreate(savedInstanceState);
@@ -92,7 +94,7 @@ public class MainActivity extends BaseActivity {
@Override
public void onBackPressed() {
if (bottomSheetBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED)
collapseBottomSheet();
collapseBottomSheetDelayed();
else
super.onBackPressed();
}
@@ -116,7 +118,7 @@ public class MainActivity extends BaseActivity {
bottomSheetBehavior.addBottomSheetCallback(bottomSheetCallback);
fragmentManager.beginTransaction().replace(R.id.player_bottom_sheet, new PlayerBottomSheetFragment(), "PlayerBottomSheet").commit();
setBottomSheetInPeek(mainViewModel.isQueueLoaded());
checkBottomSheetAfterStateChanged();
}
public void setBottomSheetInPeek(Boolean isVisible) {
@@ -135,12 +137,22 @@ public class MainActivity extends BaseActivity {
}
}
public void collapseBottomSheet() {
private void checkBottomSheetAfterStateChanged() {
final Handler handler = new Handler();
final Runnable runnable = () -> setBottomSheetInPeek(mainViewModel.isQueueLoaded());
handler.postDelayed(runnable, 100);
}
public void collapseBottomSheetDelayed() {
final Handler handler = new Handler();
final Runnable runnable = () -> bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
handler.postDelayed(runnable, 100);
}
public void expandBottomSheet() {
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
}
public void setBottomSheetDraggableState(Boolean isDraggable) {
bottomSheetBehavior.setDraggable(isDraggable);
}
@@ -155,7 +167,7 @@ public class MainActivity extends BaseActivity {
switch (state) {
case BottomSheetBehavior.STATE_HIDDEN:
hideMusicSession();
resetMusicSession();
break;
case BottomSheetBehavior.STATE_COLLAPSED:
if (playerBottomSheetFragment != null)
@@ -247,6 +259,7 @@ public class MainActivity extends BaseActivity {
}
private void goToLogin() {
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
setBottomNavigationBarVisibility(false);
setBottomSheetVisibility(false);
@@ -333,7 +346,9 @@ public class MainActivity extends BaseActivity {
private void checkConnectionType() {
if (Preferences.isWifiOnly()) {
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();
dialog.show(getSupportFragmentManager(), null);
}

View File

@@ -2,15 +2,12 @@ package com.cappielloantonio.tempo.ui.activity.base;
import android.Manifest;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.os.PowerManager;
import android.provider.Settings;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
@@ -19,11 +16,11 @@ import androidx.media3.exoplayer.offline.DownloadService;
import androidx.media3.session.MediaBrowser;
import androidx.media3.session.SessionToken;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.service.DownloaderService;
import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.util.UIUtil;
import com.google.android.gms.cast.framework.CastContext;
import com.cappielloantonio.tempo.ui.dialog.BatteryOptimizationDialog;
import com.cappielloantonio.tempo.util.Flavors;
import com.cappielloantonio.tempo.util.Preferences;
import com.google.android.material.elevation.SurfaceColors;
import com.google.common.util.concurrent.ListenableFuture;
@@ -36,8 +33,10 @@ public class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initializeCastContext();
Flavors.initializeCastContext(this);
initializeDownloader();
checkBatteryOptimization();
checkPermission();
}
@Override
@@ -47,13 +46,6 @@ public class BaseActivity extends AppCompatActivity {
initializeBrowser();
}
@Override
protected void onResume() {
super.onResume();
checkBatteryOptimization();
checkPermission();
}
@Override
protected void onStop() {
releaseBrowser();
@@ -61,7 +53,7 @@ public class BaseActivity extends AppCompatActivity {
}
private void checkBatteryOptimization() {
if (detectBatteryOptimization()) {
if (detectBatteryOptimization() && Preferences.askForOptimization()) {
showBatteryOptimizationDialog();
}
}
@@ -81,19 +73,8 @@ public class BaseActivity extends AppCompatActivity {
}
private void showBatteryOptimizationDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage(R.string.activity_battery_optimizations_summary)
.setTitle(R.string.activity_battery_optimizations_title)
.setNegativeButton(R.string.activity_negative_button, null)
.setPositiveButton(R.string.activity_neutral_button, (dialog, id) -> openPowerSettings())
.show();
}
private void openPowerSettings() {
Intent intent = new Intent();
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setAction(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS);
startActivity(intent);
BatteryOptimizationDialog dialog = new BatteryOptimizationDialog();
dialog.show(getSupportFragmentManager(), null);
}
private void initializeBrowser() {
@@ -116,11 +97,8 @@ public class BaseActivity extends AppCompatActivity {
}
}
private void initializeCastContext() {
if (UIUtil.isCastApiAvailable(this)) CastContext.getSharedInstance(this);
}
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()));
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), album.getCoverArtId())
.from(holder.itemView.getContext(), album.getCoverArtId(), CustomGlideRequest.ResourceType.Album)
.build()
.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()));
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), album.getCoverArtId())
.from(holder.itemView.getContext(), album.getCoverArtId(), CustomGlideRequest.ResourceType.Album)
.build()
.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()));
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), album.getCoverArtId())
.from(holder.itemView.getContext(), album.getCoverArtId(), CustomGlideRequest.ResourceType.Album)
.build()
.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()));
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), album.getCoverArtId())
.from(holder.itemView.getContext(), album.getCoverArtId(), CustomGlideRequest.ResourceType.Album)
.build()
.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()));
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), artist.getCoverArtId())
.from(holder.itemView.getContext(), artist.getCoverArtId(), CustomGlideRequest.ResourceType.Artist)
.build()
.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()));
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), artist.getCoverArtId())
.from(holder.itemView.getContext(), artist.getCoverArtId(), CustomGlideRequest.ResourceType.Artist)
.build()
.into(holder.item.artistCatalogueCoverImageView);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,6 +2,7 @@ package com.cappielloantonio.tempo.ui.adapter;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
@@ -42,10 +43,17 @@ public class MusicDirectoryAdapter extends RecyclerView.Adapter<MusicDirectoryAd
holder.item.musicDirectoryTitleTextView.setText(child.getTitle());
CustomGlideRequest.ResourceType type = child.isDir()
? CustomGlideRequest.ResourceType.Directory
: CustomGlideRequest.ResourceType.Song;
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), child.getCoverArtId())
.from(holder.itemView.getContext(), child.getCoverArtId(), type)
.build()
.into(holder.item.musicDirectoryCoverImageView);
holder.item.musicDirectoryMoreButton.setVisibility(child.isDir() ? View.VISIBLE : View.INVISIBLE);
holder.item.musicDirectoryPlayButton.setVisibility(child.isDir() ? View.INVISIBLE : View.VISIBLE);
}
@Override
@@ -69,6 +77,8 @@ public class MusicDirectoryAdapter extends RecyclerView.Adapter<MusicDirectoryAd
item.musicDirectoryTitleTextView.setSelected(true);
itemView.setOnClickListener(v -> onClick());
itemView.setOnLongClickListener(v -> onLongClick());
item.musicDirectoryMoreButton.setOnClickListener(v -> onClick());
}
@@ -76,7 +86,7 @@ public class MusicDirectoryAdapter extends RecyclerView.Adapter<MusicDirectoryAd
Bundle bundle = new Bundle();
if (children.get(getBindingAdapterPosition()).isDir()) {
bundle.putParcelable(Constants.MUSIC_DIRECTORY_OBJECT, children.get(getBindingAdapterPosition()));
bundle.putString(Constants.MUSIC_DIRECTORY_ID, children.get(getBindingAdapterPosition()).getId());
click.onMusicDirectoryClick(bundle);
} else {
bundle.putParcelableArrayList(Constants.TRACKS_OBJECT, new ArrayList<>(children));
@@ -84,5 +94,18 @@ public class MusicDirectoryAdapter extends RecyclerView.Adapter<MusicDirectoryAd
click.onMediaClick(bundle);
}
}
private boolean onLongClick() {
if (!children.get(getBindingAdapterPosition()).isDir()) {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.TRACK_OBJECT, children.get(getBindingAdapterPosition()));
click.onMediaLongClick(bundle);
return true;
} else {
return false;
}
}
}
}

View File

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

View File

@@ -10,15 +10,17 @@ import androidx.recyclerview.widget.RecyclerView;
import com.cappielloantonio.tempo.databinding.ItemLibraryMusicIndexBinding;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.helper.recyclerview.FastScrollbar;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.subsonic.models.Artist;
import com.cappielloantonio.tempo.util.Constants;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
@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 List<Artist> artists;
@@ -42,7 +44,7 @@ public class MusicIndexAdapter extends RecyclerView.Adapter<MusicIndexAdapter.Vi
holder.item.musicIndexTitleTextView.setText(artist.getName());
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), artist.getName())
.from(holder.itemView.getContext(), artist.getName(), CustomGlideRequest.ResourceType.Directory)
.build()
.into(holder.item.musicIndexCoverImageView);
}
@@ -57,6 +59,11 @@ public class MusicIndexAdapter extends RecyclerView.Adapter<MusicIndexAdapter.Vi
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 {
ItemLibraryMusicIndexBinding item;
@@ -73,7 +80,7 @@ public class MusicIndexAdapter extends RecyclerView.Adapter<MusicIndexAdapter.Vi
public void onClick() {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.MUSIC_INDEX_OBJECT, artists.get(getBindingAdapterPosition()));
bundle.putString(Constants.MUSIC_DIRECTORY_ID, artists.get(getBindingAdapterPosition()).getId());
click.onMusicIndexClick(bundle);
}
}

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)));
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), song.getCoverArtId())
.from(holder.itemView.getContext(), song.getCoverArtId(), CustomGlideRequest.ResourceType.Song)
.build()
.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));
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), song.getCoverArtId())
.from(holder.itemView.getContext(), song.getCoverArtId(), CustomGlideRequest.ResourceType.Song)
.build()
.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)));
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), playlist.getCoverArtId())
.from(holder.itemView.getContext(), playlist.getCoverArtId(), CustomGlideRequest.ResourceType.Playlist)
.build()
.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()));
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), podcastChannel.getCoverArtId())
.from(holder.itemView.getContext(), podcastChannel.getCoverArtId(), CustomGlideRequest.ResourceType.Podcast)
.build()
.into(holder.item.podcastChannelCatalogueCoverImageView);
}
@@ -128,14 +128,14 @@ public class PodcastChannelCatalogueAdapter extends RecyclerView.Adapter<Podcast
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.PODCAST_CHANNEL_OBJECT, podcastChannels.get(getBindingAdapterPosition()));
click.onAlbumClick(bundle);
click.onPodcastChannelClick(bundle);
}
private boolean onLongClick() {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.PODCAST_CHANNEL_OBJECT, podcastChannels.get(getBindingAdapterPosition()));
click.onAlbumLongClick(bundle);
click.onPodcastChannelLongClick(bundle);
return true;
}

View File

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

View File

@@ -2,6 +2,7 @@ package com.cappielloantonio.tempo.ui.adapter;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
@@ -18,11 +19,14 @@ import com.cappielloantonio.tempo.util.MusicUtil;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
public class PodcastEpisodeAdapter extends RecyclerView.Adapter<PodcastEpisodeAdapter.ViewHolder> {
private final ClickCallback click;
private List<PodcastEpisode> podcastEpisodes;
private List<PodcastEpisode> podcastEpisodesFull;
public PodcastEpisodeAdapter(ClickCallback click) {
this.click = click;
@@ -47,9 +51,13 @@ public class PodcastEpisodeAdapter extends RecyclerView.Adapter<PodcastEpisodeAd
holder.item.podcastDescriptionText.setText(MusicUtil.getReadableString(podcastEpisode.getDescription()));
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), podcastEpisode.getCoverArtId())
.from(holder.itemView.getContext(), podcastEpisode.getCoverArtId(), CustomGlideRequest.ResourceType.Podcast)
.build()
.into(holder.item.podcastCoverImageView);
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
@@ -58,7 +66,8 @@ public class PodcastEpisodeAdapter extends RecyclerView.Adapter<PodcastEpisodeAd
}
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();
}
@@ -85,22 +94,57 @@ public class PodcastEpisodeAdapter extends RecyclerView.Adapter<PodcastEpisodeAd
item.podcastPlayButton.setOnClickListener(v -> onClick());
item.podcastMoreButton.setOnClickListener(v -> openMore());
item.podcastDownloadRequestButton.setOnClickListener(v -> requestDownload());
}
public void onClick() {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.PODCAST_OBJECT, podcastEpisodes.get(getBindingAdapterPosition()));
PodcastEpisode podcastEpisode = 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() {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.PODCAST_OBJECT, podcastEpisodes.get(getBindingAdapterPosition()));
PodcastEpisode podcastEpisode = 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

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

View File

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

View File

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

View File

@@ -0,0 +1,41 @@
package com.cappielloantonio.tempo.ui.dialog;
import android.app.Dialog;
import android.content.Intent;
import android.os.Bundle;
import android.provider.Settings;
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.DialogBatteryOptimizationBinding;
import com.cappielloantonio.tempo.util.Preferences;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
@OptIn(markerClass = UnstableApi.class)
public class BatteryOptimizationDialog extends DialogFragment {
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
DialogBatteryOptimizationBinding bind = DialogBatteryOptimizationBinding.inflate(getLayoutInflater());
return new MaterialAlertDialogBuilder(requireContext())
.setView(bind.getRoot())
.setTitle(R.string.activity_battery_optimizations_title)
.setPositiveButton(R.string.battery_optimization_positive_button, (dialog, listener) -> openPowerSettings())
.setNeutralButton(R.string.battery_optimization_neutral_button, (dialog, listener) -> Preferences.dontAskForOptimization())
.setNegativeButton(R.string.battery_optimization_negative_button, null)
.create();
}
private void openPowerSettings() {
Intent intent = new Intent();
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setAction(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS);
startActivity(intent);
}
}

View File

@@ -1,6 +1,5 @@
package com.cappielloantonio.tempo.ui.dialog;
import android.app.AlertDialog;
import android.app.Dialog;
import android.os.Bundle;
@@ -10,22 +9,18 @@ import androidx.fragment.app.DialogFragment;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.DialogConnectionAlertBinding;
import com.cappielloantonio.tempo.util.Preferences;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.util.Objects;
public class ConnectionAlertDialog extends DialogFragment {
private static final String TAG = "ServerUnreachableDialog";
private DialogConnectionAlertBinding bind;
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
bind = DialogConnectionAlertBinding.inflate(getLayoutInflater());
DialogConnectionAlertBinding bind = DialogConnectionAlertBinding.inflate(getLayoutInflater());
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setView(bind.getRoot())
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(getActivity())
.setView(bind.getRoot())
.setTitle(R.string.connection_alert_dialog_title)
.setPositiveButton(R.string.connection_alert_dialog_positive_button, (dialog, id) -> dialog.cancel())
.setNegativeButton(R.string.connection_alert_dialog_negative_button, (dialog, id) -> dialog.cancel());
@@ -45,16 +40,12 @@ public class ConnectionAlertDialog extends DialogFragment {
setButtonAction();
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void setButtonAction() {
((AlertDialog) Objects.requireNonNull(getDialog())).getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener(v -> {
androidx.appcompat.app.AlertDialog alertDialog = (androidx.appcompat.app.AlertDialog) Objects.requireNonNull(getDialog());
alertDialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_NEUTRAL).setOnClickListener(v -> {
Preferences.setDataSavingMode(true);
Objects.requireNonNull(getDialog()).dismiss();
});
}
}
}

View File

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

View File

@@ -0,0 +1,61 @@
package com.cappielloantonio.tempo.ui.dialog;
import android.app.Dialog;
import android.os.Bundle;
import android.widget.Button;
import androidx.annotation.NonNull;
import androidx.annotation.OptIn;
import androidx.fragment.app.DialogFragment;
import androidx.media3.common.util.UnstableApi;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.DialogDownloadDirectoryBinding;
import com.cappielloantonio.tempo.interfaces.DialogClickCallback;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
@OptIn(markerClass = UnstableApi.class)
public class DownloadDirectoryDialog extends DialogFragment {
private final DialogClickCallback dialogClickCallback;
public DownloadDirectoryDialog(DialogClickCallback dialogClickCallback) {
this.dialogClickCallback = dialogClickCallback;
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
DialogDownloadDirectoryBinding bind = DialogDownloadDirectoryBinding.inflate(getLayoutInflater());
return new MaterialAlertDialogBuilder(requireContext())
.setView(bind.getRoot())
.setTitle(R.string.download_directory_dialog_title)
.setPositiveButton(R.string.download_directory_dialog_positive_button, null)
.setNegativeButton(R.string.download_directory_dialog_negative_button, null)
.create();
}
@Override
public void onResume() {
super.onResume();
setButtonAction();
}
private void setButtonAction() {
androidx.appcompat.app.AlertDialog dialog = (androidx.appcompat.app.AlertDialog) getDialog();
if (dialog != null) {
Button positiveButton = dialog.getButton(Dialog.BUTTON_POSITIVE);
positiveButton.setOnClickListener(v -> {
dialogClickCallback.onPositiveClick();
dialog.dismiss();
});
Button negativeButton = dialog.getButton(Dialog.BUTTON_NEGATIVE);
negativeButton.setOnClickListener(v -> {
dialogClickCallback.onNegativeClick();
dialog.dismiss();
});
}
}
}

View File

@@ -0,0 +1,79 @@
package com.cappielloantonio.tempo.ui.dialog;
import android.app.Dialog;
import android.os.Bundle;
import android.widget.Button;
import androidx.annotation.NonNull;
import androidx.annotation.OptIn;
import androidx.fragment.app.DialogFragment;
import androidx.media3.common.util.UnstableApi;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.DialogDownloadStorageBinding;
import com.cappielloantonio.tempo.interfaces.DialogClickCallback;
import com.cappielloantonio.tempo.util.DownloadUtil;
import com.cappielloantonio.tempo.util.Preferences;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
@OptIn(markerClass = UnstableApi.class)
public class DownloadStorageDialog extends DialogFragment {
private final DialogClickCallback dialogClickCallback;
public DownloadStorageDialog(DialogClickCallback dialogClickCallback) {
this.dialogClickCallback = dialogClickCallback;
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
DialogDownloadStorageBinding bind = DialogDownloadStorageBinding.inflate(getLayoutInflater());
return new MaterialAlertDialogBuilder(getActivity())
.setView(bind.getRoot())
.setTitle(R.string.download_storage_dialog_title)
.setPositiveButton(R.string.download_storage_external_dialog_positive_button, null)
.setNegativeButton(R.string.download_storage_internal_dialog_negative_button, null)
.create();
}
@Override
public void onResume() {
super.onResume();
setButtonAction();
}
private void setButtonAction() {
androidx.appcompat.app.AlertDialog dialog = (androidx.appcompat.app.AlertDialog) getDialog();
if (dialog != null) {
Button positiveButton = dialog.getButton(Dialog.BUTTON_POSITIVE);
positiveButton.setOnClickListener(v -> {
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

@@ -1,6 +1,5 @@
package com.cappielloantonio.tempo.ui.dialog;
import android.app.AlertDialog;
import android.app.Dialog;
import android.os.Bundle;
import android.view.View;
@@ -17,6 +16,7 @@ import com.cappielloantonio.tempo.subsonic.models.Playlist;
import com.cappielloantonio.tempo.ui.adapter.PlaylistDialogHorizontalAdapter;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.viewmodel.PlaylistChooserViewModel;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.util.Objects;
@@ -30,17 +30,15 @@ public class PlaylistChooserDialog extends DialogFragment implements ClickCallba
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
bind = DialogPlaylistChooserBinding.inflate(getLayoutInflater());
playlistChooserViewModel = new ViewModelProvider(requireActivity()).get(PlaylistChooserViewModel.class);
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setView(bind.getRoot())
return new MaterialAlertDialogBuilder(getActivity())
.setView(bind.getRoot())
.setTitle(R.string.playlist_chooser_dialog_title)
.setNeutralButton(R.string.playlist_chooser_dialog_neutral_button, (dialog, id) -> {
})
.setNegativeButton(R.string.playlist_chooser_dialog_negative_button, (dialog, id) -> dialog.cancel());
return builder.create();
.setNeutralButton(R.string.playlist_chooser_dialog_neutral_button, (dialog, id) -> { })
.setNegativeButton(R.string.playlist_chooser_dialog_negative_button, (dialog, id) -> dialog.cancel())
.create();
}
@Override
@@ -63,11 +61,12 @@ public class PlaylistChooserDialog extends DialogFragment implements ClickCallba
}
private void setButtonAction() {
((AlertDialog) Objects.requireNonNull(getDialog())).getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener(v -> {
androidx.appcompat.app.AlertDialog alertDialog = (androidx.appcompat.app.AlertDialog) Objects.requireNonNull(getDialog());
alertDialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_NEUTRAL).setOnClickListener(v -> {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.TRACK_OBJECT, playlistChooserViewModel.getSongToAdd());
PlaylistEditorDialog dialog = new PlaylistEditorDialog();
PlaylistEditorDialog dialog = new PlaylistEditorDialog(null);
dialog.setArguments(bundle);
dialog.show(requireActivity().getSupportFragmentManager(), null);

View File

@@ -1,9 +1,12 @@
package com.cappielloantonio.tempo.ui.dialog;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.fragment.app.DialogFragment;
@@ -14,10 +17,13 @@ import androidx.recyclerview.widget.RecyclerView;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.DialogPlaylistEditorBinding;
import com.cappielloantonio.tempo.interfaces.PlaylistCallback;
import com.cappielloantonio.tempo.ui.adapter.PlaylistDialogSongHorizontalAdapter;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.util.Preferences;
import com.cappielloantonio.tempo.viewmodel.PlaylistEditorViewModel;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.util.Collections;
import java.util.Objects;
@@ -25,26 +31,29 @@ import java.util.Objects;
public class PlaylistEditorDialog extends DialogFragment {
private DialogPlaylistEditorBinding bind;
private PlaylistEditorViewModel playlistEditorViewModel;
private PlaylistCallback playlistCallback;
private String playlistName;
private PlaylistDialogSongHorizontalAdapter playlistDialogSongHorizontalAdapter;
public PlaylistEditorDialog(PlaylistCallback playlistCallback) {
this.playlistCallback = playlistCallback;
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
bind = DialogPlaylistEditorBinding.inflate(getLayoutInflater());
playlistEditorViewModel = new ViewModelProvider(requireActivity()).get(PlaylistEditorViewModel.class);
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setView(bind.getRoot())
return new MaterialAlertDialogBuilder(getActivity())
.setView(bind.getRoot())
.setTitle(R.string.playlist_editor_dialog_title)
.setPositiveButton(R.string.playlist_editor_dialog_positive_button, (dialog, id) -> {
})
.setPositiveButton(R.string.playlist_editor_dialog_positive_button, (dialog, id) -> { })
.setNeutralButton(R.string.playlist_editor_dialog_neutral_button, (dialog, id) -> dialog.cancel())
.setNegativeButton(R.string.playlist_editor_dialog_negative_button, (dialog, id) -> dialog.cancel());
return builder.create();
.setNegativeButton(R.string.playlist_editor_dialog_negative_button, (dialog, id) -> dialog.cancel())
.create();
}
@Override
@@ -77,7 +86,9 @@ public class PlaylistEditorDialog extends DialogFragment {
}
private void setButtonAction() {
((AlertDialog) Objects.requireNonNull(getDialog())).getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> {
androidx.appcompat.app.AlertDialog alertDialog = (androidx.appcompat.app.AlertDialog) Objects.requireNonNull(getDialog());
alertDialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> {
if (validateInput()) {
if (playlistEditorViewModel.getSongToAdd() != null) {
playlistEditorViewModel.createPlaylist(playlistName);
@@ -85,14 +96,24 @@ public class PlaylistEditorDialog extends DialogFragment {
playlistEditorViewModel.updatePlaylist(playlistName);
}
Objects.requireNonNull(getDialog()).dismiss();
dialogDismiss();
}
});
((AlertDialog) Objects.requireNonNull(getDialog())).getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener(v -> {
alertDialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_NEUTRAL).setOnClickListener(v -> {
playlistEditorViewModel.deletePlaylist();
Objects.requireNonNull(getDialog()).dismiss();
dialogDismiss();
});
bind.playlistShareButton.setOnClickListener(view -> {
playlistEditorViewModel.sharePlaylist().observe(requireActivity(), sharedPlaylist -> {
ClipboardManager clipboardManager = (ClipboardManager) requireActivity().getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clipData = ClipData.newPlainText(getString(R.string.app_name), sharedPlaylist.getUrl());
clipboardManager.setPrimaryClip(clipData);
});
});
bind.playlistShareButton.setVisibility(Preferences.isSharingEnabled() ? View.VISIBLE : View.GONE);
}
private void initSongsView() {
@@ -102,7 +123,9 @@ public class PlaylistEditorDialog extends DialogFragment {
playlistDialogSongHorizontalAdapter = new PlaylistDialogSongHorizontalAdapter();
bind.playlistSongRecyclerView.setAdapter(playlistDialogSongHorizontalAdapter);
playlistEditorViewModel.getPlaylistSongLiveList().observe(requireActivity(), songs -> playlistDialogSongHorizontalAdapter.setItems(songs));
playlistEditorViewModel.getPlaylistSongLiveList().observe(requireActivity(), songs -> {
if (songs != null) playlistDialogSongHorizontalAdapter.setItems(songs);
});
new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.LEFT) {
int originalPosition = -1;
@@ -157,4 +180,11 @@ public class PlaylistEditorDialog extends DialogFragment {
return true;
}
private void dialogDismiss() {
Objects.requireNonNull(getDialog()).dismiss();
if (playlistCallback != null) {
playlistCallback.onDismiss();
}
}
}

View File

@@ -1,9 +1,9 @@
package com.cappielloantonio.tempo.ui.dialog;
import android.app.AlertDialog;
import android.app.Dialog;
import android.os.Bundle;
import android.text.TextUtils;
import android.widget.Button;
import androidx.annotation.NonNull;
import androidx.fragment.app.DialogFragment;
@@ -13,6 +13,7 @@ import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.DialogPodcastChannelEditorBinding;
import com.cappielloantonio.tempo.interfaces.PodcastCallback;
import com.cappielloantonio.tempo.viewmodel.PodcastChannelEditorViewModel;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.util.Objects;
@@ -31,17 +32,15 @@ public class PodcastChannelEditorDialog extends DialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
bind = DialogPodcastChannelEditorBinding.inflate(getLayoutInflater());
podcastChannelEditorViewModel = new ViewModelProvider(requireActivity()).get(PodcastChannelEditorViewModel.class);
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setView(bind.getRoot())
return new MaterialAlertDialogBuilder(getActivity())
.setView(bind.getRoot())
.setTitle(R.string.podcast_channel_editor_dialog_title)
.setPositiveButton(R.string.radio_editor_dialog_positive_button, (dialog, id) -> {
})
.setNegativeButton(R.string.radio_editor_dialog_negative_button, (dialog, id) -> dialog.cancel());
return builder.create();
.setPositiveButton(R.string.radio_editor_dialog_positive_button, (dialog, id) -> { })
.setNegativeButton(R.string.radio_editor_dialog_negative_button, (dialog, id) -> dialog.cancel())
.create();
}
@Override
@@ -58,14 +57,19 @@ public class PodcastChannelEditorDialog extends DialogFragment {
}
private void setButtonAction() {
((AlertDialog) Objects.requireNonNull(getDialog())).getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> {
if (validateInput()) {
podcastChannelEditorViewModel.createChannel(channelUrl);
dismissDialog();
}
});
androidx.appcompat.app.AlertDialog dialog = (androidx.appcompat.app.AlertDialog) getDialog();
if (dialog != null) {
Button positiveButton = dialog.getButton(Dialog.BUTTON_POSITIVE);
positiveButton.setOnClickListener(v -> {
if (validateInput()) {
podcastChannelEditorViewModel.createChannel(channelUrl);
dismissDialog();
}
});
}
}
private boolean validateInput() {
channelUrl = Objects.requireNonNull(bind.podcastChannelRssUrlNameTextView.getText()).toString().trim();

View File

@@ -1,6 +1,5 @@
package com.cappielloantonio.tempo.ui.dialog;
import android.app.AlertDialog;
import android.app.Dialog;
import android.os.Bundle;
import android.text.TextUtils;
@@ -15,6 +14,7 @@ import com.cappielloantonio.tempo.interfaces.RadioCallback;
import com.cappielloantonio.tempo.subsonic.models.InternetRadioStation;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.viewmodel.RadioEditorViewModel;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.util.Objects;
@@ -35,26 +35,36 @@ public class RadioEditorDialog extends DialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
bind = DialogRadioEditorBinding.inflate(getLayoutInflater());
radioEditorViewModel = new ViewModelProvider(requireActivity()).get(RadioEditorViewModel.class);
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setView(bind.getRoot())
return new MaterialAlertDialogBuilder(requireContext())
.setView(bind.getRoot())
.setTitle(R.string.radio_editor_dialog_title)
.setPositiveButton(R.string.radio_editor_dialog_positive_button, (dialog, id) -> {
if (validateInput()) {
if (radioEditorViewModel.getRadioToEdit() == null) {
radioEditorViewModel.createRadio(radioName, radioStreamURL, radioHomepageURL.isEmpty() ? null : radioHomepageURL);
} else {
radioEditorViewModel.updateRadio(radioName, radioStreamURL, radioHomepageURL.isEmpty() ? null : radioHomepageURL);
}
dismissDialog();
}
})
.setNeutralButton(R.string.radio_editor_dialog_neutral_button, (dialog, id) -> dialog.cancel())
.setNegativeButton(R.string.radio_editor_dialog_negative_button, (dialog, id) -> dialog.cancel());
return builder.create();
.setNeutralButton(R.string.radio_editor_dialog_neutral_button, (dialog, id) -> {
radioEditorViewModel.deleteRadio();
dismissDialog();
})
.setNegativeButton(R.string.radio_editor_dialog_negative_button, (dialog, id) -> {
dialog.cancel();
})
.create();
}
@Override
public void onStart() {
super.onStart();
setParameterInfo();
setButtonAction();
}
@Override
@@ -75,25 +85,6 @@ public class RadioEditorDialog extends DialogFragment {
}
}
private void setButtonAction() {
((AlertDialog) Objects.requireNonNull(getDialog())).getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> {
if (validateInput()) {
if (radioEditorViewModel.getRadioToEdit() == null) {
radioEditorViewModel.createRadio(radioName, radioStreamURL, radioHomepageURL.isEmpty() ? null : radioHomepageURL);
} else {
radioEditorViewModel.updateRadio(radioName, radioStreamURL, radioHomepageURL.isEmpty() ? null : radioHomepageURL);
}
dismissDialog();
}
});
((AlertDialog) Objects.requireNonNull(getDialog())).getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener(v -> {
radioEditorViewModel.deleteRadio();
dismissDialog();
});
}
private boolean validateInput() {
radioName = Objects.requireNonNull(bind.internetRadioStationNameTextView.getText()).toString().trim();
radioStreamURL = Objects.requireNonNull(bind.internetRadioStationStreamUrlTextView.getText()).toString().trim();

View File

@@ -1,6 +1,5 @@
package com.cappielloantonio.tempo.ui.dialog;
import android.app.AlertDialog;
import android.app.Dialog;
import android.os.Bundle;
@@ -12,6 +11,7 @@ import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.DialogRatingBinding;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.viewmodel.RatingViewModel;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
public class RatingDialog extends DialogFragment {
private static final String TAG = "ServerSignupDialog";
@@ -25,14 +25,12 @@ public class RatingDialog extends DialogFragment {
bind = DialogRatingBinding.inflate(getLayoutInflater());
ratingViewModel = new ViewModelProvider(requireActivity()).get(RatingViewModel.class);
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setView(bind.getRoot())
return new MaterialAlertDialogBuilder(getActivity())
.setView(bind.getRoot())
.setTitle(R.string.rating_dialog_title)
.setNegativeButton(R.string.rating_dialog_negative_button, (dialog, id) -> dialog.cancel())
.setPositiveButton(R.string.rating_dialog_positive_button, (dialog, id) -> ratingViewModel.rate((int) bind.ratingBar.getRating()));
return builder.create();
.setPositiveButton(R.string.rating_dialog_positive_button, (dialog, id) -> ratingViewModel.rate((int) bind.ratingBar.getRating()))
.create();
}
@Override

View File

@@ -1,6 +1,5 @@
package com.cappielloantonio.tempo.ui.dialog;
import android.app.AlertDialog;
import android.app.Dialog;
import android.os.Bundle;
import android.text.TextUtils;
@@ -14,6 +13,7 @@ import com.cappielloantonio.tempo.databinding.DialogServerSignupBinding;
import com.cappielloantonio.tempo.model.Server;
import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.viewmodel.LoginViewModel;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.util.Objects;
import java.util.UUID;
@@ -33,21 +33,17 @@ public class ServerSignupDialog extends DialogFragment {
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
loginViewModel = new ViewModelProvider(requireActivity()).get(LoginViewModel.class);
bind = DialogServerSignupBinding.inflate(getLayoutInflater());
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
loginViewModel = new ViewModelProvider(requireActivity()).get(LoginViewModel.class);
builder.setView(bind.getRoot())
return new MaterialAlertDialogBuilder(getActivity())
.setView(bind.getRoot())
.setTitle(R.string.server_signup_dialog_title)
.setNeutralButton(R.string.server_signup_dialog_neutral_button, (dialog, id) -> {
})
.setPositiveButton(R.string.server_signup_dialog_positive_button, (dialog, id) -> {
})
.setNegativeButton(R.string.server_signup_dialog_negative_button, (dialog, id) -> dialog.cancel());
return builder.create();
.setNeutralButton(R.string.server_signup_dialog_neutral_button, (dialog, id) -> { })
.setPositiveButton(R.string.server_signup_dialog_positive_button, (dialog, id) -> { })
.setNegativeButton(R.string.server_signup_dialog_negative_button, (dialog, id) -> dialog.cancel())
.create();
}
@Override
@@ -81,14 +77,16 @@ public class ServerSignupDialog extends DialogFragment {
}
private void setButtonAction() {
((AlertDialog) Objects.requireNonNull(getDialog())).getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> {
androidx.appcompat.app.AlertDialog alertDialog = (androidx.appcompat.app.AlertDialog) Objects.requireNonNull(getDialog());
alertDialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> {
if (validateInput()) {
saveServerPreference();
Objects.requireNonNull(getDialog()).dismiss();
}
});
((AlertDialog) Objects.requireNonNull(getDialog())).getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener(v -> {
alertDialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_NEUTRAL).setOnClickListener(v -> {
loginViewModel.deleteServer(null);
Objects.requireNonNull(getDialog()).dismiss();
});
@@ -116,6 +114,11 @@ public class ServerSignupDialog extends DialogFragment {
return false;
}
if (!server.matches("^https?://(.*)")) {
bind.serverTextView.setError(getString(R.string.error_server_prefix));
return false;
}
return true;
}

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